mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
[IMP] owl: update to v0.19.0
This commit is contained in:
@@ -1109,18 +1109,6 @@
|
|||||||
function setTextContent(node, text) {
|
function setTextContent(node, text) {
|
||||||
node.textContent = text;
|
node.textContent = text;
|
||||||
}
|
}
|
||||||
function getTextContent(node) {
|
|
||||||
return node.textContent;
|
|
||||||
}
|
|
||||||
function isElement(node) {
|
|
||||||
return node.nodeType === 1;
|
|
||||||
}
|
|
||||||
function isText(node) {
|
|
||||||
return node.nodeType === 3;
|
|
||||||
}
|
|
||||||
function isComment(node) {
|
|
||||||
return node.nodeType === 8;
|
|
||||||
}
|
|
||||||
const htmlDomApi = {
|
const htmlDomApi = {
|
||||||
createElement,
|
createElement,
|
||||||
createElementNS,
|
createElementNS,
|
||||||
@@ -1133,10 +1121,6 @@
|
|||||||
nextSibling,
|
nextSibling,
|
||||||
tagName,
|
tagName,
|
||||||
setTextContent,
|
setTextContent,
|
||||||
getTextContent,
|
|
||||||
isElement,
|
|
||||||
isText,
|
|
||||||
isComment
|
|
||||||
};
|
};
|
||||||
function addNS(data, children, sel) {
|
function addNS(data, children, sel) {
|
||||||
data.ns = "http://www.w3.org/2000/svg";
|
data.ns = "http://www.w3.org/2000/svg";
|
||||||
@@ -1312,12 +1296,12 @@
|
|||||||
this._addTemplate(name, doc.firstChild);
|
this._addTemplate(name, doc.firstChild);
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Load templates from a xml (as a string). This will look up for the first
|
* Load templates from a xml (as a string or xml document). This will look up
|
||||||
* <templates> tag, and will consider each child of this as a template, with
|
* for the first <templates> tag, and will consider each child of this as a
|
||||||
* the name given by the t-name attribute.
|
* template, with the name given by the t-name attribute.
|
||||||
*/
|
*/
|
||||||
addTemplates(xmlstr) {
|
addTemplates(xmlstr) {
|
||||||
const doc = parseXML(xmlstr);
|
const doc = typeof xmlstr === 'string' ? parseXML(xmlstr) : xmlstr;
|
||||||
const templates = doc.getElementsByTagName("templates")[0];
|
const templates = doc.getElementsByTagName("templates")[0];
|
||||||
if (!templates) {
|
if (!templates) {
|
||||||
return;
|
return;
|
||||||
@@ -2752,7 +2736,14 @@
|
|||||||
ctx.addLine(`def${defID} = def${defID}.then(vnode=>{${createHook}let pvnode=h(vnode.sel, {key: ${templateID}, hook: {insert(vn) {let nvn=w${componentID}.__mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;${refExpr}${transitionsInsertCode}},remove() {},destroy(vn) {${finalizeComponentCode}}}});${registerCode}w${componentID}.__owl__.pvnode = pvnode;});`);
|
ctx.addLine(`def${defID} = def${defID}.then(vnode=>{${createHook}let pvnode=h(vnode.sel, {key: ${templateID}, hook: {insert(vn) {let nvn=w${componentID}.__mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;${refExpr}${transitionsInsertCode}},remove() {},destroy(vn) {${finalizeComponentCode}}}});${registerCode}w${componentID}.__owl__.pvnode = pvnode;});`);
|
||||||
ctx.addElse();
|
ctx.addElse();
|
||||||
// need to update component
|
// need to update component
|
||||||
const patchQueueCode = async ? `patchQueue${componentID}` : "extra.patchQueue";
|
let patchQueueCode = async ? `patchQueue${componentID}` : "extra.patchQueue";
|
||||||
|
if (keepAlive) {
|
||||||
|
// if we have t-keepalive="1", the component could be unmounted, but then
|
||||||
|
// we __updateProps is called. This is ok, but we do not want to call
|
||||||
|
// the willPatch/patched hooks of the component in this case, so we
|
||||||
|
// disable the patch queue
|
||||||
|
patchQueueCode = `w${componentID}.__owl__.isMounted ? ${patchQueueCode} : []`;
|
||||||
|
}
|
||||||
if (QWeb.dev) {
|
if (QWeb.dev) {
|
||||||
ctx.addLine(`utils.validateProps(w${componentID}.constructor, props${componentID})`);
|
ctx.addLine(`utils.validateProps(w${componentID}.constructor, props${componentID})`);
|
||||||
}
|
}
|
||||||
@@ -2958,7 +2949,10 @@
|
|||||||
renderPromise: null,
|
renderPromise: null,
|
||||||
renderProps: props || null,
|
renderProps: props || null,
|
||||||
boundHandlers: {},
|
boundHandlers: {},
|
||||||
mountedHandlers: {}
|
mountedHandlers: {},
|
||||||
|
observer: null,
|
||||||
|
render: null,
|
||||||
|
classObj: null
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
@@ -3054,44 +3048,57 @@
|
|||||||
*
|
*
|
||||||
* Note that a component can be mounted an unmounted several times
|
* Note that a component can be mounted an unmounted several times
|
||||||
*/
|
*/
|
||||||
async mount(target) {
|
async mount(target, renderBeforeRemount = false) {
|
||||||
if (this.__owl__.isMounted) {
|
const __owl__ = this.__owl__;
|
||||||
|
if (__owl__.isMounted) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!this.__owl__.vnode) {
|
if (!__owl__.vnode) {
|
||||||
// we use the fact that renderId === 1 as a way to determine that the
|
|
||||||
// component is mounted for the first time
|
|
||||||
const vnode = await this.__prepare();
|
const vnode = await this.__prepare();
|
||||||
if (this.__owl__.isDestroyed) {
|
if (__owl__.isDestroyed) {
|
||||||
// component was destroyed before we get here...
|
// component was destroyed before we get here...
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.__patch(vnode);
|
this.__patch(vnode);
|
||||||
}
|
}
|
||||||
|
else if (renderBeforeRemount) {
|
||||||
|
const patchQueue = [];
|
||||||
|
await this.__render(false, patchQueue, undefined, undefined);
|
||||||
|
this.__applyPatchQueue(patchQueue);
|
||||||
|
}
|
||||||
target.appendChild(this.el);
|
target.appendChild(this.el);
|
||||||
if (document.body.contains(target)) {
|
if (document.body.contains(target)) {
|
||||||
this.__callMounted();
|
this.__callMounted();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* The unmount method is the opposite of the mount method. It is useful
|
||||||
|
* to call willUnmount calls and remove the component from the DOM.
|
||||||
|
*/
|
||||||
unmount() {
|
unmount() {
|
||||||
if (this.__owl__.isMounted) {
|
if (this.__owl__.isMounted) {
|
||||||
this.__callWillUnmount();
|
this.__callWillUnmount();
|
||||||
this.el.remove();
|
this.el.remove();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async render(force = false, patchQueue, scope, vars) {
|
/**
|
||||||
|
* The render method is the main entry point to render a component (once it
|
||||||
|
* is ready. This method is not initially called when the component is
|
||||||
|
* rendered the first time).
|
||||||
|
*
|
||||||
|
* This method will cause all its sub components to potentially rerender
|
||||||
|
* themselves. Note that `render` is not called if a component is updated via
|
||||||
|
* its props.
|
||||||
|
*/
|
||||||
|
async render(force = false) {
|
||||||
const __owl__ = this.__owl__;
|
const __owl__ = this.__owl__;
|
||||||
if (!__owl__.isMounted) {
|
if (!__owl__.isMounted) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const shouldPatch = !patchQueue;
|
const patchQueue = [];
|
||||||
if (shouldPatch) {
|
const renderId = ++__owl__.renderId;
|
||||||
patchQueue = [];
|
await this.__render(force, patchQueue, undefined, undefined);
|
||||||
}
|
if (__owl__.isMounted && renderId === __owl__.renderId) {
|
||||||
__owl__.renderId++;
|
|
||||||
const renderId = __owl__.renderId;
|
|
||||||
await this.__render(force, patchQueue, scope, vars);
|
|
||||||
if (shouldPatch && __owl__.isMounted && renderId === __owl__.renderId) {
|
|
||||||
// we only update the vnode and the actual DOM if no other rendering
|
// we only update the vnode and the actual DOM if no other rendering
|
||||||
// occurred between now and when the render method was initially called.
|
// occurred between now and when the render method was initially called.
|
||||||
this.__applyPatchQueue(patchQueue);
|
this.__applyPatchQueue(patchQueue);
|
||||||
@@ -3162,6 +3169,17 @@
|
|||||||
//--------------------------------------------------------------------------
|
//--------------------------------------------------------------------------
|
||||||
// Private
|
// Private
|
||||||
//--------------------------------------------------------------------------
|
//--------------------------------------------------------------------------
|
||||||
|
/**
|
||||||
|
* Private helper to perform a full destroy, from the point of view of an Owl
|
||||||
|
* component. It does not remove the el (this is done only once on the top
|
||||||
|
* level destroyed component, for performance reasons).
|
||||||
|
*
|
||||||
|
* The job of this method is mostly to call willUnmount hooks, and to perform
|
||||||
|
* all necessary internal cleanup.
|
||||||
|
*
|
||||||
|
* Note that it does not call the __callWillUnmount method to avoid visiting
|
||||||
|
* all children many times.
|
||||||
|
*/
|
||||||
__destroy(parent) {
|
__destroy(parent) {
|
||||||
const __owl__ = this.__owl__;
|
const __owl__ = this.__owl__;
|
||||||
const isMounted = __owl__.isMounted;
|
const isMounted = __owl__.isMounted;
|
||||||
@@ -3214,6 +3232,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* The __updateProps method is called by the t-component directive whenever
|
||||||
|
* it updates a component (so, when the parent template is rerendered).
|
||||||
|
*/
|
||||||
async __updateProps(nextProps, forceUpdate = false, patchQueue, scope, vars) {
|
async __updateProps(nextProps, forceUpdate = false, patchQueue, scope, vars) {
|
||||||
const shouldUpdate = forceUpdate || this.shouldUpdate(nextProps);
|
const shouldUpdate = forceUpdate || this.shouldUpdate(nextProps);
|
||||||
if (shouldUpdate) {
|
if (shouldUpdate) {
|
||||||
@@ -3223,15 +3245,16 @@
|
|||||||
}
|
}
|
||||||
await this.willUpdateProps(nextProps);
|
await this.willUpdateProps(nextProps);
|
||||||
this.props = nextProps;
|
this.props = nextProps;
|
||||||
await this.render(forceUpdate, patchQueue, scope, vars);
|
await this.__render(forceUpdate, patchQueue, scope, vars);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Main patching method. We call the virtual dom patch method here to convert
|
||||||
|
* a virtual dom vnode into some actual dom.
|
||||||
|
*/
|
||||||
__patch(vnode) {
|
__patch(vnode) {
|
||||||
const __owl__ = this.__owl__;
|
const __owl__ = this.__owl__;
|
||||||
const target = __owl__.vnode || document.createElement(vnode.sel);
|
const target = __owl__.vnode || document.createElement(vnode.sel);
|
||||||
if (this.__owl__.classObj) {
|
|
||||||
vnode.data.class = Object.assign(vnode.data.class || {}, this.__owl__.classObj);
|
|
||||||
}
|
|
||||||
__owl__.vnode = patch(target, vnode);
|
__owl__.vnode = patch(target, vnode);
|
||||||
}
|
}
|
||||||
__prepare(scope, vars) {
|
__prepare(scope, vars) {
|
||||||
@@ -3286,9 +3309,7 @@
|
|||||||
const __owl__ = this.__owl__;
|
const __owl__ = this.__owl__;
|
||||||
const promises = [];
|
const promises = [];
|
||||||
const patch = [this];
|
const patch = [this];
|
||||||
if (__owl__.isMounted) {
|
patchQueue.push(patch);
|
||||||
patchQueue.push(patch);
|
|
||||||
}
|
|
||||||
if (__owl__.observer) {
|
if (__owl__.observer) {
|
||||||
__owl__.observer.allowMutations = false;
|
__owl__.observer.allowMutations = false;
|
||||||
}
|
}
|
||||||
@@ -3318,6 +3339,12 @@
|
|||||||
// parent component. With this, we make sure that the parent component will be
|
// parent component. With this, we make sure that the parent component will be
|
||||||
// able to patch itself properly after
|
// able to patch itself properly after
|
||||||
vnode.key = __owl__.id;
|
vnode.key = __owl__.id;
|
||||||
|
// we applly here the class information described on the component by the
|
||||||
|
// template (so, something like <MyComponent class="..."/>) to the actual
|
||||||
|
// root vnode
|
||||||
|
if (__owl__.classObj) {
|
||||||
|
vnode.data.class = Object.assign(vnode.data.class || {}, __owl__.classObj);
|
||||||
|
}
|
||||||
return Promise.all(promises).then(() => vnode);
|
return Promise.all(promises).then(() => vnode);
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
@@ -3344,6 +3371,10 @@
|
|||||||
this.mounted();
|
this.mounted();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Enable the observe feature on the state. We only create an observer if
|
||||||
|
* there is some state to be observed.
|
||||||
|
*/
|
||||||
__observeState() {
|
__observeState() {
|
||||||
if (this.state) {
|
if (this.state) {
|
||||||
const __owl__ = this.__owl__;
|
const __owl__ = this.__owl__;
|
||||||
@@ -3401,6 +3432,14 @@
|
|||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
// Error handling
|
// Error handling
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
/**
|
||||||
|
* This is the global error handler for errors occurring in Owl main lifecycle
|
||||||
|
* methods. Caught errors are triggered on the QWeb instance, and are
|
||||||
|
* potentially given to some parent component which implements `catchError`.
|
||||||
|
*
|
||||||
|
* If there are no such component, we destroy everything. This is better than
|
||||||
|
* being in a corrupted state.
|
||||||
|
*/
|
||||||
function errorHandler(error, component) {
|
function errorHandler(error, component) {
|
||||||
let canCatch = false;
|
let canCatch = false;
|
||||||
let qweb = component.env.qweb;
|
let qweb = component.env.qweb;
|
||||||
@@ -3423,22 +3462,21 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
class ConnectedComponent extends Component {
|
class ConnectedComponent extends Component {
|
||||||
constructor(parent, props) {
|
constructor() {
|
||||||
super(parent, props);
|
super(...arguments);
|
||||||
this.deep = true;
|
this.deep = true;
|
||||||
this.hashFunction = ({ storeProps }, options) => {
|
this.hashFunction = (storeProps, options) => {
|
||||||
const observer = this.__owl__.store.observer;
|
const revFn = this.__owl__.revFn;
|
||||||
let refFunction = this.deep ? observer.deepRevNumber : observer.revNumber;
|
const rev = revFn(storeProps);
|
||||||
if ("__owl__" in storeProps) {
|
if (rev > 0) {
|
||||||
return refFunction.call(observer, storeProps);
|
return rev;
|
||||||
}
|
}
|
||||||
const { currentStoreProps } = options;
|
|
||||||
let hash = 0;
|
let hash = 0;
|
||||||
for (let key in storeProps) {
|
for (let key in storeProps) {
|
||||||
const val = storeProps[key];
|
const val = storeProps[key];
|
||||||
const hashVal = refFunction.call(observer, val);
|
const hashVal = revFn(val);
|
||||||
if (hashVal === 0) {
|
if (hashVal === 0) {
|
||||||
if (val !== currentStoreProps[key]) {
|
if (val !== options.prevStoreProps[key]) {
|
||||||
options.didChange = true;
|
options.didChange = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3448,20 +3486,6 @@
|
|||||||
}
|
}
|
||||||
return hash;
|
return hash;
|
||||||
};
|
};
|
||||||
const store = this.getStore(this.env);
|
|
||||||
const ownProps = this.props || {};
|
|
||||||
const storeProps = this.constructor.mapStoreToProps(store.state, ownProps, store.getters);
|
|
||||||
const mergedProps = Object.assign({}, ownProps, storeProps);
|
|
||||||
this.props = mergedProps;
|
|
||||||
this.__owl__.ownProps = ownProps;
|
|
||||||
this.__owl__.currentStoreProps = storeProps;
|
|
||||||
this.__owl__.store = store;
|
|
||||||
this.__owl__.storeHash = this.hashFunction({
|
|
||||||
state: store.state,
|
|
||||||
storeProps: storeProps
|
|
||||||
}, {
|
|
||||||
currentStoreProps: storeProps
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
getStore(env) {
|
getStore(env) {
|
||||||
return env.store;
|
return env.store;
|
||||||
@@ -3469,6 +3493,26 @@
|
|||||||
static mapStoreToProps(storeState, ownProps, getters) {
|
static mapStoreToProps(storeState, ownProps, getters) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
dispatch(name, ...payload) {
|
||||||
|
return this.__owl__.store.dispatch(name, ...payload);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Need to do this here so 'deep' can be overrided by subcomponent easily
|
||||||
|
*/
|
||||||
|
async __prepareAndRender(scope, vars) {
|
||||||
|
const store = this.getStore(this.env);
|
||||||
|
const ownProps = this.props || {};
|
||||||
|
this.storeProps = this.constructor.mapStoreToProps(store.state, ownProps, store.getters);
|
||||||
|
const observer = store.observer;
|
||||||
|
const revFn = this.deep ? observer.deepRevNumber : observer.revNumber;
|
||||||
|
this.__owl__.store = store;
|
||||||
|
this.__owl__.revFn = revFn.bind(observer);
|
||||||
|
this.__owl__.storeHash = this.hashFunction(this.storeProps, {
|
||||||
|
prevStoreProps: this.storeProps
|
||||||
|
});
|
||||||
|
this.__owl__.rev = observer.rev;
|
||||||
|
return super.__prepareAndRender(scope, vars);
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* We do not use the mounted hook here for a subtle reason: we want the
|
* We do not use the mounted hook here for a subtle reason: we want the
|
||||||
* updates to be called for the parents before the children. However,
|
* updates to be called for the parents before the children. However,
|
||||||
@@ -3478,78 +3522,63 @@
|
|||||||
this.__owl__.store.on("update", this, this.__checkUpdate);
|
this.__owl__.store.on("update", this, this.__checkUpdate);
|
||||||
super.__callMounted();
|
super.__callMounted();
|
||||||
}
|
}
|
||||||
willUnmount() {
|
__callWillUnmount() {
|
||||||
this.__owl__.store.off("update", this);
|
this.__owl__.store.off("update", this);
|
||||||
super.willUnmount();
|
super.__callWillUnmount();
|
||||||
}
|
}
|
||||||
async __checkUpdate(updateId) {
|
__destroy(parent) {
|
||||||
if (updateId === this.__owl__.currentUpdateId) {
|
this.__owl__.store.off("update", this);
|
||||||
return;
|
super.__destroy(parent);
|
||||||
}
|
}
|
||||||
const ownProps = this.__owl__.ownProps;
|
async __updateProps(nextProps, f, p, s, v) {
|
||||||
const storeProps = this.constructor.mapStoreToProps(this.__owl__.store.state, ownProps, this.__owl__.store.getters);
|
this.__updateStoreProps(nextProps);
|
||||||
const options = {
|
return super.__updateProps(nextProps, f, p, s, v);
|
||||||
currentStoreProps: this.__owl__.currentStoreProps
|
}
|
||||||
};
|
__updateStoreProps(nextProps) {
|
||||||
const storeHash = this.hashFunction({
|
const store = this.__owl__.store;
|
||||||
state: this.__owl__.store.state,
|
const storeProps = this.constructor.mapStoreToProps(store.state, nextProps, store.getters);
|
||||||
storeProps: storeProps
|
const options = { prevStoreProps: this.storeProps, didChange: false };
|
||||||
}, options);
|
const storeHash = this.hashFunction(storeProps, options);
|
||||||
|
this.storeProps = storeProps;
|
||||||
let didChange = options.didChange;
|
let didChange = options.didChange;
|
||||||
if (storeHash !== this.__owl__.storeHash) {
|
if (storeHash !== this.__owl__.storeHash) {
|
||||||
didChange = true;
|
|
||||||
this.__owl__.storeHash = storeHash;
|
this.__owl__.storeHash = storeHash;
|
||||||
|
didChange = true;
|
||||||
}
|
}
|
||||||
if (didChange) {
|
this.__owl__.rev = store.observer.rev;
|
||||||
this.__owl__.currentStoreProps = storeProps;
|
return didChange;
|
||||||
await this.__updateProps(ownProps, false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
__updateProps(nextProps, forceUpdate, patchQueue) {
|
async __checkUpdate() {
|
||||||
const __owl__ = this.__owl__;
|
const observer = this.__owl__.store.observer;
|
||||||
__owl__.currentUpdateId = __owl__.store._updateId;
|
if (observer.rev === this.__owl__.rev) {
|
||||||
if (__owl__.ownProps !== nextProps) {
|
// update was already done by updateProps, from parent
|
||||||
__owl__.currentStoreProps = this.constructor.mapStoreToProps(__owl__.store.state, nextProps, __owl__.store.getters);
|
return;
|
||||||
|
}
|
||||||
|
const didChange = this.__updateStoreProps(this.props);
|
||||||
|
if (didChange) {
|
||||||
|
return this.render();
|
||||||
}
|
}
|
||||||
__owl__.ownProps = nextProps;
|
|
||||||
const mergedProps = Object.assign({}, nextProps, __owl__.currentStoreProps);
|
|
||||||
return super.__updateProps(mergedProps, forceUpdate, patchQueue);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Store extends EventBus {
|
class Store extends EventBus {
|
||||||
constructor(config, options = {}) {
|
constructor(config, options = {}) {
|
||||||
super();
|
super();
|
||||||
this._commitLevel = 0;
|
|
||||||
this.history = [];
|
|
||||||
this._updateId = 1;
|
|
||||||
this.debug = options.debug || false;
|
this.debug = options.debug || false;
|
||||||
this.actions = config.actions;
|
this.actions = config.actions;
|
||||||
this.mutations = config.mutations;
|
|
||||||
this.env = config.env;
|
this.env = config.env;
|
||||||
this.observer = new Observer();
|
this.observer = new Observer();
|
||||||
this.observer.notifyCB = this.__notifyComponents.bind(this);
|
this.observer.notifyCB = this.__notifyComponents.bind(this);
|
||||||
this.observer.allowMutations = false;
|
|
||||||
this.state = this.observer.observe(config.state || {});
|
this.state = this.observer.observe(config.state || {});
|
||||||
this.getters = {};
|
this.getters = {};
|
||||||
this._gettersCache = {};
|
if (config.getters) {
|
||||||
if (this.debug) {
|
const firstArg = {
|
||||||
this.history.push({ state: this.state });
|
state: this.state,
|
||||||
}
|
getters: this.getters,
|
||||||
const cTypes = ["undefined", "number", "string"];
|
|
||||||
for (let entry of Object.entries(config.getters || {})) {
|
|
||||||
const name = entry[0];
|
|
||||||
const func = entry[1];
|
|
||||||
this.getters[name] = payload => {
|
|
||||||
if (this._commitLevel === 0 && cTypes.indexOf(typeof payload) >= 0) {
|
|
||||||
this._gettersCache[name] = this._gettersCache[name] || {};
|
|
||||||
this._gettersCache[name][payload] =
|
|
||||||
this._gettersCache[name][payload] ||
|
|
||||||
func({ state: this.state, getters: this.getters }, payload);
|
|
||||||
return this._gettersCache[name][payload];
|
|
||||||
}
|
|
||||||
return func({ state: this.state, getters: this.getters }, payload);
|
|
||||||
};
|
};
|
||||||
|
for (let g in config.getters) {
|
||||||
|
this.getters[g] = config.getters[g].bind(this, firstArg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dispatch(action, ...payload) {
|
dispatch(action, ...payload) {
|
||||||
@@ -3557,7 +3586,6 @@
|
|||||||
throw new Error(`[Error] action ${action} is undefined`);
|
throw new Error(`[Error] action ${action} is undefined`);
|
||||||
}
|
}
|
||||||
const result = this.actions[action]({
|
const result = this.actions[action]({
|
||||||
commit: this.commit.bind(this),
|
|
||||||
dispatch: this.dispatch.bind(this),
|
dispatch: this.dispatch.bind(this),
|
||||||
env: this.env,
|
env: this.env,
|
||||||
state: this.state,
|
state: this.state,
|
||||||
@@ -3565,30 +3593,6 @@
|
|||||||
}, ...payload);
|
}, ...payload);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
commit(type, ...payload) {
|
|
||||||
if (!this.mutations[type]) {
|
|
||||||
throw new Error(`[Error] mutation ${type} is undefined`);
|
|
||||||
}
|
|
||||||
this._commitLevel++;
|
|
||||||
this.observer.allowMutations = true;
|
|
||||||
const res = this.mutations[type].call(null, {
|
|
||||||
commit: this.commit.bind(this),
|
|
||||||
state: this.state,
|
|
||||||
getters: this.getters
|
|
||||||
}, ...payload);
|
|
||||||
if (this._commitLevel === 1) {
|
|
||||||
this.observer.allowMutations = false;
|
|
||||||
if (this.debug) {
|
|
||||||
this.history.push({
|
|
||||||
state: this.state,
|
|
||||||
mutation: type,
|
|
||||||
payload: [...payload]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this._commitLevel--;
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
/**
|
/**
|
||||||
* Instead of using trigger to emit an update event, we actually implement
|
* Instead of using trigger to emit an update event, we actually implement
|
||||||
* our own function to do that. The reason is that we need to be smarter than
|
* our own function to do that. The reason is that we need to be smarter than
|
||||||
@@ -3604,15 +3608,12 @@
|
|||||||
* updating all widgets concurrently, except for parents/children.
|
* updating all widgets concurrently, except for parents/children.
|
||||||
*/
|
*/
|
||||||
async __notifyComponents() {
|
async __notifyComponents() {
|
||||||
this._updateId++;
|
|
||||||
const current = this._updateId;
|
|
||||||
this._gettersCache = {};
|
|
||||||
const subs = this.subscriptions.update || [];
|
const subs = this.subscriptions.update || [];
|
||||||
for (let i = 0, iLen = subs.length; i < iLen; i++) {
|
for (let i = 0, iLen = subs.length; i < iLen; i++) {
|
||||||
const sub = subs[i];
|
const sub = subs[i];
|
||||||
const shouldCallback = sub.owner ? sub.owner.__owl__.isMounted : true;
|
const shouldCallback = sub.owner ? sub.owner.__owl__.isMounted : true;
|
||||||
if (shouldCallback) {
|
if (shouldCallback) {
|
||||||
await sub.callback.call(sub.owner, current);
|
await sub.callback.call(sub.owner);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4012,9 +4013,9 @@
|
|||||||
exports.store = store;
|
exports.store = store;
|
||||||
exports.utils = utils;
|
exports.utils = utils;
|
||||||
|
|
||||||
exports.__info__.version = '0.18.0';
|
exports.__info__.version = '0.19.0';
|
||||||
exports.__info__.date = '2019-08-22T13:04:23.405Z';
|
exports.__info__.date = '2019-08-28T09:55:59.136Z';
|
||||||
exports.__info__.hash = '70a7016';
|
exports.__info__.hash = 'f289cb1';
|
||||||
exports.__info__.url = 'https://github.com/odoo/owl';
|
exports.__info__.url = 'https://github.com/odoo/owl';
|
||||||
|
|
||||||
}(this.owl = this.owl || {}));
|
}(this.owl = this.owl || {}));
|
||||||
|
|||||||
+293
-74
@@ -268,56 +268,42 @@ const TODO_APP_STORE = `// This example is an implementation of the TodoList app
|
|||||||
|
|
||||||
const ENTER_KEY = 13;
|
const ENTER_KEY = 13;
|
||||||
const ESC_KEY = 27;
|
const ESC_KEY = 27;
|
||||||
const LOCALSTORAGE_KEY = "todos-odoo";
|
const LOCALSTORAGE_KEY = "todomvc";
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
// Store Definition
|
// Store Definition
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
const actions = {
|
const actions = {
|
||||||
addTodo({ commit }, title) {
|
|
||||||
commit("addTodo", title);
|
|
||||||
},
|
|
||||||
removeTodo({ commit }, id) {
|
|
||||||
commit("removeTodo", id);
|
|
||||||
},
|
|
||||||
toggleTodo({ state, commit }, id) {
|
|
||||||
const todo = state.todos.find(t => t.id === id);
|
|
||||||
commit("editTodo", { id, completed: !todo.completed });
|
|
||||||
},
|
|
||||||
clearCompleted({ state, commit }) {
|
|
||||||
state.todos
|
|
||||||
.filter(todo => todo.completed)
|
|
||||||
.forEach(todo => {
|
|
||||||
commit("removeTodo", todo.id);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
toggleAll({ state, commit }, completed) {
|
|
||||||
state.todos.forEach(todo => {
|
|
||||||
commit("editTodo", {
|
|
||||||
id: todo.id,
|
|
||||||
completed
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
editTodo({ commit }, { id, title }) {
|
|
||||||
commit("editTodo", { id, title });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const mutations = {
|
|
||||||
addTodo({ state }, title) {
|
addTodo({ state }, title) {
|
||||||
const id = state.nextId++;
|
state.todos.push({
|
||||||
const todo = {
|
id: state.nextId++,
|
||||||
id,
|
|
||||||
title,
|
title,
|
||||||
completed: false
|
completed: false
|
||||||
};
|
});
|
||||||
state.todos.push(todo);
|
|
||||||
},
|
},
|
||||||
removeTodo({ state }, id) {
|
removeTodo({ state }, id) {
|
||||||
const index = state.todos.findIndex(t => t.id === id);
|
const index = state.todos.findIndex(t => t.id === id);
|
||||||
state.todos.splice(index, 1);
|
state.todos.splice(index, 1);
|
||||||
},
|
},
|
||||||
|
toggleTodo({ state, dispatch }, id) {
|
||||||
|
const todo = state.todos.find(t => t.id === id);
|
||||||
|
dispatch("editTodo", { id, completed: !todo.completed });
|
||||||
|
},
|
||||||
|
clearCompleted({ state, dispatch }) {
|
||||||
|
state.todos
|
||||||
|
.filter(todo => todo.completed)
|
||||||
|
.forEach(todo => {
|
||||||
|
dispatch("removeTodo", todo.id);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
toggleAll({ state, dispatch }, completed) {
|
||||||
|
state.todos.forEach(todo => {
|
||||||
|
dispatch("editTodo", {
|
||||||
|
id: todo.id,
|
||||||
|
completed
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
editTodo({ state }, { id, title, completed }) {
|
editTodo({ state }, { id, title, completed }) {
|
||||||
const todo = state.todos.find(t => t.id === id);
|
const todo = state.todos.find(t => t.id === id);
|
||||||
if (title !== undefined) {
|
if (title !== undefined) {
|
||||||
@@ -329,24 +315,20 @@ const mutations = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function saveState(state) {
|
||||||
|
const str = JSON.stringify(state);
|
||||||
|
window.localStorage.setItem(LOCALSTORAGE_KEY, str);
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadState() {
|
||||||
|
const localState = window.localStorage.getItem(LOCALSTORAGE_KEY);
|
||||||
|
return localState ? JSON.parse(localState) : { todos: [], nextId: 1};
|
||||||
|
}
|
||||||
|
|
||||||
function makeStore() {
|
function makeStore() {
|
||||||
const todos = JSON.parse(
|
const state = loadState();
|
||||||
window.localStorage.getItem(LOCALSTORAGE_KEY) || "[]"
|
const store = new owl.store.Store({ state, actions });
|
||||||
);
|
store.on("update", null, () => saveState(store.state));
|
||||||
const nextId = Math.max(0, ...todos.map(t => t.id || 0)) + 1;
|
|
||||||
const state = {
|
|
||||||
todos,
|
|
||||||
nextId
|
|
||||||
};
|
|
||||||
const store = new owl.store.Store({
|
|
||||||
state,
|
|
||||||
actions,
|
|
||||||
mutations
|
|
||||||
});
|
|
||||||
store.on("update", null, () => {
|
|
||||||
const state = JSON.stringify(store.state.todos);
|
|
||||||
window.localStorage.setItem(LOCALSTORAGE_KEY, state);
|
|
||||||
});
|
|
||||||
return store;
|
return store;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -357,11 +339,11 @@ class TodoItem extends owl.Component {
|
|||||||
state = { isEditing: false };
|
state = { isEditing: false };
|
||||||
|
|
||||||
removeTodo() {
|
removeTodo() {
|
||||||
this.env.dispatch("removeTodo", this.props.id);
|
this.env.store.dispatch("removeTodo", this.props.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleTodo() {
|
toggleTodo() {
|
||||||
this.env.dispatch("toggleTodo", this.props.id);
|
this.env.store.dispatch("toggleTodo", this.props.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
async editTodo() {
|
async editTodo() {
|
||||||
@@ -393,7 +375,7 @@ class TodoItem extends owl.Component {
|
|||||||
if (!value) {
|
if (!value) {
|
||||||
this.removeTodo(this.props.id);
|
this.removeTodo(this.props.id);
|
||||||
} else {
|
} else {
|
||||||
this.env.dispatch("editTodo", {
|
this.env.store.dispatch("editTodo", {
|
||||||
id: this.props.id,
|
id: this.props.id,
|
||||||
title: value
|
title: value
|
||||||
});
|
});
|
||||||
@@ -415,7 +397,7 @@ class TodoApp extends owl.store.ConnectedComponent {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
get visibleTodos() {
|
get visibleTodos() {
|
||||||
let todos = this.props.todos;
|
let todos = this.storeProps.todos;
|
||||||
if (this.state.filter === "active") {
|
if (this.state.filter === "active") {
|
||||||
todos = todos.filter(t => !t.completed);
|
todos = todos.filter(t => !t.completed);
|
||||||
}
|
}
|
||||||
@@ -426,11 +408,11 @@ class TodoApp extends owl.store.ConnectedComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get allChecked() {
|
get allChecked() {
|
||||||
return this.props.todos.every(todo => todo.completed);
|
return this.storeProps.todos.every(todo => todo.completed);
|
||||||
}
|
}
|
||||||
|
|
||||||
get remaining() {
|
get remaining() {
|
||||||
return this.props.todos.filter(todo => !todo.completed).length;
|
return this.storeProps.todos.filter(todo => !todo.completed).length;
|
||||||
}
|
}
|
||||||
|
|
||||||
get remainingText() {
|
get remainingText() {
|
||||||
@@ -442,20 +424,12 @@ class TodoApp extends owl.store.ConnectedComponent {
|
|||||||
if (ev.keyCode === ENTER_KEY) {
|
if (ev.keyCode === ENTER_KEY) {
|
||||||
const title = ev.target.value;
|
const title = ev.target.value;
|
||||||
if (title.trim()) {
|
if (title.trim()) {
|
||||||
this.env.dispatch("addTodo", title);
|
this.dispatch("addTodo", title);
|
||||||
}
|
}
|
||||||
ev.target.value = "";
|
ev.target.value = "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
clearCompleted() {
|
|
||||||
this.env.dispatch("clearCompleted");
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleAll() {
|
|
||||||
this.env.dispatch("toggleAll", !this.allChecked);
|
|
||||||
}
|
|
||||||
|
|
||||||
setFilter(filter) {
|
setFilter(filter) {
|
||||||
this.state.filter = filter;
|
this.state.filter = filter;
|
||||||
}
|
}
|
||||||
@@ -469,7 +443,6 @@ const qweb = new owl.QWeb(TEMPLATES);
|
|||||||
const env = {
|
const env = {
|
||||||
qweb,
|
qweb,
|
||||||
store,
|
store,
|
||||||
dispatch: store.dispatch.bind(store),
|
|
||||||
};
|
};
|
||||||
const app = new TodoApp(env);
|
const app = new TodoApp(env);
|
||||||
app.mount(document.body);
|
app.mount(document.body);
|
||||||
@@ -481,8 +454,8 @@ const TODO_APP_STORE_XML = `<templates>
|
|||||||
<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"/>
|
||||||
</header>
|
</header>
|
||||||
<section class="main" t-if="props.todos.length">
|
<section class="main" t-if="storeProps.todos.length">
|
||||||
<input class="toggle-all" id="toggle-all" type="checkbox" t-att-checked="allChecked" t-on-click="toggleAll"/>
|
<input class="toggle-all" id="toggle-all" type="checkbox" t-att-checked="allChecked" t-on-click="dispatch('toggleAll', !allChecked)"/>
|
||||||
<label for="toggle-all"></label>
|
<label for="toggle-all"></label>
|
||||||
<ul class="todo-list">
|
<ul class="todo-list">
|
||||||
<t t-foreach="visibleTodos" t-as="todo">
|
<t t-foreach="visibleTodos" t-as="todo">
|
||||||
@@ -490,7 +463,7 @@ const TODO_APP_STORE_XML = `<templates>
|
|||||||
</t>
|
</t>
|
||||||
</ul>
|
</ul>
|
||||||
</section>
|
</section>
|
||||||
<footer class="footer" t-if="props.todos.length">
|
<footer class="footer" t-if="storeProps.todos.length">
|
||||||
<span class="todo-count">
|
<span class="todo-count">
|
||||||
<strong>
|
<strong>
|
||||||
<t t-esc="remaining"/>
|
<t t-esc="remaining"/>
|
||||||
@@ -508,7 +481,7 @@ const TODO_APP_STORE_XML = `<templates>
|
|||||||
<a t-on-click="setFilter('completed')" t-att-class="{selected: state.filter === 'completed'}">Completed</a>
|
<a t-on-click="setFilter('completed')" t-att-class="{selected: state.filter === 'completed'}">Completed</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<button class="clear-completed" t-if="props.todos.length gt remaining" t-on-click="clearCompleted">
|
<button class="clear-completed" t-if="storeProps.todos.length gt remaining" t-on-click="dispatch('clearCompleted')">
|
||||||
Clear completed
|
Clear completed
|
||||||
</button>
|
</button>
|
||||||
</footer>
|
</footer>
|
||||||
@@ -1334,6 +1307,246 @@ const FORM_XML = `<templates>
|
|||||||
</templates>
|
</templates>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const WMS = `// This example is slightly more complex than usual. We demonstrate
|
||||||
|
// here a way to manage sub windows in Owl, declaratively. This is still just a
|
||||||
|
// demonstration. Managing windows can be as complex as we want. For example,
|
||||||
|
// we could implement the following features:
|
||||||
|
// - resizing windows
|
||||||
|
// - minimizing windows
|
||||||
|
// - configuration options for windows to make a window non resizeable
|
||||||
|
// - minimal width/height
|
||||||
|
// - better heuristic for initial window position
|
||||||
|
// - ...
|
||||||
|
|
||||||
|
class HelloWorld extends owl.Component {}
|
||||||
|
|
||||||
|
class Counter extends owl.Component {
|
||||||
|
state = { value: 0 };
|
||||||
|
|
||||||
|
inc() {
|
||||||
|
this.state.value++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Window extends owl.Component {
|
||||||
|
get style() {
|
||||||
|
let { width, height, top, left, zindex } = this.props.info;
|
||||||
|
|
||||||
|
return \`width: \${width}px;height: \${height}px;top:\${top}px;left:\${left}px;z-index:\${zindex}\`;
|
||||||
|
}
|
||||||
|
close() {
|
||||||
|
this.trigger("close-window", { id: this.props.info.id });
|
||||||
|
}
|
||||||
|
startDragAndDrop(ev) {
|
||||||
|
this.updateZIndex();
|
||||||
|
this.el.classList.add('dragging');
|
||||||
|
const offsetX = this.props.info.left - ev.pageX;
|
||||||
|
const offsetY = this.props.info.top - ev.pageY;
|
||||||
|
let left, top;
|
||||||
|
|
||||||
|
const el = this.el;
|
||||||
|
const self = this;
|
||||||
|
window.addEventListener("mousemove", moveWindow);
|
||||||
|
window.addEventListener("mouseup", stopDnD, { once: true });
|
||||||
|
|
||||||
|
function moveWindow(ev) {
|
||||||
|
left = Math.max(offsetX + ev.pageX, 0);
|
||||||
|
top = Math.max(offsetY + ev.pageY, 0);
|
||||||
|
el.style.left = \`\${left}px\`;
|
||||||
|
el.style.top = \`\${top}px\`;
|
||||||
|
}
|
||||||
|
function stopDnD() {
|
||||||
|
window.removeEventListener("mousemove", moveWindow);
|
||||||
|
const options = { id: self.props.info.id, left, top };
|
||||||
|
self.el.classList.remove('dragging');
|
||||||
|
self.trigger("set-window-position", options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updateZIndex() {
|
||||||
|
this.trigger("update-z-index", { id: this.props.info.id });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class WindowManager extends owl.Component {
|
||||||
|
components = { Window };
|
||||||
|
windows = [];
|
||||||
|
nextId = 1;
|
||||||
|
currentZindex = 1;
|
||||||
|
addWindow(name) {
|
||||||
|
const info = this.env.windows.find(w => w.name === name);
|
||||||
|
const id = \`w\${this.nextId++}\`;
|
||||||
|
this.windows.push({
|
||||||
|
id: id,
|
||||||
|
title: info.title,
|
||||||
|
width: info.defaultWidth,
|
||||||
|
height: info.defaultHeight,
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
zindex: this.currentZindex++
|
||||||
|
});
|
||||||
|
this.components[id] = info.component;
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
closeWindow(ev) {
|
||||||
|
const id = ev.detail.id;
|
||||||
|
delete this.components[id];
|
||||||
|
const index = this.windows.findIndex(w => w.id === id);
|
||||||
|
this.windows.splice(index, 1);
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
setWindowPosition(ev) {
|
||||||
|
const id = ev.detail.id;
|
||||||
|
const w = this.windows.find(w => w.id === id);
|
||||||
|
w.top = ev.detail.top;
|
||||||
|
w.left = ev.detail.left;
|
||||||
|
}
|
||||||
|
updateZIndex(ev) {
|
||||||
|
const id = ev.detail.id;
|
||||||
|
const w = this.windows.find(w => w.id === id);
|
||||||
|
w.zindex = this.currentZindex++;
|
||||||
|
ev.target.style["z-index"] = w.zindex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class App extends owl.Component {
|
||||||
|
components = { WindowManager };
|
||||||
|
|
||||||
|
addWindow(name) {
|
||||||
|
this.refs.wm.addWindow(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const qweb = new owl.QWeb(TEMPLATES);
|
||||||
|
const windows = [
|
||||||
|
{
|
||||||
|
name: "Hello",
|
||||||
|
title: "Hello",
|
||||||
|
component: HelloWorld,
|
||||||
|
defaultWidth: 200,
|
||||||
|
defaultHeight: 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Counter",
|
||||||
|
title: "Click Counter",
|
||||||
|
component: Counter,
|
||||||
|
defaultWidth: 300,
|
||||||
|
defaultHeight: 120
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const env = { qweb, windows };
|
||||||
|
const app = new App(env);
|
||||||
|
app.mount(document.body);
|
||||||
|
`;
|
||||||
|
|
||||||
|
const WMS_XML = `<templates>
|
||||||
|
<div t-name="Window" class="window" t-att-style="style" t-on-click="updateZIndex">
|
||||||
|
<div class="header">
|
||||||
|
<span t-on-mousedown="startDragAndDrop"><t t-esc="props.info.title"/></span>
|
||||||
|
<span class="close" t-on-click="close">×</span>
|
||||||
|
</div>
|
||||||
|
<t t-slot="default"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div t-name="WindowManager" class="window-manager"
|
||||||
|
t-on-close-window="closeWindow"
|
||||||
|
t-on-update-z-index="updateZIndex"
|
||||||
|
t-on-set-window-position="setWindowPosition">
|
||||||
|
<Window t-foreach="windows" t-as="w" t-key="w.id" info="w">
|
||||||
|
<t t-component="{{w.id}}"/>
|
||||||
|
</Window>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div t-name="App" class="app">
|
||||||
|
<WindowManager t-ref="wm"/>
|
||||||
|
<div class="menubar">
|
||||||
|
<button t-on-click="addWindow('Hello')">Say Hello</button>
|
||||||
|
<button t-on-click="addWindow('Counter')">Counter</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div t-name="HelloWorld">
|
||||||
|
World
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div t-name="Counter" class="counter">
|
||||||
|
<button t-on-click="inc">Inc</button>
|
||||||
|
<span><t t-esc="state.value"/></span>
|
||||||
|
</div>
|
||||||
|
</templates>
|
||||||
|
`;
|
||||||
|
|
||||||
|
const WMS_CSS = `body {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: auto 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.window-manager {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: #eeeeee;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menubar {
|
||||||
|
background-color: #875a7b;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menubar button {
|
||||||
|
height: 40px;
|
||||||
|
font-size: 18px;
|
||||||
|
margin: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.window {
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: 30px auto;
|
||||||
|
border: 1px solid gray;
|
||||||
|
background-color: white;
|
||||||
|
position: absolute;
|
||||||
|
box-shadow: 1px 1px 2px 1px grey;
|
||||||
|
}
|
||||||
|
|
||||||
|
.window.dragging {
|
||||||
|
opacity: 0.75;
|
||||||
|
}
|
||||||
|
|
||||||
|
.window .header {
|
||||||
|
background-color: #875a7b;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto 24px;
|
||||||
|
color: white;
|
||||||
|
line-height: 30px;
|
||||||
|
padding-left: 5px;
|
||||||
|
cursor: default;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.window .header .close {
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 22px;
|
||||||
|
padding-left: 4px;
|
||||||
|
padding-right: 4px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.counter {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
.counter button {
|
||||||
|
width: 80px;
|
||||||
|
height:40px;
|
||||||
|
font-size: 20px;
|
||||||
|
}`;
|
||||||
|
|
||||||
export const SAMPLES = [
|
export const SAMPLES = [
|
||||||
{
|
{
|
||||||
description: "Components",
|
description: "Components",
|
||||||
@@ -1376,6 +1589,12 @@ export const SAMPLES = [
|
|||||||
xml: SLOTS_XML,
|
xml: SLOTS_XML,
|
||||||
css: SLOTS_CSS
|
css: SLOTS_CSS
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
description: "Window Management System",
|
||||||
|
code: WMS,
|
||||||
|
xml: WMS_XML,
|
||||||
|
css: WMS_CSS,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
description: "Asynchronous components",
|
description: "Asynchronous components",
|
||||||
code: ASYNC_COMPONENTS,
|
code: ASYNC_COMPONENTS,
|
||||||
|
|||||||
Reference in New Issue
Block a user