mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
[FIX] tools: adapt playground to owl 2
This commit is contained in:
committed by
Géry Debongnie
parent
dabc24cee3
commit
efd934d2b1
+76
-169
@@ -1,19 +1,11 @@
|
|||||||
import { SAMPLES } from "./samples.js";
|
import { SAMPLES } from "./samples.js";
|
||||||
const { mount, hooks } = owl;
|
import { debounce, loadJS } from "./utils.js";
|
||||||
const { useState, useRef, onMounted, onWillUnmount } = hooks;
|
import { exportStandaloneApp } from "./export_snippet.js";
|
||||||
|
const { useState, useRef, onMounted, onWillUnmount, onPatched, onWillUpdateProps } = owl;
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
// Constants, helpers, utils
|
// Constants, helpers, utils
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
let owlJS;
|
|
||||||
|
|
||||||
async function owlSourceCode() {
|
|
||||||
if (owlJS) {
|
|
||||||
return owlJS;
|
|
||||||
}
|
|
||||||
const result = await fetch("../owl.js");
|
|
||||||
owlJS = await result.text();
|
|
||||||
return owlJS;
|
|
||||||
}
|
|
||||||
|
|
||||||
const MODES = {
|
const MODES = {
|
||||||
js: "ace/mode/javascript",
|
js: "ace/mode/javascript",
|
||||||
@@ -24,77 +16,41 @@ const MODES = {
|
|||||||
const DEFAULT_XML = `<templates>
|
const DEFAULT_XML = `<templates>
|
||||||
</templates>`;
|
</templates>`;
|
||||||
|
|
||||||
const DEFAULT_HTML = `<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<title>OWL App</title>
|
|
||||||
<link rel="icon" href="data:,">
|
|
||||||
|
|
||||||
<script src="owl.js"></script>
|
|
||||||
<link rel="stylesheet" href="app.css">
|
|
||||||
<script src="app.js"></script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
`;
|
|
||||||
|
|
||||||
const APP_PY = `#!/usr/bin/env python3
|
|
||||||
|
|
||||||
import threading
|
|
||||||
import time
|
|
||||||
|
|
||||||
from http.server import SimpleHTTPRequestHandler, HTTPServer
|
|
||||||
|
|
||||||
def start_server():
|
|
||||||
SimpleHTTPRequestHandler.extensions_map['.js'] = 'application/javascript'
|
|
||||||
httpd = HTTPServer(('0.0.0.0', 3600), SimpleHTTPRequestHandler)
|
|
||||||
httpd.serve_forever()
|
|
||||||
|
|
||||||
url = 'http://127.0.0.1:3600'
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
print("Owl Application")
|
|
||||||
print("---------------")
|
|
||||||
print("Server running on: {}".format(url))
|
|
||||||
threading.Thread(target=start_server, daemon=True).start()
|
|
||||||
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
time.sleep(1)
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
httpd.server_close()
|
|
||||||
quit(0)
|
|
||||||
`;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Make an iframe, with all the js, css and xml properly injected.
|
* Make an iframe, with all the js, css and xml properly injected.
|
||||||
*/
|
*/
|
||||||
function makeCodeIframe(js, css, xml) {
|
function makeCodeIframe(js, css, xml) {
|
||||||
const sanitizedXML = xml.replace(/<!--[\s\S]*?-->/g, "").replace(/`/g, '\\\`');
|
const sanitizedXML = xml.replace(/<!--[\s\S]*?-->/g, "").replace(/`/g, '\\\`');
|
||||||
|
|
||||||
|
|
||||||
// create iframe
|
// create iframe
|
||||||
const iframe = document.createElement("iframe");
|
const iframe = document.createElement("iframe");
|
||||||
|
|
||||||
iframe.onload = () => {
|
iframe.onload = () => {
|
||||||
const doc = iframe.contentDocument;
|
const doc = iframe.contentDocument;
|
||||||
|
const utilsMod = doc.createElement("script");
|
||||||
|
utilsMod.setAttribute("type", "module");
|
||||||
|
utilsMod.setAttribute("src", "utils.js");
|
||||||
|
doc.head.appendChild(utilsMod);
|
||||||
// inject js
|
// inject js
|
||||||
const owlScript = doc.createElement("script");
|
const owlScript = doc.createElement("script");
|
||||||
owlScript.type = "text/javascript";
|
owlScript.type = "text/javascript";
|
||||||
owlScript.src = "../owl.js";
|
owlScript.src = "../owl.js";
|
||||||
owlScript.addEventListener("load", () => {
|
owlScript.addEventListener("load", () => {
|
||||||
const script = doc.createElement("script");
|
const script = doc.createElement("script");
|
||||||
script.type = "text/javascript";
|
script.type = "module";
|
||||||
const content = `
|
const content = `
|
||||||
{
|
import * as utils from "./utils.js";
|
||||||
owl.config.mode = 'dev';
|
(function (owl) {
|
||||||
let templates = \`${sanitizedXML}\`;
|
const _configure = owl.App.prototype.configure;
|
||||||
const qweb = new owl.QWeb({ templates });
|
owl.App.prototype.configure = function configureOverriden(config) {
|
||||||
owl.Component.env = { qweb };
|
config = Object.assign({ dev: true }, config);
|
||||||
}
|
this.addTemplates(\`${sanitizedXML}\`);
|
||||||
${js}`;
|
return _configure.call(this, config);
|
||||||
|
}
|
||||||
|
})(owl);
|
||||||
|
(async function() {
|
||||||
|
${js}
|
||||||
|
})()`;
|
||||||
script.innerHTML = content;
|
script.innerHTML = content;
|
||||||
doc.body.appendChild(script);
|
doc.body.appendChild(script);
|
||||||
});
|
});
|
||||||
@@ -108,58 +64,6 @@ function makeCodeIframe(js, css, xml) {
|
|||||||
return iframe;
|
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 = `
|
|
||||||
/**
|
|
||||||
* This is the javascript code defined in the playground.
|
|
||||||
* In a larger application, this code should probably be moved in different
|
|
||||||
* sub files.
|
|
||||||
*/
|
|
||||||
function app() {
|
|
||||||
${processedJS}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialization code
|
|
||||||
* This code load templates, and make sure everything is properly connected.
|
|
||||||
*/
|
|
||||||
async function start() {
|
|
||||||
let templates;
|
|
||||||
try {
|
|
||||||
templates = await owl.utils.loadFile('app.xml');
|
|
||||||
} catch(e) {
|
|
||||||
console.error(\`This app requires a static server. If you have python installed, try 'python app.py'\`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const env = { qweb: new owl.QWeb({templates})};
|
|
||||||
owl.Component.env = env;
|
|
||||||
await owl.utils.whenReady();
|
|
||||||
app();
|
|
||||||
}
|
|
||||||
|
|
||||||
start();
|
|
||||||
`;
|
|
||||||
|
|
||||||
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" });
|
|
||||||
}
|
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
// SAMPLES
|
// SAMPLES
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
@@ -189,7 +93,7 @@ function deleteLocalSample() {
|
|||||||
|
|
||||||
function useSamples() {
|
function useSamples() {
|
||||||
const samples = loadSamples();
|
const samples = loadSamples();
|
||||||
const component = owl.Component.current;
|
const component = owl.useComponent();
|
||||||
let interval;
|
let interval;
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
@@ -205,57 +109,57 @@ function useSamples() {
|
|||||||
});
|
});
|
||||||
return samples;
|
return samples;
|
||||||
}
|
}
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
// Tabbed editor
|
// Tabbed editor
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
class TabbedEditor extends owl.Component {
|
class TabbedEditor extends owl.Component {
|
||||||
constructor(parent, props) {
|
setup() {
|
||||||
super(parent, props);
|
const props = this.props;
|
||||||
this.state = useState({
|
this.state = useState({
|
||||||
currentTab: props.js !== false ? "js" : props.xml ? "xml" : "css"
|
currentTab: props.js !== false ? "js" : props.xml ? "xml" : "css"
|
||||||
});
|
});
|
||||||
this.setTab = owl.utils.debounce(this.setTab, 250, true);
|
this.setTab = debounce(this.setTab.bind(this), 250, true);
|
||||||
|
|
||||||
this.sessions = {};
|
this.sessions = {};
|
||||||
this._setupSessions(props);
|
this._setupSessions(props);
|
||||||
this.editorNode = useRef("editor");
|
this.editorNode = useRef("editor");
|
||||||
this._updateCode = this._updateCode.bind(this);
|
this._updateCode = this._updateCode.bind(this);
|
||||||
}
|
|
||||||
|
|
||||||
mounted() {
|
onMounted(() => {
|
||||||
this.editor = this.editor || ace.edit(this.editorNode.el);
|
this.editor = this.editor || ace.edit(this.editorNode.el);
|
||||||
|
|
||||||
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.setSession(this.sessions[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._updateCode);
|
this.editor.on("blur", this._updateCode);
|
||||||
this.interval = setInterval(this._updateCode, 3000);
|
this.interval = setInterval(this._updateCode, 3000);
|
||||||
}
|
});
|
||||||
|
|
||||||
willUnmount() {
|
onPatched(() => {
|
||||||
clearInterval(this.interval);
|
const session = this.sessions[this.state.currentTab];
|
||||||
this.editor.off("blur", this._updateCode);
|
let content = this.props[this.state.currentTab];
|
||||||
}
|
if (content === false) {
|
||||||
willUpdateProps(nextProps) {
|
const tab = this.props.js !== false ? "js" : this.props.xml ? "xml" : "css";
|
||||||
this._setupSessions(nextProps);
|
content = this.props[tab];
|
||||||
}
|
this.state.currentTab = tab;
|
||||||
|
}
|
||||||
|
if (this.editor.getValue() !== content) {
|
||||||
|
session.setValue(content, -1);
|
||||||
|
this.editor.setSession(session);
|
||||||
|
this.editor.resize();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
patched() {
|
onWillUpdateProps((nextProps) => this._setupSessions(nextProps));
|
||||||
const session = this.sessions[this.state.currentTab];
|
|
||||||
let content = this.props[this.state.currentTab];
|
onWillUnmount(() => {
|
||||||
if (content === false) {
|
clearInterval(this.interval);
|
||||||
const tab = this.props.js !== false ? "js" : this.props.xml ? "xml" : "css";
|
this.editor.off("blur", this._updateCode);
|
||||||
content = this.props[tab];
|
});
|
||||||
this.state.currentTab = tab;
|
|
||||||
}
|
|
||||||
if (this.editor.getValue() !== content) {
|
|
||||||
session.setValue(content, -1);
|
|
||||||
this.editor.setSession(session);
|
|
||||||
this.editor.resize();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setTab(tab) {
|
setTab(tab) {
|
||||||
@@ -298,20 +202,20 @@ class TabbedEditor extends owl.Component {
|
|||||||
const editorValue = this.editor.getValue();
|
const editorValue = this.editor.getValue();
|
||||||
const propsValue = this.props[this.state.currentTab];
|
const propsValue = this.props[this.state.currentTab];
|
||||||
if (editorValue !== propsValue) {
|
if (editorValue !== propsValue) {
|
||||||
this.trigger("updateCode", {
|
this.props.updateCode({
|
||||||
type: this.state.currentTab,
|
type: this.state.currentTab,
|
||||||
value: editorValue
|
value: editorValue
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
TabbedEditor.template = "TabbedEditor";
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
// MAIN APP
|
// MAIN APP
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
class App extends owl.Component {
|
class App extends owl.Component {
|
||||||
constructor(...args) {
|
setup() {
|
||||||
super(...args);
|
|
||||||
this.version = owl.__info__.version;
|
this.version = owl.__info__.version;
|
||||||
this.SAMPLES = useSamples();
|
this.SAMPLES = useSamples();
|
||||||
this.isDirty = false;
|
this.isDirty = false;
|
||||||
@@ -326,10 +230,11 @@ class App extends owl.Component {
|
|||||||
topPanelHeight: null
|
topPanelHeight: null
|
||||||
});
|
});
|
||||||
|
|
||||||
this.toggleLayout = owl.utils.debounce(this.toggleLayout, 250, true);
|
this.toggleLayout = debounce(this.toggleLayout, 250, true);
|
||||||
this.runCode = owl.utils.debounce(this.runCode, 250, true);
|
this.runCode = debounce(this.runCode, 250, true);
|
||||||
this.downloadCode = owl.utils.debounce(this.downloadCode, 250, true);
|
this.downloadCode = debounce(this.downloadCode, 250, true);
|
||||||
this.content = useRef("content");
|
this.content = useRef("content");
|
||||||
|
this.updateCode = this.updateCode.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
runCode() {
|
runCode() {
|
||||||
@@ -379,9 +284,9 @@ class App extends owl.Component {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
updateCode(ev) {
|
updateCode({type, value}) {
|
||||||
if (this.state[ev.detail.type] !== ev.detail.value) {
|
if (this.state[type] !== value) {
|
||||||
this.state[ev.detail.type] = ev.detail.value;
|
this.state[type] = value;
|
||||||
this.isDirty = true;
|
this.isDirty = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -401,12 +306,13 @@ class App extends owl.Component {
|
|||||||
|
|
||||||
async downloadCode() {
|
async downloadCode() {
|
||||||
const { js, css, xml } = this.state;
|
const { js, css, xml } = this.state;
|
||||||
const content = await makeApp(js, css, xml);
|
const content = await exportStandaloneApp(js, css, xml);
|
||||||
await owl.utils.loadJS("libs/FileSaver.min.js");
|
await loadJS("libs/FileSaver.min.js");
|
||||||
saveAs(content, "app.zip");
|
saveAs(content, "app.zip");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
App.components = { TabbedEditor };
|
App.components = { TabbedEditor };
|
||||||
|
App.template = "App";
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
// Application initialization
|
// Application initialization
|
||||||
@@ -416,12 +322,13 @@ async function start() {
|
|||||||
const commit = `https://github.com/odoo/owl/commit/${owl.__info__.hash}`;
|
const commit = `https://github.com/odoo/owl/commit/${owl.__info__.hash}`;
|
||||||
console.info(`This application is using Owl built with the following commit:`, commit);
|
console.info(`This application is using Owl built with the following commit:`, commit);
|
||||||
const [templates] = await Promise.all([
|
const [templates] = await Promise.all([
|
||||||
owl.utils.loadFile("templates.xml"),
|
owl.loadFile("templates.xml"),
|
||||||
owl.utils.whenReady()
|
owl.whenReady()
|
||||||
]);
|
]);
|
||||||
const qweb = new owl.QWeb({ templates });
|
const rootApp = new owl.App(App);
|
||||||
const env = { qweb };
|
rootApp.addTemplates(templates);
|
||||||
await mount(App, {target: document.body, env});
|
|
||||||
|
await rootApp.mount(document.body);
|
||||||
}
|
}
|
||||||
|
|
||||||
start();
|
start();
|
||||||
|
|||||||
@@ -0,0 +1,123 @@
|
|||||||
|
import { loadJS } from "./utils.js";
|
||||||
|
|
||||||
|
const sourceCache = new Map();
|
||||||
|
async function getSourceCode(filePath) {
|
||||||
|
if (sourceCache.has(filePath)) {
|
||||||
|
return sourceCache.get(filePath);
|
||||||
|
}
|
||||||
|
let source = await fetch(`${filePath}.js`);
|
||||||
|
source = await source.text();
|
||||||
|
sourceCache.set(filePath, source);
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DEFAULT_HTML = `<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>OWL App</title>
|
||||||
|
<link rel="icon" href="data:,">
|
||||||
|
|
||||||
|
<script src="owl.js"></script>
|
||||||
|
<link rel="stylesheet" href="app.css">
|
||||||
|
<script type="module" src="app.js"></script>
|
||||||
|
<script type="module" src="utils.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`;
|
||||||
|
|
||||||
|
const APP_PY = `#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
|
||||||
|
from http.server import SimpleHTTPRequestHandler, HTTPServer
|
||||||
|
|
||||||
|
def start_server():
|
||||||
|
SimpleHTTPRequestHandler.extensions_map['.js'] = 'application/javascript'
|
||||||
|
httpd = HTTPServer(('0.0.0.0', 3600), SimpleHTTPRequestHandler)
|
||||||
|
httpd.serve_forever()
|
||||||
|
|
||||||
|
url = 'http://127.0.0.1:3600'
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
print("Owl Application")
|
||||||
|
print("---------------")
|
||||||
|
print("Server running on: {}".format(url))
|
||||||
|
threading.Thread(target=start_server, daemon=True).start()
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
time.sleep(1)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
httpd.server_close()
|
||||||
|
quit(0)
|
||||||
|
`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make a zip file containing a functioning application
|
||||||
|
*/
|
||||||
|
export async function exportStandaloneApp(js, css, xml) {
|
||||||
|
await loadJS("libs/jszip.min.js");
|
||||||
|
|
||||||
|
const zip = new JSZip();
|
||||||
|
const processedJS = js
|
||||||
|
.split("\n")
|
||||||
|
.map(l => (l === "" ? "" : " " + l))
|
||||||
|
.join("\n");
|
||||||
|
|
||||||
|
const JS = `
|
||||||
|
/**
|
||||||
|
* This is the javascript code defined in the playground.
|
||||||
|
* In a larger application, this code should probably be moved in different
|
||||||
|
* sub files.
|
||||||
|
*/
|
||||||
|
import * as utils from "./utils.js";
|
||||||
|
async function app() {
|
||||||
|
${processedJS}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialization code
|
||||||
|
* This code load templates, and make sure everything is properly connected.
|
||||||
|
*/
|
||||||
|
function prepareOWLApp(templates) {
|
||||||
|
const _configure = owl.App.prototype.configure;
|
||||||
|
owl.App.prototype.configure = function configureOverriden(config) {
|
||||||
|
config = Object.assign({ dev: true }, config);
|
||||||
|
this.addTemplates(templates);
|
||||||
|
return _configure.call(this, config);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function start() {
|
||||||
|
let templates;
|
||||||
|
try {
|
||||||
|
templates = await owl.loadFile('app.xml');
|
||||||
|
} catch(e) {
|
||||||
|
console.error(\`This app requires a static server. If you have python installed, try 'python app.py'\`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
prepareOWLApp(templates);
|
||||||
|
app();
|
||||||
|
}
|
||||||
|
|
||||||
|
start();
|
||||||
|
`;
|
||||||
|
|
||||||
|
const [owlSrc, utilsSrc] = await Promise.all([
|
||||||
|
getSourceCode("../owl"),
|
||||||
|
getSourceCode("./utils"),
|
||||||
|
]);
|
||||||
|
|
||||||
|
zip.file("app.js", JS);
|
||||||
|
zip.file("utils.js", utilsSrc);
|
||||||
|
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", owlSrc);
|
||||||
|
return zip.generateAsync({ type: "blob" });
|
||||||
|
}
|
||||||
@@ -12,6 +12,8 @@
|
|||||||
|
|
||||||
<!-- Application JS/CSS -->
|
<!-- Application JS/CSS -->
|
||||||
<link rel="stylesheet" href="playground.css">
|
<link rel="stylesheet" href="playground.css">
|
||||||
|
<script type="module" src="export_snippet.js"></script>
|
||||||
|
<script type="module" src="utils.js"></script>
|
||||||
<script type="module" src="app.js"></script>
|
<script type="module" src="app.js"></script>
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
|
|||||||
+316
-531
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,8 @@
|
|||||||
<templates>
|
<templates>
|
||||||
<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" t-key="tab">
|
||||||
<a t-ref="{{tab}}" t-if="props[tab] !== false" t-key="tab" class="tab flash" t-att-class="{active: state.currentTab===tab}" t-on-click="setTab(tab)">
|
<a t-ref="{{tab}}" t-if="props[tab] !== false" t-key="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>
|
||||||
@@ -12,8 +12,7 @@
|
|||||||
|
|
||||||
<div t-name="App" class="playground">
|
<div t-name="App" class="playground">
|
||||||
<div class="left-bar" t-att-class="{split: state.splitLayout}"
|
<div class="left-bar" t-att-class="{split: state.splitLayout}"
|
||||||
t-att-style="leftPaneStyle"
|
t-att-style="leftPaneStyle">
|
||||||
t-on-updateCode="updateCode">
|
|
||||||
<div class="menubar">
|
<div class="menubar">
|
||||||
<a class="btn run-code flash" t-on-click="runCode" title="Execute this Code">▶ Run</a>
|
<a class="btn run-code flash" t-on-click="runCode" title="Execute this Code">▶ Run</a>
|
||||||
<select t-on-change="setSample">
|
<select t-on-change="setSample">
|
||||||
@@ -28,7 +27,8 @@
|
|||||||
js="state.js"
|
js="state.js"
|
||||||
css="!state.splitLayout and state.css"
|
css="!state.splitLayout and state.css"
|
||||||
xml="!state.splitLayout and state.xml"
|
xml="!state.splitLayout and state.xml"
|
||||||
t-att-style="topEditorStyle"/>
|
style="topEditorStyle"
|
||||||
|
updateCode="updateCode"/>
|
||||||
<t t-if="state.splitLayout">
|
<t t-if="state.splitLayout">
|
||||||
<div class="separator horizontal"/>
|
<div class="separator horizontal"/>
|
||||||
<TabbedEditor
|
<TabbedEditor
|
||||||
@@ -36,7 +36,8 @@
|
|||||||
css="state.css"
|
css="state.css"
|
||||||
xml="state.xml"
|
xml="state.xml"
|
||||||
resizeable="true"
|
resizeable="true"
|
||||||
t-on-updatePanelHeight="updatePanelHeight"/>
|
onUpdatePanelHeight="updatePanelHeight"
|
||||||
|
updateCode="updateCode"/>
|
||||||
</t>
|
</t>
|
||||||
</div>
|
</div>
|
||||||
<div class="separator vertical" t-on-mousedown="onMouseDown"/>
|
<div class="separator vertical" t-on-mousedown="onMouseDown"/>
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
export function debounce(func, wait, immediate) {
|
||||||
|
let timeout;
|
||||||
|
return function () {
|
||||||
|
const context = this;
|
||||||
|
const args = arguments;
|
||||||
|
function later() {
|
||||||
|
timeout = null;
|
||||||
|
if (!immediate) {
|
||||||
|
func.apply(context, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const callNow = immediate && !timeout;
|
||||||
|
clearTimeout(timeout);
|
||||||
|
timeout = setTimeout(later, wait);
|
||||||
|
if (callNow) {
|
||||||
|
func.apply(context, args);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useBus(bus, evName, cb) {
|
||||||
|
bus.addEventListener(evName, cb);
|
||||||
|
owl.onWillUnmount(() => {
|
||||||
|
bus.removeEventListener(evName, cb);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadedScripts = {};
|
||||||
|
|
||||||
|
export function loadJS(url) {
|
||||||
|
if (url in loadedScripts) {
|
||||||
|
return loadedScripts[url];
|
||||||
|
}
|
||||||
|
const promise = new Promise(function (resolve, reject) {
|
||||||
|
const script = document.createElement("script");
|
||||||
|
script.type = "text/javascript";
|
||||||
|
script.src = url;
|
||||||
|
script.onload = function () {
|
||||||
|
resolve();
|
||||||
|
};
|
||||||
|
script.onerror = function () {
|
||||||
|
reject(`Error loading file '${url}'`);
|
||||||
|
};
|
||||||
|
const head = document.head || document.getElementsByTagName("head")[0];
|
||||||
|
head.appendChild(script);
|
||||||
|
});
|
||||||
|
loadedScripts[url] = promise;
|
||||||
|
return promise;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user