mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
[IMP] update owl/playground (to v0.11.0)
This commit is contained in:
+170
-138
@@ -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,82 +61,90 @@ while True:
|
|||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
`;
|
`;
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
/**
|
||||||
// Tabbed editor
|
* Make an iframe, with all the js, css and xml properly injected.
|
||||||
//------------------------------------------------------------------------------
|
*/
|
||||||
class TabbedEditor extends owl.Component {
|
function makeCodeIframe(js, css, xml, errorHandler) {
|
||||||
constructor() {
|
// check templates
|
||||||
super(...arguments);
|
var qweb = new owl.QWeb();
|
||||||
this.template = "tabbed-editor";
|
const sanitizedXML = xml.replace(/<!--[\s\S]*?-->/g, "");
|
||||||
this.state = {
|
|
||||||
currentTab: this.props.display.split("|")[0]
|
|
||||||
};
|
|
||||||
this.tabs = {
|
|
||||||
js: this.props.display.includes("js"),
|
|
||||||
xml: this.props.display.includes("xml"),
|
|
||||||
css: this.props.display.includes("css")
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
mounted() {
|
// will throw error if there is something wrong with xml
|
||||||
this.editor = ace.edit(this.refs.editor);
|
qweb.addTemplates(sanitizedXML);
|
||||||
|
|
||||||
// remove this for xml/css (?)
|
// create iframe
|
||||||
this.editor.session.setOption("useWorker", false);
|
const iframe = document.createElement("iframe");
|
||||||
this.editor.setValue(this.props[this.state.currentTab], -1);
|
|
||||||
this.editor.setFontSize("12px");
|
iframe.onload = () => {
|
||||||
this.editor.setTheme("ace/theme/monokai");
|
const doc = iframe.contentDocument;
|
||||||
this.editor.session.setMode(MODES[this.state.currentTab]);
|
// inject js
|
||||||
const tabSize = this.state.currentTab === "xml" ? 2 : 4;
|
const owlScript = doc.createElement("script");
|
||||||
this.editor.session.setOption("tabSize", tabSize);
|
owlScript.type = "text/javascript";
|
||||||
this.editor.on("blur", () => {
|
owlScript.src = "../owl.js";
|
||||||
const editorValue = this.editor.getValue();
|
owlScript.addEventListener("load", () => {
|
||||||
const propsValue = this.props[this.state.currentTab];
|
const script = doc.createElement("script");
|
||||||
if (editorValue !== propsValue) {
|
script.type = "text/javascript";
|
||||||
this.trigger("updateCode", {
|
const content = `window.TEMPLATES = \`${sanitizedXML}\`\n${js}`;
|
||||||
type: this.state.currentTab,
|
script.innerHTML = content;
|
||||||
value: editorValue
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
patched() {
|
// Application code
|
||||||
if (this.editor) {
|
${processedJS}
|
||||||
window.dispatchEvent(new Event("resize"));
|
}
|
||||||
this.editor.setValue(this.props[this.state.currentTab], -1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
willUnmount() {
|
// wait for DOM ready before starting
|
||||||
this.editor.destroy();
|
owl.utils.whenReady(startApp);`;
|
||||||
delete this.editor;
|
|
||||||
}
|
|
||||||
|
|
||||||
setTab(tab) {
|
zip.file("app.js", JS);
|
||||||
this.editor.setValue(this.props[tab], -1);
|
zip.file("app.css", css);
|
||||||
|
zip.file("app.py", APP_PY);
|
||||||
const mode = MODES[tab];
|
zip.file("app.xml", xml);
|
||||||
this.editor.session.setMode(mode);
|
zip.file("index.html", DEFAULT_HTML);
|
||||||
const tabSize = tab === "xml" ? 2 : 4;
|
zip.file("owl.js", owlSourceCode());
|
||||||
this.editor.session.setOption("tabSize", tabSize);
|
return zip.generateAsync({ type: "blob" });
|
||||||
this.state.currentTab = tab;
|
|
||||||
}
|
|
||||||
|
|
||||||
onMouseDown(ev) {
|
|
||||||
if (ev.target.tagName === "DIV") {
|
|
||||||
let y = ev.clientY;
|
|
||||||
const resizer = ev => {
|
|
||||||
const delta = ev.clientY - y;
|
|
||||||
y = ev.clientY;
|
|
||||||
this.trigger("updatePanelHeight", { delta });
|
|
||||||
};
|
|
||||||
document.body.addEventListener("mousemove", resizer);
|
|
||||||
document.body.addEventListener("mouseup", () => {
|
|
||||||
document.body.removeEventListener("mousemove", resizer);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
@@ -142,7 +153,6 @@ class TabbedEditor extends owl.Component {
|
|||||||
class App extends owl.Component {
|
class App extends owl.Component {
|
||||||
constructor(...args) {
|
constructor(...args) {
|
||||||
super(...args);
|
super(...args);
|
||||||
this.template = "playground";
|
|
||||||
this.version = owl._version;
|
this.version = owl._version;
|
||||||
this.SAMPLES = SAMPLES;
|
this.SAMPLES = SAMPLES;
|
||||||
this.widgets = { TabbedEditor };
|
this.widgets = { TabbedEditor };
|
||||||
@@ -157,6 +167,10 @@ class App extends owl.Component {
|
|||||||
leftPaneWidth: Math.ceil(window.innerWidth / 2),
|
leftPaneWidth: Math.ceil(window.innerWidth / 2),
|
||||||
topPanelHeight: null
|
topPanelHeight: null
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.toggleLayout = owl.utils.debounce(this.toggleLayout, 250, true);
|
||||||
|
this.runCode = owl.utils.debounce(this.runCode, 250, true);
|
||||||
|
this.downloadCode = owl.utils.debounce(this.downloadCode, 250, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
displayError(error) {
|
displayError(error) {
|
||||||
@@ -169,61 +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.loadTemplates(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) {
|
||||||
@@ -280,38 +259,91 @@ 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);`;
|
// Tabbed editor
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
class TabbedEditor extends owl.Component {
|
||||||
|
constructor(parent, props) {
|
||||||
|
super(parent, props);
|
||||||
|
this.state = {
|
||||||
|
currentTab: props.js ? "js" : props.xml ? "xml" : "css"
|
||||||
|
};
|
||||||
|
this.setTab = owl.utils.debounce(this.setTab, 250, true);
|
||||||
|
|
||||||
zip.file("app.js", JS);
|
this.sessions = {};
|
||||||
zip.file("app.css", this.state.css);
|
for (let tab of ["js", "xml", "css"]) {
|
||||||
zip.file("app.py", APP_PY);
|
if (props[tab]) {
|
||||||
zip.file("app.xml", this.state.xml);
|
this.sessions[tab] = new ace.EditSession(props[tab], MODES[tab]);
|
||||||
zip.file("index.html", DEFAULT_HTML);
|
this.sessions[tab].setOption("useWorker", false);
|
||||||
zip.file("owl.js", owlSourceCode());
|
const tabSize = tab === "xml" ? 2 : 4;
|
||||||
zip.generateAsync({ type: "blob" }).then(function(content) {
|
this.sessions[tab].setOption("tabSize", tabSize);
|
||||||
saveAs(content, "app.zip");
|
this.sessions[tab].setUndoManager(new ace.UndoManager());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.editor = ace.edit(this.refs.editor);
|
||||||
|
|
||||||
|
this.editor.setValue(this.props[this.state.currentTab], -1);
|
||||||
|
this.editor.setFontSize("12px");
|
||||||
|
this.editor.setTheme("ace/theme/monokai");
|
||||||
|
this.editor.setSession(this.sessions[this.state.currentTab]);
|
||||||
|
const tabSize = this.state.currentTab === "xml" ? 2 : 4;
|
||||||
|
this.editor.session.setOption("tabSize", tabSize);
|
||||||
|
this.editor.on("blur", () => {
|
||||||
|
const editorValue = this.editor.getValue();
|
||||||
|
const propsValue = this.props[this.state.currentTab];
|
||||||
|
if (editorValue !== propsValue) {
|
||||||
|
this.trigger("updateCode", {
|
||||||
|
type: this.state.currentTab,
|
||||||
|
value: editorValue
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
patched() {
|
||||||
|
const session = this.sessions[this.state.currentTab];
|
||||||
|
session.setValue(this.props[this.state.currentTab], -1);
|
||||||
|
this.editor.setSession(session);
|
||||||
|
}
|
||||||
|
|
||||||
|
willUnmount() {
|
||||||
|
this.editor.destroy();
|
||||||
|
delete this.editor;
|
||||||
|
}
|
||||||
|
|
||||||
|
setTab(tab) {
|
||||||
|
if (this.state.currentTab !== tab) {
|
||||||
|
this.state.currentTab = tab;
|
||||||
|
const session = this.sessions[this.state.currentTab];
|
||||||
|
session.doc.setValue(this.props[tab], -1);
|
||||||
|
this.editor.setSession(session);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMouseDown(ev) {
|
||||||
|
if (ev.target.tagName === "DIV") {
|
||||||
|
let y = ev.clientY;
|
||||||
|
const resizer = ev => {
|
||||||
|
const delta = ev.clientY - y;
|
||||||
|
y = ev.clientY;
|
||||||
|
this.trigger("updatePanelHeight", { delta });
|
||||||
|
};
|
||||||
|
document.body.addEventListener("mousemove", resizer);
|
||||||
|
document.body.addEventListener("mouseup", () => {
|
||||||
|
document.body.removeEventListener("mousemove", resizer);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ body {
|
|||||||
display: grid;
|
display: grid;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
grid-template-columns: auto 6px 100%;
|
grid-template-columns: auto 8px 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* LEFT BAR ****************************************/
|
/* LEFT BAR ****************************************/
|
||||||
@@ -115,6 +115,7 @@ body {
|
|||||||
width: 34px;
|
width: 34px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
border-bottom: 2px solid transparent;
|
border-bottom: 2px solid transparent;
|
||||||
|
text-transform: uppercase;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab.active {
|
.tab.active {
|
||||||
|
|||||||
+42
-67
@@ -1,7 +1,6 @@
|
|||||||
const CLICK_COUNTER = `class ClickCounter extends owl.Component {
|
const CLICK_COUNTER = `class ClickCounter extends owl.Component {
|
||||||
constructor() {
|
constructor() {
|
||||||
super(...arguments);
|
super(...arguments);
|
||||||
this.template = "clickcounter";
|
|
||||||
this.state = { value: 0 };
|
this.state = { value: 0 };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -16,7 +15,7 @@ counter.mount(document.body);
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const CLICK_COUNTER_XML = `<templates>
|
const CLICK_COUNTER_XML = `<templates>
|
||||||
<button t-name="clickcounter" t-on-click="increment">
|
<button t-name="ClickCounter" t-on-click="increment">
|
||||||
Click Me! [<t t-esc="state.value"/>]
|
Click Me! [<t t-esc="state.value"/>]
|
||||||
</button>
|
</button>
|
||||||
</templates>`;
|
</templates>`;
|
||||||
@@ -29,7 +28,6 @@ const CLICK_COUNTER_CSS = `button {
|
|||||||
|
|
||||||
const CLICK_COUNTER_ESNEXT = `// This example will not work if your browser does not support ESNext class fields
|
const CLICK_COUNTER_ESNEXT = `// This example will not work if your browser does not support ESNext class fields
|
||||||
class ClickCounter extends owl.Component {
|
class ClickCounter extends owl.Component {
|
||||||
template = "clickcounter";
|
|
||||||
state = { value: 0 };
|
state = { value: 0 };
|
||||||
|
|
||||||
increment() {
|
increment() {
|
||||||
@@ -45,7 +43,6 @@ counter.mount(document.body);
|
|||||||
const WIDGET_COMPOSITION = `class ClickCounter extends owl.Component {
|
const WIDGET_COMPOSITION = `class ClickCounter extends owl.Component {
|
||||||
constructor(parent, props) {
|
constructor(parent, props) {
|
||||||
super(parent, props);
|
super(parent, props);
|
||||||
this.template = "clickcounter";
|
|
||||||
this.state = { value: props.initialState || 0 };
|
this.state = { value: props.initialState || 0 };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,7 +56,6 @@ let nextId = 1;
|
|||||||
class App extends owl.Component {
|
class App extends owl.Component {
|
||||||
constructor() {
|
constructor() {
|
||||||
super(...arguments);
|
super(...arguments);
|
||||||
this.template = "app";
|
|
||||||
this.state = { counters: [] }
|
this.state = { counters: [] }
|
||||||
this.widgets = { ClickCounter };
|
this.widgets = { ClickCounter };
|
||||||
}
|
}
|
||||||
@@ -75,11 +71,11 @@ app.mount(document.body);
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const WIDGET_COMPOSITION_XML = `<templates>
|
const WIDGET_COMPOSITION_XML = `<templates>
|
||||||
<button t-name="clickcounter" t-on-click="increment">
|
<button t-name="ClickCounter" t-on-click="increment">
|
||||||
Click Me! [<t t-esc="state.value"/>]
|
Click Me! [<t t-esc="state.value"/>]
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div t-name="app">
|
<div t-name="App">
|
||||||
<div><button t-on-click="addCounter">Add a counter</button></div>
|
<div><button t-on-click="addCounter">Add a counter</button></div>
|
||||||
<div>
|
<div>
|
||||||
<t t-foreach="state.counters" t-as="counter">
|
<t t-foreach="state.counters" t-as="counter">
|
||||||
@@ -97,7 +93,6 @@ const WIDGET_COMPOSITION_CSS = `button {
|
|||||||
|
|
||||||
const ANIMATION = `// This example will not work if your browser does not support ESNext class fields
|
const ANIMATION = `// This example will not work if your browser does not support ESNext class fields
|
||||||
class App extends owl.Component {
|
class App extends owl.Component {
|
||||||
template = "app";
|
|
||||||
state = {flag: 0};
|
state = {flag: 0};
|
||||||
|
|
||||||
toggle() {
|
toggle() {
|
||||||
@@ -111,7 +106,7 @@ app.mount(document.body);
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const ANIMATION_XML = `<templates>
|
const ANIMATION_XML = `<templates>
|
||||||
<div t-name="app">
|
<div t-name="App">
|
||||||
<button t-on-click="toggle">
|
<button t-on-click="toggle">
|
||||||
Click Me!
|
Click Me!
|
||||||
</button>
|
</button>
|
||||||
@@ -152,7 +147,6 @@ const ANIMATION_CSS = `button {
|
|||||||
const LIFECYCLE_DEMO = `class HookWidget extends owl.Component {
|
const LIFECYCLE_DEMO = `class HookWidget extends owl.Component {
|
||||||
constructor() {
|
constructor() {
|
||||||
super(...arguments);
|
super(...arguments);
|
||||||
this.template = "demo.hookwidget";
|
|
||||||
this.state = { n: 0 };
|
this.state = { n: 0 };
|
||||||
console.log("constructor");
|
console.log("constructor");
|
||||||
}
|
}
|
||||||
@@ -183,7 +177,6 @@ class ParentWidget extends owl.Component {
|
|||||||
constructor() {
|
constructor() {
|
||||||
super(...arguments);
|
super(...arguments);
|
||||||
this.widgets = { HookWidget };
|
this.widgets = { HookWidget };
|
||||||
this.template = "demo.parentwidget";
|
|
||||||
this.state = { n: 0, flag: true };
|
this.state = { n: 0, flag: true };
|
||||||
}
|
}
|
||||||
increment() {
|
increment() {
|
||||||
@@ -200,14 +193,14 @@ widget.mount(document.body);
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const LIFECYCLE_DEMO_XML = `<templates>
|
const LIFECYCLE_DEMO_XML = `<templates>
|
||||||
<div t-name="demo.parentwidget">
|
<div t-name="ParentWidget">
|
||||||
<button t-on-click="increment">Increment</button>
|
<button t-on-click="increment">Increment</button>
|
||||||
<button t-on-click="toggleSubWidget">ToggleSubWidget</button>
|
<button t-on-click="toggleSubWidget">ToggleSubWidget</button>
|
||||||
<div t-if="state.flag">
|
<div t-if="state.flag">
|
||||||
<t t-widget="HookWidget" t-props="{n:state.n}"/>
|
<t t-widget="HookWidget" t-props="{n:state.n}"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div t-name="demo.hookwidget" t-on-click="increment">Demo Sub Widget. Props: <t t-esc="props.n"/>. State: <t t-esc="state.n"/>. (click on me to update me)</div>
|
<div t-name="HookWidget" t-on-click="increment">Demo Sub Widget. Props: <t t-esc="props.n"/>. State: <t t-esc="state.n"/>. (click on me to update me)</div>
|
||||||
</templates>`;
|
</templates>`;
|
||||||
|
|
||||||
const BENCHMARK_APP = `//------------------------------------------------------------------------------
|
const BENCHMARK_APP = `//------------------------------------------------------------------------------
|
||||||
@@ -241,7 +234,6 @@ for (let i = 1; i < 16000; i++) {
|
|||||||
class Counter extends owl.Component {
|
class Counter extends owl.Component {
|
||||||
constructor(parent, props) {
|
constructor(parent, props) {
|
||||||
super(parent, props);
|
super(parent, props);
|
||||||
this.template = "counter";
|
|
||||||
this.state = { counter: props.initialState || 0 };
|
this.state = { counter: props.initialState || 0 };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -256,7 +248,6 @@ class Counter extends owl.Component {
|
|||||||
class Message extends owl.Component {
|
class Message extends owl.Component {
|
||||||
constructor() {
|
constructor() {
|
||||||
super(...arguments);
|
super(...arguments);
|
||||||
this.template = "message";
|
|
||||||
this.widgets = { Counter };
|
this.widgets = { Counter };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -273,7 +264,6 @@ class Message extends owl.Component {
|
|||||||
class App extends owl.Component {
|
class App extends owl.Component {
|
||||||
constructor() {
|
constructor() {
|
||||||
super(...arguments);
|
super(...arguments);
|
||||||
this.template = "root";
|
|
||||||
this.widgets = { Message };
|
this.widgets = { Message };
|
||||||
this.state = { messages: messages.slice(0, 10) };
|
this.state = { messages: messages.slice(0, 10) };
|
||||||
}
|
}
|
||||||
@@ -351,7 +341,7 @@ const BENCHMARK_APP_CSS = `.main {
|
|||||||
}`;
|
}`;
|
||||||
|
|
||||||
const BENCHMARK_APP_XML = `<templates>
|
const BENCHMARK_APP_XML = `<templates>
|
||||||
<div t-name="root" class="main">
|
<div t-name="App" class="main">
|
||||||
<div class="left-thing">
|
<div class="left-thing">
|
||||||
<div class="counter">
|
<div class="counter">
|
||||||
<button t-on-click="increment(-1)">-</button>
|
<button t-on-click="increment(-1)">-</button>
|
||||||
@@ -374,14 +364,14 @@ const BENCHMARK_APP_XML = `<templates>
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div t-name="message" class="message">
|
<div t-name="Message" class="message">
|
||||||
<span class="author"><t t-esc="props.author"/></span>
|
<span class="author"><t t-esc="props.author"/></span>
|
||||||
<span class="msg"><t t-esc="props.msg"/></span>
|
<span class="msg"><t t-esc="props.msg"/></span>
|
||||||
<button class="remove" t-on-click="removeMessage">Remove</button>
|
<button class="remove" t-on-click="removeMessage">Remove</button>
|
||||||
<t t-widget="Counter" t-props="{initialState: props.id}"/>
|
<t t-widget="Counter" t-props="{initialState: props.id}"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div t-name="counter">
|
<div t-name="Counter">
|
||||||
<button t-on-click="increment(-1)">-</button>
|
<button t-on-click="increment(-1)">-</button>
|
||||||
<span style="font-weight:bold">Value: <t t-esc="state.counter"/></span>
|
<span style="font-weight:bold">Value: <t t-esc="state.counter"/></span>
|
||||||
<button t-on-click="increment(1)">+</button>
|
<button t-on-click="increment(1)">+</button>
|
||||||
@@ -477,7 +467,6 @@ function makeStore() {
|
|||||||
// TodoItem
|
// TodoItem
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
class TodoItem extends owl.Component {
|
class TodoItem extends owl.Component {
|
||||||
template = "todoitem";
|
|
||||||
state = { isEditing: false };
|
state = { isEditing: false };
|
||||||
|
|
||||||
removeTodo() {
|
removeTodo() {
|
||||||
@@ -490,11 +479,12 @@ class TodoItem extends owl.Component {
|
|||||||
|
|
||||||
async editTodo() {
|
async editTodo() {
|
||||||
this.state.isEditing = true;
|
this.state.isEditing = true;
|
||||||
setTimeout(() => {
|
}
|
||||||
this.refs.input.value = "";
|
|
||||||
this.refs.input.focus();
|
focusInput() {
|
||||||
this.refs.input.value = this.props.title;
|
this.refs.input.value = "";
|
||||||
});
|
this.refs.input.focus();
|
||||||
|
this.refs.input.value = this.props.title;
|
||||||
}
|
}
|
||||||
|
|
||||||
handleKeyup(ev) {
|
handleKeyup(ev) {
|
||||||
@@ -535,7 +525,6 @@ function mapStateToProps(state) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class TodoApp extends owl.Component {
|
class TodoApp extends owl.Component {
|
||||||
template = "todoapp";
|
|
||||||
widgets = { TodoItem };
|
widgets = { TodoItem };
|
||||||
state = { filter: "all" };
|
state = { filter: "all" };
|
||||||
|
|
||||||
@@ -602,7 +591,7 @@ app.mount(document.body);
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const TODO_APP_STORE_XML = `<templates>
|
const TODO_APP_STORE_XML = `<templates>
|
||||||
<section t-name="todoapp" class="todoapp">
|
<section t-name="TodoApp" class="todoapp">
|
||||||
<header class="header">
|
<header class="header">
|
||||||
<h1>todos</h1>
|
<h1>todos</h1>
|
||||||
<input class="new-todo" autofocus="true" autocomplete="off" placeholder="What needs to be done?" t-on-keyup="addTodo"/>
|
<input class="new-todo" autofocus="true" autocomplete="off" placeholder="What needs to be done?" t-on-keyup="addTodo"/>
|
||||||
@@ -640,7 +629,7 @@ const TODO_APP_STORE_XML = `<templates>
|
|||||||
</footer>
|
</footer>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<li t-name="todoitem" class="todo" t-att-class="{completed: props.completed, editing: state.isEditing}">
|
<li t-name="TodoItem" class="todo" t-att-class="{completed: props.completed, editing: state.isEditing}">
|
||||||
<div class="view">
|
<div class="view">
|
||||||
<input class="toggle" type="checkbox" t-on-change="toggleTodo" t-att-checked="props.completed"/>
|
<input class="toggle" type="checkbox" t-on-change="toggleTodo" t-att-checked="props.completed"/>
|
||||||
<label t-on-dblclick="editTodo">
|
<label t-on-dblclick="editTodo">
|
||||||
@@ -648,7 +637,7 @@ const TODO_APP_STORE_XML = `<templates>
|
|||||||
</label>
|
</label>
|
||||||
<button class="destroy" t-on-click="removeTodo"></button>
|
<button class="destroy" t-on-click="removeTodo"></button>
|
||||||
</div>
|
</div>
|
||||||
<input class="edit" t-ref="'input'" t-if="state.isEditing" t-att-value="props.title" t-on-keyup="handleKeyup" t-on-blur="handleBlur"/>
|
<input class="edit" t-ref="'input'" t-if="state.isEditing" t-att-value="props.title" t-on-keyup="handleKeyup" t-mounted="focusInput" t-on-blur="handleBlur"/>
|
||||||
</li>
|
</li>
|
||||||
</templates>`;
|
</templates>`;
|
||||||
|
|
||||||
@@ -1034,39 +1023,25 @@ html .clear-completed:active {
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const RESPONSIVE = `class Navbar extends owl.Component {
|
const RESPONSIVE = `class Navbar extends owl.Component {}
|
||||||
template="navbar";
|
|
||||||
}
|
|
||||||
|
|
||||||
class ControlPanel extends owl.Component {
|
class ControlPanel extends owl.Component {
|
||||||
template="controlpanel";
|
|
||||||
widgets = { MobileSearchView };
|
widgets = { MobileSearchView };
|
||||||
}
|
}
|
||||||
|
|
||||||
class FormView extends owl.Component {
|
class FormView extends owl.Component {
|
||||||
template="formview";
|
|
||||||
widgets = { AdvancedWidget };
|
widgets = { AdvancedWidget };
|
||||||
}
|
}
|
||||||
|
|
||||||
class AdvancedWidget extends owl.Component {
|
class AdvancedWidget extends owl.Component {}
|
||||||
template="advancedwidget";
|
|
||||||
}
|
|
||||||
|
|
||||||
class Chatter extends owl.Component {
|
class Chatter extends owl.Component {}
|
||||||
template="chatter";
|
|
||||||
}
|
|
||||||
|
|
||||||
class MobileSearchView extends owl.Component {
|
class MobileSearchView extends owl.Component {}
|
||||||
template="mobilesearchview";
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class App extends owl.Component {
|
class App extends owl.Component {
|
||||||
constructor() {
|
widgets = { Navbar, ControlPanel, FormView, Chatter };
|
||||||
super(...arguments);
|
|
||||||
this.template = "app";
|
|
||||||
this.widgets = { Navbar, ControlPanel, FormView, Chatter };
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function isMobile() {
|
function isMobile() {
|
||||||
@@ -1081,39 +1056,39 @@ const env = {
|
|||||||
const app = new App(env);
|
const app = new App(env);
|
||||||
app.mount(document.body);
|
app.mount(document.body);
|
||||||
|
|
||||||
window.addEventListener(
|
function updateEnv() {
|
||||||
"resize",
|
const _isMobile = isMobile();
|
||||||
owl.utils.debounce(function() {
|
if (_isMobile !== env.isMobile) {
|
||||||
const _isMobile = isMobile();
|
app.updateEnv({
|
||||||
if (_isMobile !== env.isMobile) {
|
isMobile: _isMobile
|
||||||
app.updateEnv({
|
});
|
||||||
isMobile: _isMobile
|
}
|
||||||
});
|
}
|
||||||
}
|
|
||||||
}, 20)
|
window.addEventListener("resize", owl.utils.debounce(updateEnv, 20));
|
||||||
);`;
|
`;
|
||||||
|
|
||||||
const RESPONSIVE_XML = `<templates>
|
const RESPONSIVE_XML = `<templates>
|
||||||
<div t-name="navbar" class="navbar">Navbar</div>
|
<div t-name="Navbar" class="navbar">Navbar</div>
|
||||||
|
|
||||||
<div t-name="controlpanel" class="controlpanel">
|
<div t-name="ControlPanel" class="controlpanel">
|
||||||
<h2>controlpanel</h2>
|
<h2>controlpanel</h2>
|
||||||
<t t-if="env.isMobile" t-widget="MobileSearchView"/>
|
<t t-if="env.isMobile" t-widget="MobileSearchView"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div t-name="formview" class="formview">
|
<div t-name="FormView" class="formview">
|
||||||
<h2>formview</h2>
|
<h2>formview</h2>
|
||||||
<t t-if="!env.isMobile" t-widget="AdvancedWidget"/>
|
<t t-if="!env.isMobile" t-widget="AdvancedWidget"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div t-name="chatter" class="chatter">
|
<div t-name="Chatter" class="chatter">
|
||||||
<h2>Chatter</h2>
|
<h2>Chatter</h2>
|
||||||
<t t-foreach="100" t-as="item"><div>Message <t t-esc="item"/></div></t>
|
<t t-foreach="100" t-as="item"><div>Message <t t-esc="item"/></div></t>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div t-name="mobilesearchview">MOBILE searchview</div>
|
<div t-name="MobileSearchView">MOBILE searchview</div>
|
||||||
|
|
||||||
<div t-name="app" class="app" t-att-class="{mobile: env.isMobile, desktop: !env.isMobile}">
|
<div t-name="App" class="app" t-att-class="{mobile: env.isMobile, desktop: !env.isMobile}">
|
||||||
<t t-widget="Navbar"/>
|
<t t-widget="Navbar"/>
|
||||||
<t t-widget="ControlPanel"/>
|
<t t-widget="ControlPanel"/>
|
||||||
<div class="content-wrapper" t-if="!env.isMobile">
|
<div class="content-wrapper" t-if="!env.isMobile">
|
||||||
@@ -1128,7 +1103,7 @@ const RESPONSIVE_XML = `<templates>
|
|||||||
</t>
|
</t>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div t-name="advancedwidget">
|
<div t-name="AdvancedWidget">
|
||||||
This widget is only created in desktop mode.
|
This widget is only created in desktop mode.
|
||||||
<button>Button!</button>
|
<button>Button!</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+18
-10
@@ -1,14 +1,16 @@
|
|||||||
<templates>
|
<templates>
|
||||||
<div t-name="tabbed-editor" class="tabbed-editor" t-att-style="props.style">
|
<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">
|
||||||
<a t-if="tabs.js" class="tab flash" t-att-class="{active: state.currentTab==='js'}" t-on-click="setTab('js')">JS</a>
|
<t t-foreach="['js', 'xml', 'css']" t-as="tab">
|
||||||
<a t-if="tabs.xml" class="tab flash" t-att-class="{active: state.currentTab==='xml'}" t-on-click="setTab('xml')">XML</a>
|
<a t-ref="tab" t-if="props[tab]" class="tab flash" t-att-class="{active: state.currentTab===tab}" t-on-click="setTab(tab)">
|
||||||
<a t-if="tabs.css" class="tab flash" t-att-class="{active: state.currentTab==='css'}" t-on-click="setTab('css')">CSS</a>
|
<t t-esc="tab"/>
|
||||||
|
</a>
|
||||||
|
</t>
|
||||||
</div>
|
</div>
|
||||||
<div class="code-editor" t-ref="'editor'"></div>
|
<div class="code-editor" t-ref="'editor'"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div t-name="playground" class="playground">
|
<div t-name="App" class="playground">
|
||||||
<div class="left-bar" t-att-style="leftPaneStyle" t-att-class="{split: state.splitLayout}">
|
<div class="left-bar" t-att-style="leftPaneStyle" t-att-class="{split: state.splitLayout}">
|
||||||
<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>
|
||||||
@@ -20,13 +22,19 @@
|
|||||||
<a class="btn flash" t-on-click="downloadCode" title="Download a Zip with this Code"><i class="fas fa-download"></i></a>
|
<a class="btn flash" t-on-click="downloadCode" title="Download a Zip with this Code"><i class="fas fa-download"></i></a>
|
||||||
<a class="layout-selector flash" t-on-click="toggleLayout" title="Toggle Layout"><i class="fas" t-att-class="state.splitLayout ? 'fa-toggle-on' : 'fa-toggle-off'"></i></a>
|
<a class="layout-selector flash" t-on-click="toggleLayout" title="Toggle Layout"><i class="fas" t-att-class="state.splitLayout ? 'fa-toggle-on' : 'fa-toggle-off'"></i></a>
|
||||||
</div>
|
</div>
|
||||||
<t t-if="!state.splitLayout">
|
<t t-if="state.splitLayout">
|
||||||
<t t-widget="TabbedEditor" t-props="{js:state.js, css:state.css, xml: state.xml, display: 'js|xml|css'}" t-on-updateCode="updateCode"/>
|
<t t-widget="TabbedEditor"
|
||||||
|
t-props="{js:state.js, css:false, xml: false}"
|
||||||
|
t-on-updateCode="updateCode"
|
||||||
|
t-att-style="topEditorStyle"/>
|
||||||
|
<div class="separator horizontal"/>
|
||||||
|
<t t-widget="TabbedEditor" t-keepalive="1"
|
||||||
|
t-props="{js:false, css:state.css, xml: state.xml, resizeable: true}"
|
||||||
|
t-on-updateCode="updateCode"
|
||||||
|
t-on-updatePanelHeight="updatePanelHeight"/>
|
||||||
</t>
|
</t>
|
||||||
<t t-else="1">
|
<t t-else="1">
|
||||||
<t t-widget="TabbedEditor" t-props="{js:state.js, css:state.css, xml: state.xml, display: 'js', style:topEditorStyle}" t-on-updateCode="updateCode"/>
|
<t t-widget="TabbedEditor" t-props="{js:state.js, css:state.css, xml: state.xml, display: 'js|xml|css'}" t-on-updateCode="updateCode"/>
|
||||||
<div class="separator horizontal"/>
|
|
||||||
<t t-widget="TabbedEditor" t-keepalive="1" t-props="{js:state.js, css:state.css, xml: state.xml, display: 'xml|css', resizeable: true}" t-on-updateCode="updateCode" t-on-updatePanelHeight="updatePanelHeight"/>
|
|
||||||
</t>
|
</t>
|
||||||
</div>
|
</div>
|
||||||
<div class="separator vertical" t-on-mousedown="onMouseDown"/>
|
<div class="separator vertical" t-on-mousedown="onMouseDown"/>
|
||||||
|
|||||||
Reference in New Issue
Block a user