import { SAMPLES as samples } from "./samples.js" ; const { useState, useRef, onMounted, onWillUnmount } = owl.hooks; //------------------------------------------------------------------------------ // 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 = { js: "ace/mode/javascript", css: "ace/mode/css", xml: "ace/mode/xml" }; const DEFAULT_XML = ` `; const DEFAULT_HTML = ` OWL App `; 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. */ function makeCodeIframe(js, css, xml) { const sanitizedXML = xml.replace(//g, ""); // create iframe const iframe = document.createElement("iframe"); iframe.className += "h-full w-full"; 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 = ` { owl.config.mode = 'dev'; let templates = \`${sanitizedXML}\`; const qweb = new owl.QWeb({ templates }); owl.Component.env = { qweb }; } ${js}`; script.innerHTML = content; 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 = ` /** * 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 //------------------------------------------------------------------------------ function loadSamples() { let result = samples.slice(); const localSample = localStorage.getItem("owl-playground-local-sample"); if (localSample) { const { js, css, xml } = JSON.parse(localSample); result.unshift({ description: "Local Storage Code", code: js, xml, css }); } return result; } function saveLocalSample(js, css, xml) { const str = JSON.stringify({ js, css, xml }); localStorage.setItem("owl-playground-local-sample", str); } function deleteLocalSample() { localStorage.removeItem("owl-playground-local-sample"); } function useSamples() { return loadSamples(); } //------------------------------------------------------------------------------ // MAIN APP //------------------------------------------------------------------------------ class Tab extends owl.Component { } class Editor extends owl.Component { static props = ['js', 'xml', 'css'] state = useState({ currentExtension: 'js', js: this.props.js, css: this.props.css, xml: this.props.xml, }) jsModel = null; cssModel = null; xmlModel = null; editor = null; willUpdateProps(nextProps) { console.log(nextProps); this.state.js = nextProps.js; this.state.css = nextProps.css; this.state.xml = nextProps.xml; this.jsModel = null; this.cssModel = null; this.xmlModel = null; this._openFile(this.state.currentExtension, false) } _run(ev) { this._saveCodeState(); this.trigger('run-project', { js: this.state.js, xml: this.state.xml, css: this.state.css }) } _openFile(extension, save = true) { if (save) this._saveCodeState(); this.state.currentExtension = extension; let language = null; let model = null; if (extension === 'js') { console.log("here") if (this.jsModel) { model = this.jsModel; } else { language = 'javascript' } } if (extension === 'xml') { if (this.xmlModel) { model = this.xmlModel; } else { language = 'xml' } } if (extension === 'css') { if (this.cssModel) { model = this.cssModel; } else { language = 'css' } } console.log(model) if (!model) { model = monaco.editor.createModel( this.state[this.state.currentExtension], language, null ) if (extension === 'js') { this.jsModel = model; } if (extension === 'xml') { this.xmlModel = model; } if (extension === 'css') { this.cssModel = model; } } this.editor.setModel(model) } mounted() { this.editor = monaco.editor.create(document.getElementById('container'), { value: this.state[this.state.currentExtension], language: 'javascript', automaticLayout: true, minimap: { enabled: false } }); } _saveCodeState() { this.state[this.state.currentExtension] = this.editor.getValue(); } _openCSS(ev) { this._openFile('css') } _openXML(ev) { this._openFile('xml') } _openJS(ev) { this._openFile('js') } } Editor.components = { Tab } class App extends owl.Component { samples = []; constructor(...args) { super(...args); this.version = owl.__info__.version; this.samples = useSamples(); this.state = useState({ js: this.samples[0].code, css: this.samples[0].css || "", xml: this.samples[0].xml || DEFAULT_XML, }); this.runCode = owl.utils.debounce(this.runCode, 250, true); this.downloadCode = owl.utils.debounce(this.downloadCode, 250, true); this.content = useRef("iframe-container"); } _run(ev) { this.content.el.innerHTML = ""; const { js, css, xml } = ev.detail; const subiframe = makeCodeIframe(js, css, xml); this.content.el.appendChild(subiframe); } _setSample(ev) { const sample = this.samples.find(s => s.description === ev.target.value); this.state.js = sample.code; this.state.css = sample.css || ""; this.state.xml = sample.xml || DEFAULT_XML; deleteLocalSample(); } } App.components = { Editor } //------------------------------------------------------------------------------ // Application initialization //------------------------------------------------------------------------------ async function start() { document.title = `${document.title} (v${owl.__info__.version})`; const commit = `https://github.com/odoo/owl/commit/${owl.__info__.hash}`; console.info(`This application is using Owl built with the following commit:`, commit); const [templates] = await Promise.all([ owl.utils.loadFile("templates.xml"), owl.utils.whenReady() ]); const qweb = new owl.QWeb({ templates }); owl.Component.env = { qweb }; const app = new App(); app.mount(document.body); } start();