mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6aaa174a8a | |||
| 35121dff59 | |||
| 6a833c54ec |
@@ -0,0 +1,26 @@
|
||||
# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
|
||||
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
|
||||
|
||||
name: Node.js CI
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [10.x, 12.x, 14.x]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- run: npm install
|
||||
- run: npm test
|
||||
@@ -48,6 +48,7 @@
|
||||
"jest": "^23.6.0",
|
||||
"jest-environment-jsdom": "^24.7.1",
|
||||
"live-server": "^1.2.1",
|
||||
"monaco-editor": "^0.21.2",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"prettier": "^2.0.4",
|
||||
"rollup": "^1.6.0",
|
||||
|
||||
@@ -312,13 +312,9 @@ QWeb.addDirective({
|
||||
let valuesID = ctx.generateID();
|
||||
ctx.addLine(`let _${keysID} = _${valuesID} = _${arrayID};`);
|
||||
ctx.addIf(`!(_${arrayID} instanceof Array)`);
|
||||
ctx.addIf(`typeof _${arrayID}[Symbol.iterator] === 'function'`);
|
||||
ctx.addLine(`_${keysID} = [..._${arrayID}];`)
|
||||
ctx.addElse();
|
||||
ctx.addLine(`_${keysID} = Object.keys(_${arrayID});`);
|
||||
ctx.addLine(`_${valuesID} = Object.values(_${arrayID});`);
|
||||
ctx.closeIf();
|
||||
ctx.closeIf();
|
||||
ctx.addLine(`let _length${keysID} = _${keysID}.length;`);
|
||||
let varsID = ctx.startProtectScope(true);
|
||||
const loopVar = `i${ctx.loopNumber}`;
|
||||
|
||||
@@ -53,12 +53,8 @@ exports[`basic widget properties reconciliation alg works for t-foreach in t-for
|
||||
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||
let _3 = _4 = _2;
|
||||
if (!(_2 instanceof Array)) {
|
||||
if (typeof _2[Symbol.iterator] === 'function') {
|
||||
_3 = [..._2];
|
||||
} else {
|
||||
_3 = Object.keys(_2);
|
||||
_4 = Object.values(_2);
|
||||
}
|
||||
_3 = Object.keys(_2);
|
||||
_4 = Object.values(_2);
|
||||
}
|
||||
let _length3 = _3.length;
|
||||
let _origScope5 = scope;
|
||||
@@ -74,12 +70,8 @@ exports[`basic widget properties reconciliation alg works for t-foreach in t-for
|
||||
if (!_6) { throw new Error('QWeb error: Invalid loop expression')}
|
||||
let _7 = _8 = _6;
|
||||
if (!(_6 instanceof Array)) {
|
||||
if (typeof _6[Symbol.iterator] === 'function') {
|
||||
_7 = [..._6];
|
||||
} else {
|
||||
_7 = Object.keys(_6);
|
||||
_8 = Object.values(_6);
|
||||
}
|
||||
_7 = Object.keys(_6);
|
||||
_8 = Object.values(_6);
|
||||
}
|
||||
let _length7 = _7.length;
|
||||
let _origScope9 = scope;
|
||||
@@ -280,12 +272,8 @@ exports[`composition sub components with some state rendered in a loop 1`] = `
|
||||
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||
let _3 = _4 = _2;
|
||||
if (!(_2 instanceof Array)) {
|
||||
if (typeof _2[Symbol.iterator] === 'function') {
|
||||
_3 = [..._2];
|
||||
} else {
|
||||
_3 = Object.keys(_2);
|
||||
_4 = Object.values(_2);
|
||||
}
|
||||
_3 = Object.keys(_2);
|
||||
_4 = Object.values(_2);
|
||||
}
|
||||
let _length3 = _3.length;
|
||||
let _origScope5 = scope;
|
||||
@@ -570,12 +558,8 @@ exports[`other directives with t-component t-on expression captured in t-foreach
|
||||
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||
let _3 = _4 = _2;
|
||||
if (!(_2 instanceof Array)) {
|
||||
if (typeof _2[Symbol.iterator] === 'function') {
|
||||
_3 = [..._2];
|
||||
} else {
|
||||
_3 = Object.keys(_2);
|
||||
_4 = Object.values(_2);
|
||||
}
|
||||
_3 = Object.keys(_2);
|
||||
_4 = Object.values(_2);
|
||||
}
|
||||
let _length3 = _3.length;
|
||||
let _origScope5 = scope;
|
||||
@@ -616,12 +600,8 @@ exports[`other directives with t-component t-on expression in t-foreach 1`] = `
|
||||
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||
let _3 = _4 = _2;
|
||||
if (!(_2 instanceof Array)) {
|
||||
if (typeof _2[Symbol.iterator] === 'function') {
|
||||
_3 = [..._2];
|
||||
} else {
|
||||
_3 = Object.keys(_2);
|
||||
_4 = Object.values(_2);
|
||||
}
|
||||
_3 = Object.keys(_2);
|
||||
_4 = Object.values(_2);
|
||||
}
|
||||
let _length3 = _3.length;
|
||||
let _origScope5 = scope;
|
||||
@@ -672,12 +652,8 @@ exports[`other directives with t-component t-on expression in t-foreach with t-s
|
||||
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||
let _3 = _4 = _2;
|
||||
if (!(_2 instanceof Array)) {
|
||||
if (typeof _2[Symbol.iterator] === 'function') {
|
||||
_3 = [..._2];
|
||||
} else {
|
||||
_3 = Object.keys(_2);
|
||||
_4 = Object.values(_2);
|
||||
}
|
||||
_3 = Object.keys(_2);
|
||||
_4 = Object.values(_2);
|
||||
}
|
||||
let _length3 = _3.length;
|
||||
let _origScope5 = scope;
|
||||
@@ -729,12 +705,8 @@ exports[`other directives with t-component t-on method call in t-foreach 1`] = `
|
||||
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||
let _3 = _4 = _2;
|
||||
if (!(_2 instanceof Array)) {
|
||||
if (typeof _2[Symbol.iterator] === 'function') {
|
||||
_3 = [..._2];
|
||||
} else {
|
||||
_3 = Object.keys(_2);
|
||||
_4 = Object.values(_2);
|
||||
}
|
||||
_3 = Object.keys(_2);
|
||||
_4 = Object.values(_2);
|
||||
}
|
||||
let _length3 = _3.length;
|
||||
let _origScope5 = scope;
|
||||
@@ -1399,12 +1371,8 @@ exports[`other directives with t-component t-set outside modified in t-foreach 1
|
||||
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||
let _3 = _4 = _2;
|
||||
if (!(_2 instanceof Array)) {
|
||||
if (typeof _2[Symbol.iterator] === 'function') {
|
||||
_3 = [..._2];
|
||||
} else {
|
||||
_3 = Object.keys(_2);
|
||||
_4 = Object.values(_2);
|
||||
}
|
||||
_3 = Object.keys(_2);
|
||||
_4 = Object.values(_2);
|
||||
}
|
||||
let _length3 = _3.length;
|
||||
let _origScope5 = scope;
|
||||
@@ -1553,12 +1521,8 @@ exports[`random stuff/miscellaneous t-on with handler bound to dynamic argument
|
||||
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||
let _3 = _4 = _2;
|
||||
if (!(_2 instanceof Array)) {
|
||||
if (typeof _2[Symbol.iterator] === 'function') {
|
||||
_3 = [..._2];
|
||||
} else {
|
||||
_3 = Object.keys(_2);
|
||||
_4 = Object.values(_2);
|
||||
}
|
||||
_3 = Object.keys(_2);
|
||||
_4 = Object.values(_2);
|
||||
}
|
||||
let _length3 = _3.length;
|
||||
let _origScope5 = scope;
|
||||
@@ -1833,12 +1797,8 @@ exports[`t-model directive in a t-foreach 1`] = `
|
||||
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||
let _3 = _4 = _2;
|
||||
if (!(_2 instanceof Array)) {
|
||||
if (typeof _2[Symbol.iterator] === 'function') {
|
||||
_3 = [..._2];
|
||||
} else {
|
||||
_3 = Object.keys(_2);
|
||||
_4 = Object.values(_2);
|
||||
}
|
||||
_3 = Object.keys(_2);
|
||||
_4 = Object.values(_2);
|
||||
}
|
||||
let _length3 = _3.length;
|
||||
let _origScope5 = scope;
|
||||
@@ -1877,12 +1837,8 @@ exports[`t-model directive in a t-foreach, part 2 1`] = `
|
||||
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||
let _3 = _4 = _2;
|
||||
if (!(_2 instanceof Array)) {
|
||||
if (typeof _2[Symbol.iterator] === 'function') {
|
||||
_3 = [..._2];
|
||||
} else {
|
||||
_3 = Object.keys(_2);
|
||||
_4 = Object.values(_2);
|
||||
}
|
||||
_3 = Object.keys(_2);
|
||||
_4 = Object.values(_2);
|
||||
}
|
||||
let _length3 = _3.length;
|
||||
let _origScope5 = scope;
|
||||
|
||||
@@ -348,12 +348,8 @@ exports[`t-slot directive slots are rendered with proper context, part 2 2`] = `
|
||||
if (!_3) { throw new Error('QWeb error: Invalid loop expression')}
|
||||
let _4 = _5 = _3;
|
||||
if (!(_3 instanceof Array)) {
|
||||
if (typeof _3[Symbol.iterator] === 'function') {
|
||||
_4 = [..._3];
|
||||
} else {
|
||||
_4 = Object.keys(_3);
|
||||
_5 = Object.values(_3);
|
||||
}
|
||||
_4 = Object.keys(_3);
|
||||
_5 = Object.values(_3);
|
||||
}
|
||||
let _length4 = _4.length;
|
||||
let _origScope6 = scope;
|
||||
@@ -449,12 +445,8 @@ exports[`t-slot directive slots are rendered with proper context, part 3 2`] = `
|
||||
if (!_3) { throw new Error('QWeb error: Invalid loop expression')}
|
||||
let _4 = _5 = _3;
|
||||
if (!(_3 instanceof Array)) {
|
||||
if (typeof _3[Symbol.iterator] === 'function') {
|
||||
_4 = [..._3];
|
||||
} else {
|
||||
_4 = Object.keys(_3);
|
||||
_5 = Object.values(_3);
|
||||
}
|
||||
_4 = Object.keys(_3);
|
||||
_5 = Object.values(_3);
|
||||
}
|
||||
let _length4 = _4.length;
|
||||
let _origScope6 = scope;
|
||||
|
||||
@@ -431,12 +431,8 @@ exports[`foreach does not pollute the rendering context 1`] = `
|
||||
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||
let _3 = _4 = _2;
|
||||
if (!(_2 instanceof Array)) {
|
||||
if (typeof _2[Symbol.iterator] === 'function') {
|
||||
_3 = [..._2];
|
||||
} else {
|
||||
_3 = Object.keys(_2);
|
||||
_4 = Object.values(_2);
|
||||
}
|
||||
_3 = Object.keys(_2);
|
||||
_4 = Object.values(_2);
|
||||
}
|
||||
let _length3 = _3.length;
|
||||
let _origScope5 = scope;
|
||||
@@ -470,12 +466,8 @@ exports[`foreach iterate on items (on a element node) 1`] = `
|
||||
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||
let _3 = _4 = _2;
|
||||
if (!(_2 instanceof Array)) {
|
||||
if (typeof _2[Symbol.iterator] === 'function') {
|
||||
_3 = [..._2];
|
||||
} else {
|
||||
_3 = Object.keys(_2);
|
||||
_4 = Object.values(_2);
|
||||
}
|
||||
_3 = Object.keys(_2);
|
||||
_4 = Object.values(_2);
|
||||
}
|
||||
let _length3 = _3.length;
|
||||
let _origScope5 = scope;
|
||||
@@ -512,12 +504,8 @@ exports[`foreach iterate on items 1`] = `
|
||||
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||
let _3 = _4 = _2;
|
||||
if (!(_2 instanceof Array)) {
|
||||
if (typeof _2[Symbol.iterator] === 'function') {
|
||||
_3 = [..._2];
|
||||
} else {
|
||||
_3 = Object.keys(_2);
|
||||
_4 = Object.values(_2);
|
||||
}
|
||||
_3 = Object.keys(_2);
|
||||
_4 = Object.values(_2);
|
||||
}
|
||||
let _length3 = _3.length;
|
||||
let _origScope5 = scope;
|
||||
@@ -563,12 +551,8 @@ exports[`foreach iterate, dict param 1`] = `
|
||||
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||
let _3 = _4 = _2;
|
||||
if (!(_2 instanceof Array)) {
|
||||
if (typeof _2[Symbol.iterator] === 'function') {
|
||||
_3 = [..._2];
|
||||
} else {
|
||||
_3 = Object.keys(_2);
|
||||
_4 = Object.values(_2);
|
||||
}
|
||||
_3 = Object.keys(_2);
|
||||
_4 = Object.values(_2);
|
||||
}
|
||||
let _length3 = _3.length;
|
||||
let _origScope5 = scope;
|
||||
@@ -614,12 +598,8 @@ exports[`foreach iterate, position 1`] = `
|
||||
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||
let _3 = _4 = _2;
|
||||
if (!(_2 instanceof Array)) {
|
||||
if (typeof _2[Symbol.iterator] === 'function') {
|
||||
_3 = [..._2];
|
||||
} else {
|
||||
_3 = Object.keys(_2);
|
||||
_4 = Object.values(_2);
|
||||
}
|
||||
_3 = Object.keys(_2);
|
||||
_4 = Object.values(_2);
|
||||
}
|
||||
let _length3 = _3.length;
|
||||
let _origScope5 = scope;
|
||||
@@ -663,12 +643,8 @@ exports[`foreach t-call with body in t-foreach in t-foreach 1`] = `
|
||||
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||
let _3 = _4 = _2;
|
||||
if (!(_2 instanceof Array)) {
|
||||
if (typeof _2[Symbol.iterator] === 'function') {
|
||||
_3 = [..._2];
|
||||
} else {
|
||||
_3 = Object.keys(_2);
|
||||
_4 = Object.values(_2);
|
||||
}
|
||||
_3 = Object.keys(_2);
|
||||
_4 = Object.values(_2);
|
||||
}
|
||||
let _length3 = _3.length;
|
||||
let _origScope5 = scope;
|
||||
@@ -684,12 +660,8 @@ exports[`foreach t-call with body in t-foreach in t-foreach 1`] = `
|
||||
if (!_6) { throw new Error('QWeb error: Invalid loop expression')}
|
||||
let _7 = _8 = _6;
|
||||
if (!(_6 instanceof Array)) {
|
||||
if (typeof _6[Symbol.iterator] === 'function') {
|
||||
_7 = [..._6];
|
||||
} else {
|
||||
_7 = Object.keys(_6);
|
||||
_8 = Object.values(_6);
|
||||
}
|
||||
_7 = Object.keys(_6);
|
||||
_8 = Object.values(_6);
|
||||
}
|
||||
let _length7 = _7.length;
|
||||
let _origScope9 = scope;
|
||||
@@ -759,12 +731,8 @@ exports[`foreach t-call without body in t-foreach in t-foreach 1`] = `
|
||||
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||
let _3 = _4 = _2;
|
||||
if (!(_2 instanceof Array)) {
|
||||
if (typeof _2[Symbol.iterator] === 'function') {
|
||||
_3 = [..._2];
|
||||
} else {
|
||||
_3 = Object.keys(_2);
|
||||
_4 = Object.values(_2);
|
||||
}
|
||||
_3 = Object.keys(_2);
|
||||
_4 = Object.values(_2);
|
||||
}
|
||||
let _length3 = _3.length;
|
||||
let _origScope5 = scope;
|
||||
@@ -780,12 +748,8 @@ exports[`foreach t-call without body in t-foreach in t-foreach 1`] = `
|
||||
if (!_6) { throw new Error('QWeb error: Invalid loop expression')}
|
||||
let _7 = _8 = _6;
|
||||
if (!(_6 instanceof Array)) {
|
||||
if (typeof _6[Symbol.iterator] === 'function') {
|
||||
_7 = [..._6];
|
||||
} else {
|
||||
_7 = Object.keys(_6);
|
||||
_8 = Object.values(_6);
|
||||
}
|
||||
_7 = Object.keys(_6);
|
||||
_8 = Object.values(_6);
|
||||
}
|
||||
let _length7 = _7.length;
|
||||
let _origScope9 = scope;
|
||||
@@ -849,12 +813,8 @@ exports[`foreach t-foreach in t-forach 1`] = `
|
||||
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||
let _3 = _4 = _2;
|
||||
if (!(_2 instanceof Array)) {
|
||||
if (typeof _2[Symbol.iterator] === 'function') {
|
||||
_3 = [..._2];
|
||||
} else {
|
||||
_3 = Object.keys(_2);
|
||||
_4 = Object.values(_2);
|
||||
}
|
||||
_3 = Object.keys(_2);
|
||||
_4 = Object.values(_2);
|
||||
}
|
||||
let _length3 = _3.length;
|
||||
let _origScope5 = scope;
|
||||
@@ -870,12 +830,8 @@ exports[`foreach t-foreach in t-forach 1`] = `
|
||||
if (!_6) { throw new Error('QWeb error: Invalid loop expression')}
|
||||
let _7 = _8 = _6;
|
||||
if (!(_6 instanceof Array)) {
|
||||
if (typeof _6[Symbol.iterator] === 'function') {
|
||||
_7 = [..._6];
|
||||
} else {
|
||||
_7 = Object.keys(_6);
|
||||
_8 = Object.values(_6);
|
||||
}
|
||||
_7 = Object.keys(_6);
|
||||
_8 = Object.values(_6);
|
||||
}
|
||||
let _length7 = _7.length;
|
||||
let _origScope9 = scope;
|
||||
@@ -905,162 +861,6 @@ exports[`foreach t-foreach in t-forach 1`] = `
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`foreach t-foreach supports custom iterators 1`] = `
|
||||
"function anonymous(context, extra
|
||||
) {
|
||||
// Template name: \\"test\\"
|
||||
let scope = Object.create(context);
|
||||
let h = this.h;
|
||||
let c1 = [], p1 = {key:1};
|
||||
let vn1 = h('div', p1, c1);
|
||||
let _2 = scope['iterator'];
|
||||
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||
let _3 = _4 = _2;
|
||||
if (!(_2 instanceof Array)) {
|
||||
if (typeof _2[Symbol.iterator] === 'function') {
|
||||
_3 = [..._2];
|
||||
} else {
|
||||
_3 = Object.keys(_2);
|
||||
_4 = Object.values(_2);
|
||||
}
|
||||
}
|
||||
let _length3 = _3.length;
|
||||
let _origScope5 = scope;
|
||||
scope = Object.create(scope);
|
||||
for (let i1 = 0; i1 < _length3; i1++) {
|
||||
scope.item_first = i1 === 0
|
||||
scope.item_last = i1 === _length3 - 1
|
||||
scope.item_index = i1
|
||||
scope.item = _3[i1]
|
||||
scope.item_value = _4[i1]
|
||||
let key1 = i1;
|
||||
let _6 = scope['item'];
|
||||
if (_6 != null) {
|
||||
c1.push({text: _6});
|
||||
}
|
||||
}
|
||||
scope = _origScope5;
|
||||
return vn1;
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`foreach t-foreach supports custom iterators 2`] = `
|
||||
"function anonymous(context, extra
|
||||
) {
|
||||
// Template name: \\"test\\"
|
||||
let scope = Object.create(context);
|
||||
let h = this.h;
|
||||
let c1 = [], p1 = {key:1};
|
||||
let vn1 = h('div', p1, c1);
|
||||
let _2 = scope['iterator'];
|
||||
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||
let _3 = _4 = _2;
|
||||
if (!(_2 instanceof Array)) {
|
||||
if (typeof _2[Symbol.iterator] === 'function') {
|
||||
_3 = [..._2];
|
||||
} else {
|
||||
_3 = Object.keys(_2);
|
||||
_4 = Object.values(_2);
|
||||
}
|
||||
}
|
||||
let _length3 = _3.length;
|
||||
let _origScope5 = scope;
|
||||
scope = Object.create(scope);
|
||||
for (let i1 = 0; i1 < _length3; i1++) {
|
||||
scope.item_first = i1 === 0
|
||||
scope.item_last = i1 === _length3 - 1
|
||||
scope.item_index = i1
|
||||
scope.item = _3[i1]
|
||||
scope.item_value = _4[i1]
|
||||
let key1 = i1;
|
||||
let _6 = scope['item'];
|
||||
if (_6 != null) {
|
||||
c1.push({text: _6});
|
||||
}
|
||||
}
|
||||
scope = _origScope5;
|
||||
return vn1;
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`foreach t-foreach supports custom iterators 3`] = `
|
||||
"function anonymous(context, extra
|
||||
) {
|
||||
// Template name: \\"test\\"
|
||||
let scope = Object.create(context);
|
||||
let h = this.h;
|
||||
let c1 = [], p1 = {key:1};
|
||||
let vn1 = h('div', p1, c1);
|
||||
let _2 = scope['iterator'];
|
||||
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||
let _3 = _4 = _2;
|
||||
if (!(_2 instanceof Array)) {
|
||||
if (typeof _2[Symbol.iterator] === 'function') {
|
||||
_3 = [..._2];
|
||||
} else {
|
||||
_3 = Object.keys(_2);
|
||||
_4 = Object.values(_2);
|
||||
}
|
||||
}
|
||||
let _length3 = _3.length;
|
||||
let _origScope5 = scope;
|
||||
scope = Object.create(scope);
|
||||
for (let i1 = 0; i1 < _length3; i1++) {
|
||||
scope.item_first = i1 === 0
|
||||
scope.item_last = i1 === _length3 - 1
|
||||
scope.item_index = i1
|
||||
scope.item = _3[i1]
|
||||
scope.item_value = _4[i1]
|
||||
let key1 = i1;
|
||||
let _6 = scope['item'];
|
||||
if (_6 != null) {
|
||||
c1.push({text: _6});
|
||||
}
|
||||
}
|
||||
scope = _origScope5;
|
||||
return vn1;
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`foreach t-foreach supports custom iterators 4`] = `
|
||||
"function anonymous(context, extra
|
||||
) {
|
||||
// Template name: \\"test\\"
|
||||
let scope = Object.create(context);
|
||||
let h = this.h;
|
||||
let c1 = [], p1 = {key:1};
|
||||
let vn1 = h('div', p1, c1);
|
||||
let _2 = scope['iterator'];
|
||||
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||
let _3 = _4 = _2;
|
||||
if (!(_2 instanceof Array)) {
|
||||
if (typeof _2[Symbol.iterator] === 'function') {
|
||||
_3 = [..._2];
|
||||
} else {
|
||||
_3 = Object.keys(_2);
|
||||
_4 = Object.values(_2);
|
||||
}
|
||||
}
|
||||
let _length3 = _3.length;
|
||||
let _origScope5 = scope;
|
||||
scope = Object.create(scope);
|
||||
for (let i1 = 0; i1 < _length3; i1++) {
|
||||
scope.item_first = i1 === 0
|
||||
scope.item_last = i1 === _length3 - 1
|
||||
scope.item_index = i1
|
||||
scope.item = _3[i1]
|
||||
scope.item_value = _4[i1]
|
||||
let key1 = i1;
|
||||
let _6 = scope['item'];
|
||||
if (_6 != null) {
|
||||
c1.push({text: _6});
|
||||
}
|
||||
}
|
||||
scope = _origScope5;
|
||||
return vn1;
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`foreach warn if no key in some case 1`] = `
|
||||
"function anonymous(context, extra
|
||||
) {
|
||||
@@ -1073,12 +873,8 @@ exports[`foreach warn if no key in some case 1`] = `
|
||||
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||
let _3 = _4 = _2;
|
||||
if (!(_2 instanceof Array)) {
|
||||
if (typeof _2[Symbol.iterator] === 'function') {
|
||||
_3 = [..._2];
|
||||
} else {
|
||||
_3 = Object.keys(_2);
|
||||
_4 = Object.values(_2);
|
||||
}
|
||||
_3 = Object.keys(_2);
|
||||
_4 = Object.values(_2);
|
||||
}
|
||||
let _length3 = _3.length;
|
||||
let _origScope5 = scope;
|
||||
@@ -1146,12 +942,8 @@ exports[`misc global 1`] = `
|
||||
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||
let _3 = _4 = _2;
|
||||
if (!(_2 instanceof Array)) {
|
||||
if (typeof _2[Symbol.iterator] === 'function') {
|
||||
_3 = [..._2];
|
||||
} else {
|
||||
_3 = Object.keys(_2);
|
||||
_4 = Object.values(_2);
|
||||
}
|
||||
_3 = Object.keys(_2);
|
||||
_4 = Object.values(_2);
|
||||
}
|
||||
let _length3 = _3.length;
|
||||
let _origScope5 = scope;
|
||||
@@ -1717,12 +1509,8 @@ exports[`t-call (template calling recursive template, part 2 2`] = `
|
||||
if (!_5) { throw new Error('QWeb error: Invalid loop expression')}
|
||||
let _6 = _7 = _5;
|
||||
if (!(_5 instanceof Array)) {
|
||||
if (typeof _5[Symbol.iterator] === 'function') {
|
||||
_6 = [..._5];
|
||||
} else {
|
||||
_6 = Object.keys(_5);
|
||||
_7 = Object.values(_5);
|
||||
}
|
||||
_6 = Object.keys(_5);
|
||||
_7 = Object.values(_5);
|
||||
}
|
||||
let _length6 = _6.length;
|
||||
let _origScope8 = scope;
|
||||
@@ -1800,12 +1588,8 @@ exports[`t-call (template calling recursive template, part 3 2`] = `
|
||||
if (!_5) { throw new Error('QWeb error: Invalid loop expression')}
|
||||
let _6 = _7 = _5;
|
||||
if (!(_5 instanceof Array)) {
|
||||
if (typeof _5[Symbol.iterator] === 'function') {
|
||||
_6 = [..._5];
|
||||
} else {
|
||||
_6 = Object.keys(_5);
|
||||
_7 = Object.values(_5);
|
||||
}
|
||||
_6 = Object.keys(_5);
|
||||
_7 = Object.values(_5);
|
||||
}
|
||||
let _length6 = _6.length;
|
||||
let _origScope8 = scope;
|
||||
@@ -1889,12 +1673,8 @@ exports[`t-call (template calling recursive template, part 4: with t-set recursi
|
||||
if (!_5) { throw new Error('QWeb error: Invalid loop expression')}
|
||||
let _6 = _7 = _5;
|
||||
if (!(_5 instanceof Array)) {
|
||||
if (typeof _5[Symbol.iterator] === 'function') {
|
||||
_6 = [..._5];
|
||||
} else {
|
||||
_6 = Object.keys(_5);
|
||||
_7 = Object.values(_5);
|
||||
}
|
||||
_6 = Object.keys(_5);
|
||||
_7 = Object.values(_5);
|
||||
}
|
||||
let _length6 = _6.length;
|
||||
let _origScope8 = scope;
|
||||
@@ -2030,12 +1810,8 @@ exports[`t-call (template calling t-call with t-set inside and outside 1`] = `
|
||||
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||
let _3 = _4 = _2;
|
||||
if (!(_2 instanceof Array)) {
|
||||
if (typeof _2[Symbol.iterator] === 'function') {
|
||||
_3 = [..._2];
|
||||
} else {
|
||||
_3 = Object.keys(_2);
|
||||
_4 = Object.values(_2);
|
||||
}
|
||||
_3 = Object.keys(_2);
|
||||
_4 = Object.values(_2);
|
||||
}
|
||||
let _length3 = _3.length;
|
||||
let _origScope5 = scope;
|
||||
@@ -2811,12 +2587,8 @@ exports[`t-key t-key directive in a list 1`] = `
|
||||
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||
let _3 = _4 = _2;
|
||||
if (!(_2 instanceof Array)) {
|
||||
if (typeof _2[Symbol.iterator] === 'function') {
|
||||
_3 = [..._2];
|
||||
} else {
|
||||
_3 = Object.keys(_2);
|
||||
_4 = Object.values(_2);
|
||||
}
|
||||
_3 = Object.keys(_2);
|
||||
_4 = Object.values(_2);
|
||||
}
|
||||
let _length3 = _3.length;
|
||||
let _origScope5 = scope;
|
||||
@@ -2919,12 +2691,8 @@ exports[`t-on can bind handlers with loop variable as argument 1`] = `
|
||||
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||
let _3 = _4 = _2;
|
||||
if (!(_2 instanceof Array)) {
|
||||
if (typeof _2[Symbol.iterator] === 'function') {
|
||||
_3 = [..._2];
|
||||
} else {
|
||||
_3 = Object.keys(_2);
|
||||
_4 = Object.values(_2);
|
||||
}
|
||||
_3 = Object.keys(_2);
|
||||
_4 = Object.values(_2);
|
||||
}
|
||||
let _length3 = _3.length;
|
||||
let _origScope5 = scope;
|
||||
@@ -3205,12 +2973,8 @@ exports[`t-on t-on with prevent modifier in t-foreach 1`] = `
|
||||
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||
let _3 = _4 = _2;
|
||||
if (!(_2 instanceof Array)) {
|
||||
if (typeof _2[Symbol.iterator] === 'function') {
|
||||
_3 = [..._2];
|
||||
} else {
|
||||
_3 = Object.keys(_2);
|
||||
_4 = Object.values(_2);
|
||||
}
|
||||
_3 = Object.keys(_2);
|
||||
_4 = Object.values(_2);
|
||||
}
|
||||
let _length3 = _3.length;
|
||||
let _origScope5 = scope;
|
||||
@@ -3463,12 +3227,8 @@ exports[`t-ref refs in a loop 1`] = `
|
||||
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||
let _3 = _4 = _2;
|
||||
if (!(_2 instanceof Array)) {
|
||||
if (typeof _2[Symbol.iterator] === 'function') {
|
||||
_3 = [..._2];
|
||||
} else {
|
||||
_3 = Object.keys(_2);
|
||||
_4 = Object.values(_2);
|
||||
}
|
||||
_3 = Object.keys(_2);
|
||||
_4 = Object.values(_2);
|
||||
}
|
||||
let _length3 = _3.length;
|
||||
let _origScope5 = scope;
|
||||
@@ -3711,12 +3471,8 @@ exports[`t-set t-set should reuse variable if possible 1`] = `
|
||||
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||
let _3 = _4 = _2;
|
||||
if (!(_2 instanceof Array)) {
|
||||
if (typeof _2[Symbol.iterator] === 'function') {
|
||||
_3 = [..._2];
|
||||
} else {
|
||||
_3 = Object.keys(_2);
|
||||
_4 = Object.values(_2);
|
||||
}
|
||||
_3 = Object.keys(_2);
|
||||
_4 = Object.values(_2);
|
||||
}
|
||||
let _length3 = _3.length;
|
||||
let _origScope5 = scope;
|
||||
|
||||
@@ -1263,44 +1263,6 @@ describe("foreach", () => {
|
||||
);
|
||||
console.warn = consoleWarn;
|
||||
});
|
||||
|
||||
test('t-foreach supports custom iterators', () => {
|
||||
qweb.addTemplate(
|
||||
"test",
|
||||
`
|
||||
<div>
|
||||
<t t-foreach="iterator" t-as="item">
|
||||
<t t-esc="item"/>
|
||||
</t>
|
||||
</div>`
|
||||
);
|
||||
|
||||
let iterator: any = [1, 2, 3];
|
||||
let result = trim(renderToString(qweb, "test", { iterator } ));
|
||||
let expected = `<div>123</div>`;
|
||||
expect(result).toBe(expected);
|
||||
|
||||
iterator = { 1: 1, 2: 2, 3:3 };
|
||||
result = trim(renderToString(qweb, "test", { iterator } ));
|
||||
expected = `<div>123</div>`;
|
||||
expect(result).toBe(expected);
|
||||
|
||||
iterator = new Set([1, 2, 3]);
|
||||
result = trim(renderToString(qweb, "test", { iterator } ));
|
||||
expected = `<div>123</div>`;
|
||||
expect(result).toBe(expected);
|
||||
|
||||
iterator = {}
|
||||
iterator[Symbol.iterator] = function* () {
|
||||
yield 1;
|
||||
yield 2;
|
||||
yield 3;
|
||||
}
|
||||
result = trim(renderToString(qweb, "test", { iterator } ));
|
||||
expected = `<div>123</div>`;
|
||||
expect(result).toBe(expected);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe("misc", () => {
|
||||
|
||||
+255
-284
@@ -1,4 +1,4 @@
|
||||
import { SAMPLES } from "./samples.js";
|
||||
import { SAMPLES as samples } from "./samples.js" ;
|
||||
const { useState, useRef, onMounted, onWillUnmount } = owl.hooks;
|
||||
//------------------------------------------------------------------------------
|
||||
// Constants, helpers, utils
|
||||
@@ -6,18 +6,18 @@ const { useState, useRef, onMounted, onWillUnmount } = owl.hooks;
|
||||
let owlJS;
|
||||
|
||||
async function owlSourceCode() {
|
||||
if (owlJS) {
|
||||
if (owlJS) {
|
||||
return owlJS;
|
||||
}
|
||||
const result = await fetch("../owl.js");
|
||||
owlJS = await result.text();
|
||||
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"
|
||||
js: "ace/mode/javascript",
|
||||
css: "ace/mode/css",
|
||||
xml: "ace/mode/xml"
|
||||
};
|
||||
|
||||
const DEFAULT_XML = `<templates>
|
||||
@@ -71,22 +71,23 @@ if __name__ == "__main__":
|
||||
* Make an iframe, with all the js, css and xml properly injected.
|
||||
*/
|
||||
function makeCodeIframe(js, css, xml) {
|
||||
const sanitizedXML = xml.replace(/<!--[\s\S]*?-->/g, "");
|
||||
const sanitizedXML = xml.replace(/<!--[\s\S]*?-->/g, "");
|
||||
|
||||
|
||||
// create iframe
|
||||
const iframe = document.createElement("iframe");
|
||||
// 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 = `
|
||||
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}\`;
|
||||
@@ -94,32 +95,32 @@ function makeCodeIframe(js, css, xml) {
|
||||
owl.Component.env = { qweb };
|
||||
}
|
||||
${js}`;
|
||||
script.innerHTML = content;
|
||||
doc.body.appendChild(script);
|
||||
});
|
||||
doc.head.appendChild(owlScript);
|
||||
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;
|
||||
// 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");
|
||||
await owl.utils.loadJS("libs/jszip.min.js");
|
||||
|
||||
const zip = new JSZip();
|
||||
const processedJS = js
|
||||
.split("\n")
|
||||
.map(l => (l === "" ? "" : " " + l))
|
||||
.join("\n");
|
||||
const zip = new JSZip();
|
||||
const processedJS = js
|
||||
.split("\n")
|
||||
.map(l => (l === "" ? "" : " " + l))
|
||||
.join("\n");
|
||||
|
||||
const JS = `
|
||||
const JS = `
|
||||
/**
|
||||
* This is the javascript code defined in the playground.
|
||||
* In a larger application, this code should probably be moved in different
|
||||
@@ -150,278 +151,248 @@ async function start() {
|
||||
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" });
|
||||
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;
|
||||
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);
|
||||
const str = JSON.stringify({ js, css, xml });
|
||||
localStorage.setItem("owl-playground-local-sample", str);
|
||||
}
|
||||
|
||||
function deleteLocalSample() {
|
||||
localStorage.removeItem("owl-playground-local-sample");
|
||||
localStorage.removeItem("owl-playground-local-sample");
|
||||
}
|
||||
|
||||
function useSamples() {
|
||||
const samples = loadSamples();
|
||||
const component = owl.Component.current;
|
||||
let interval;
|
||||
|
||||
onMounted(() => {
|
||||
const state = component.state;
|
||||
interval = setInterval(() => {
|
||||
if (component.isDirty) {
|
||||
saveLocalSample(state.js, state.css, state.xml);
|
||||
}
|
||||
}, 1000);
|
||||
});
|
||||
onWillUnmount(() => {
|
||||
clearInterval(interval);
|
||||
});
|
||||
return samples;
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
// Tabbed editor
|
||||
//------------------------------------------------------------------------------
|
||||
class TabbedEditor extends owl.Component {
|
||||
constructor(parent, props) {
|
||||
super(parent, props);
|
||||
this.state = useState({
|
||||
currentTab: props.js !== false ? "js" : props.xml ? "xml" : "css"
|
||||
});
|
||||
this.setTab = owl.utils.debounce(this.setTab, 250, true);
|
||||
|
||||
this.sessions = {};
|
||||
this._setupSessions(props);
|
||||
this.editorNode = useRef("editor");
|
||||
this._updateCode = this._updateCode.bind(this);
|
||||
}
|
||||
|
||||
mounted() {
|
||||
this.editor = this.editor || ace.edit(this.editorNode.el);
|
||||
|
||||
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", this._updateCode);
|
||||
this.interval = setInterval(this._updateCode, 3000);
|
||||
}
|
||||
|
||||
willUnmount() {
|
||||
clearInterval(this.interval);
|
||||
this.editor.off("blur", this._updateCode);
|
||||
}
|
||||
willUpdateProps(nextProps) {
|
||||
this._setupSessions(nextProps);
|
||||
}
|
||||
|
||||
patched() {
|
||||
const session = this.sessions[this.state.currentTab];
|
||||
let content = this.props[this.state.currentTab];
|
||||
if (content === false) {
|
||||
const tab = this.props.js !== false ? "js" : this.props.xml ? "xml" : "css";
|
||||
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) {
|
||||
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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_setupSessions(props) {
|
||||
for (let tab of ["js", "xml", "css"]) {
|
||||
if (props[tab] !== false && !this.sessions[tab]) {
|
||||
this.sessions[tab] = new ace.EditSession(props[tab], MODES[tab]);
|
||||
this.sessions[tab].setOption("useWorker", false);
|
||||
const tabSize = tab === "xml" ? 2 : 4;
|
||||
this.sessions[tab].setOption("tabSize", tabSize);
|
||||
this.sessions[tab].setUndoManager(new ace.UndoManager());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_updateCode() {
|
||||
const editorValue = this.editor.getValue();
|
||||
const propsValue = this.props[this.state.currentTab];
|
||||
if (editorValue !== propsValue) {
|
||||
this.trigger("updateCode", {
|
||||
type: this.state.currentTab,
|
||||
value: editorValue
|
||||
});
|
||||
}
|
||||
}
|
||||
return loadSamples();
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// MAIN APP
|
||||
//------------------------------------------------------------------------------
|
||||
class App extends owl.Component {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
this.version = owl.__info__.version;
|
||||
this.SAMPLES = useSamples();
|
||||
this.isDirty = false;
|
||||
|
||||
this.state = useState({
|
||||
js: this.SAMPLES[0].code,
|
||||
css: this.SAMPLES[0].css || "",
|
||||
xml: this.SAMPLES[0].xml || DEFAULT_XML,
|
||||
displayWelcome: true,
|
||||
splitLayout: true,
|
||||
leftPaneWidth: Math.ceil(window.innerWidth / 2),
|
||||
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);
|
||||
this.content = useRef("content");
|
||||
}
|
||||
|
||||
runCode() {
|
||||
this.content.el.innerHTML = "";
|
||||
this.state.displayWelcome = false;
|
||||
|
||||
const { js, css, xml } = this.state;
|
||||
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();
|
||||
this.isDirty = false;
|
||||
}
|
||||
|
||||
get leftPaneStyle() {
|
||||
return `width:${this.state.leftPaneWidth}px`;
|
||||
}
|
||||
|
||||
get rightPaneStyle() {
|
||||
return `width:${window.innerWidth - 6 - this.state.leftPaneWidth}px`;
|
||||
}
|
||||
|
||||
get topEditorStyle() {
|
||||
return `flex: 0 0 ${this.state.topPanelHeight}px`;
|
||||
}
|
||||
|
||||
onMouseDown() {
|
||||
const resizer = ev => {
|
||||
this.state.leftPaneWidth = ev.clientX;
|
||||
};
|
||||
|
||||
document.body.addEventListener("mousemove", resizer);
|
||||
for (let iframe of document.getElementsByTagName("iframe")) {
|
||||
iframe.classList.add("disabled");
|
||||
}
|
||||
|
||||
document.body.addEventListener("mouseup", () => {
|
||||
document.body.removeEventListener("mousemove", resizer);
|
||||
for (let iframe of document.getElementsByTagName("iframe")) {
|
||||
iframe.classList.remove("disabled");
|
||||
}
|
||||
});
|
||||
}
|
||||
updateCode(ev) {
|
||||
if (this.state[ev.detail.type] !== ev.detail.value) {
|
||||
this.state[ev.detail.type] = ev.detail.value;
|
||||
this.isDirty = true;
|
||||
}
|
||||
}
|
||||
toggleLayout() {
|
||||
this.state.splitLayout = !this.state.splitLayout;
|
||||
}
|
||||
updatePanelHeight(ev) {
|
||||
if (!ev.detail.delta) {
|
||||
return;
|
||||
}
|
||||
let height = this.state.topPanelHeight;
|
||||
if (!height) {
|
||||
height = document.getElementsByClassName("tabbed-editor")[0].clientHeight;
|
||||
}
|
||||
this.state.topPanelHeight = height + ev.detail.delta;
|
||||
}
|
||||
|
||||
async downloadCode() {
|
||||
const { js, css, xml } = this.state;
|
||||
const content = await makeApp(js, css, xml);
|
||||
await owl.utils.loadJS("libs/FileSaver.min.js");
|
||||
saveAs(content, "app.zip");
|
||||
}
|
||||
class Tab extends owl.Component {
|
||||
}
|
||||
App.components = { TabbedEditor };
|
||||
|
||||
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);
|
||||
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();
|
||||
|
||||
+21
-15
@@ -1,20 +1,26 @@
|
||||
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>OWL Playground</title>
|
||||
<link rel="icon" href="data:,">
|
||||
|
||||
<script src="libs/ace.js" type="text/javascript" charset="utf-8"></script>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
|
||||
<link
|
||||
rel="stylesheet"
|
||||
data-name="vs/editor/editor.main"
|
||||
href="../../node_modules/monaco-editor/min/vs/editor/editor.main.css"
|
||||
/>
|
||||
<script src="../owl.js"></script>
|
||||
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.1/css/solid.css" integrity="sha384-QokYePQSOwpBDuhlHOsX0ymF6R/vLk/UQVz3WHa6wygxI5oGTmDTv8wahFOSspdm" crossorigin="anonymous">
|
||||
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.1/css/fontawesome.css" integrity="sha384-vd1e11sR28tEK9YANUtpIOdjGW14pS87bUBuOIoBILVWLFnS+MCX9T6MMf0VdPGq" crossorigin="anonymous">
|
||||
|
||||
<!-- Application JS/CSS -->
|
||||
<link rel="stylesheet" href="playground.css">
|
||||
<script type="module" src="app.js"></script>
|
||||
<link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet">
|
||||
</head>
|
||||
<body class="h-screen bg-gray-200">
|
||||
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
<script>
|
||||
var require = { paths: { vs: '../../node_modules/monaco-editor/min/vs' } };
|
||||
</script>
|
||||
<script src="../../node_modules/monaco-editor/min/vs/loader.js"></script>
|
||||
<script src="../../node_modules/monaco-editor/min/vs/editor/editor.main.nls.js"></script>
|
||||
<script src="../../node_modules/monaco-editor/min/vs/editor/editor.main.js"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,427 @@
|
||||
import { 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 = `<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.
|
||||
*/
|
||||
function makeCodeIframe(js, css, xml) {
|
||||
const sanitizedXML = xml.replace(/<!--[\s\S]*?-->/g, "");
|
||||
|
||||
|
||||
// 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 = `
|
||||
{
|
||||
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() {
|
||||
const samples = loadSamples();
|
||||
const component = owl.Component.current;
|
||||
let interval;
|
||||
|
||||
onMounted(() => {
|
||||
const state = component.state;
|
||||
interval = setInterval(() => {
|
||||
if (component.isDirty) {
|
||||
saveLocalSample(state.js, state.css, state.xml);
|
||||
}
|
||||
}, 1000);
|
||||
});
|
||||
onWillUnmount(() => {
|
||||
clearInterval(interval);
|
||||
});
|
||||
return samples;
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
// Tabbed editor
|
||||
//------------------------------------------------------------------------------
|
||||
class TabbedEditor extends owl.Component {
|
||||
constructor(parent, props) {
|
||||
super(parent, props);
|
||||
this.state = useState({
|
||||
currentTab: props.js !== false ? "js" : props.xml ? "xml" : "css"
|
||||
});
|
||||
this.setTab = owl.utils.debounce(this.setTab, 250, true);
|
||||
|
||||
this.sessions = {};
|
||||
this._setupSessions(props);
|
||||
this.editorNode = useRef("editor");
|
||||
this._updateCode = this._updateCode.bind(this);
|
||||
}
|
||||
|
||||
mounted() {
|
||||
this.editor = this.editor || ace.edit(this.editorNode.el);
|
||||
|
||||
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", this._updateCode);
|
||||
this.interval = setInterval(this._updateCode, 3000);
|
||||
}
|
||||
|
||||
willUnmount() {
|
||||
clearInterval(this.interval);
|
||||
this.editor.off("blur", this._updateCode);
|
||||
}
|
||||
willUpdateProps(nextProps) {
|
||||
this._setupSessions(nextProps);
|
||||
}
|
||||
|
||||
patched() {
|
||||
const session = this.sessions[this.state.currentTab];
|
||||
let content = this.props[this.state.currentTab];
|
||||
if (content === false) {
|
||||
const tab = this.props.js !== false ? "js" : this.props.xml ? "xml" : "css";
|
||||
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) {
|
||||
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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_setupSessions(props) {
|
||||
for (let tab of ["js", "xml", "css"]) {
|
||||
if (props[tab] !== false && !this.sessions[tab]) {
|
||||
this.sessions[tab] = new ace.EditSession(props[tab], MODES[tab]);
|
||||
this.sessions[tab].setOption("useWorker", false);
|
||||
const tabSize = tab === "xml" ? 2 : 4;
|
||||
this.sessions[tab].setOption("tabSize", tabSize);
|
||||
this.sessions[tab].setUndoManager(new ace.UndoManager());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_updateCode() {
|
||||
const editorValue = this.editor.getValue();
|
||||
const propsValue = this.props[this.state.currentTab];
|
||||
if (editorValue !== propsValue) {
|
||||
this.trigger("updateCode", {
|
||||
type: this.state.currentTab,
|
||||
value: editorValue
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// MAIN APP
|
||||
//------------------------------------------------------------------------------
|
||||
class App extends owl.Component {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
this.version = owl.__info__.version;
|
||||
this.SAMPLES = useSamples();
|
||||
this.isDirty = false;
|
||||
|
||||
this.state = useState({
|
||||
js: this.SAMPLES[0].code,
|
||||
css: this.SAMPLES[0].css || "",
|
||||
xml: this.SAMPLES[0].xml || DEFAULT_XML,
|
||||
displayWelcome: true,
|
||||
splitLayout: true,
|
||||
leftPaneWidth: Math.ceil(window.innerWidth / 2),
|
||||
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);
|
||||
this.content = useRef("content");
|
||||
}
|
||||
|
||||
runCode() {
|
||||
this.content.el.innerHTML = "";
|
||||
this.state.displayWelcome = false;
|
||||
|
||||
const { js, css, xml } = this.state;
|
||||
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();
|
||||
this.isDirty = false;
|
||||
}
|
||||
|
||||
get leftPaneStyle() {
|
||||
return `width:${this.state.leftPaneWidth}px`;
|
||||
}
|
||||
|
||||
get rightPaneStyle() {
|
||||
return `width:${window.innerWidth - 6 - this.state.leftPaneWidth}px`;
|
||||
}
|
||||
|
||||
get topEditorStyle() {
|
||||
return `flex: 0 0 ${this.state.topPanelHeight}px`;
|
||||
}
|
||||
|
||||
onMouseDown() {
|
||||
const resizer = ev => {
|
||||
this.state.leftPaneWidth = ev.clientX;
|
||||
};
|
||||
|
||||
document.body.addEventListener("mousemove", resizer);
|
||||
for (let iframe of document.getElementsByTagName("iframe")) {
|
||||
iframe.classList.add("disabled");
|
||||
}
|
||||
|
||||
document.body.addEventListener("mouseup", () => {
|
||||
document.body.removeEventListener("mousemove", resizer);
|
||||
for (let iframe of document.getElementsByTagName("iframe")) {
|
||||
iframe.classList.remove("disabled");
|
||||
}
|
||||
});
|
||||
}
|
||||
updateCode(ev) {
|
||||
if (this.state[ev.detail.type] !== ev.detail.value) {
|
||||
this.state[ev.detail.type] = ev.detail.value;
|
||||
this.isDirty = true;
|
||||
}
|
||||
}
|
||||
toggleLayout() {
|
||||
this.state.splitLayout = !this.state.splitLayout;
|
||||
}
|
||||
updatePanelHeight(ev) {
|
||||
if (!ev.detail.delta) {
|
||||
return;
|
||||
}
|
||||
let height = this.state.topPanelHeight;
|
||||
if (!height) {
|
||||
height = document.getElementsByClassName("tabbed-editor")[0].clientHeight;
|
||||
}
|
||||
this.state.topPanelHeight = height + ev.detail.delta;
|
||||
}
|
||||
|
||||
async downloadCode() {
|
||||
const { js, css, xml } = this.state;
|
||||
const content = await makeApp(js, css, xml);
|
||||
await owl.utils.loadJS("libs/FileSaver.min.js");
|
||||
saveAs(content, "app.zip");
|
||||
}
|
||||
}
|
||||
App.components = { TabbedEditor };
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// 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();
|
||||
@@ -0,0 +1,21 @@
|
||||
<!---->
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>OWL Playground</title>
|
||||
<link rel="icon" href="data:,">
|
||||
|
||||
<script src="libs/ace.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="../owl.js"></script>
|
||||
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.1/css/solid.css" integrity="sha384-QokYePQSOwpBDuhlHOsX0ymF6R/vLk/UQVz3WHa6wygxI5oGTmDTv8wahFOSspdm" crossorigin="anonymous">
|
||||
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.1/css/fontawesome.css" integrity="sha384-vd1e11sR28tEK9YANUtpIOdjGW14pS87bUBuOIoBILVWLFnS+MCX9T6MMf0VdPGq" crossorigin="anonymous">
|
||||
|
||||
<!-- Application JS/CSS -->
|
||||
<link rel="stylesheet" href="playground.css">
|
||||
<script type="module" src="app.js"></script>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,57 @@
|
||||
<templates>
|
||||
<div t-name="TabbedEditor" class="tabbed-editor">
|
||||
<div class="tabBar" t-att-class="{resizeable: props.resizeable}" t-on-mousedown="onMouseDown">
|
||||
<t t-foreach="['js', 'xml', 'css']" t-as="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"/>
|
||||
</a>
|
||||
</t>
|
||||
</div>
|
||||
<div class="code-editor" t-ref="editor"></div>
|
||||
</div>
|
||||
|
||||
<div t-name="App" class="playground">
|
||||
<div class="left-bar" t-att-class="{split: state.splitLayout}"
|
||||
t-att-style="leftPaneStyle"
|
||||
t-on-updateCode="updateCode">
|
||||
<div class="menubar">
|
||||
<a class="btn run-code flash" t-on-click="runCode" title="Execute this Code">▶ Run</a>
|
||||
<select t-on-change="setSample">
|
||||
<option t-foreach="SAMPLES" t-as="sample" t-key="sample_index">
|
||||
<t t-esc="sample.description"/>
|
||||
</option>
|
||||
</select>
|
||||
<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>
|
||||
</div>
|
||||
<TabbedEditor
|
||||
js="state.js"
|
||||
css="!state.splitLayout and state.css"
|
||||
xml="!state.splitLayout and state.xml"
|
||||
t-att-style="topEditorStyle"/>
|
||||
<t t-if="state.splitLayout">
|
||||
<div class="separator horizontal"/>
|
||||
<TabbedEditor
|
||||
js="false"
|
||||
css="state.css"
|
||||
xml="state.xml"
|
||||
resizeable="true"
|
||||
t-on-updatePanelHeight="updatePanelHeight"/>
|
||||
</t>
|
||||
</div>
|
||||
<div class="separator vertical" t-on-mousedown="onMouseDown"/>
|
||||
<div class="right-pane" t-att-style="rightPaneStyle">
|
||||
<div class="welcome" t-if="state.displayWelcome">
|
||||
<div>🦉 Odoo Web Library 🦉</div>
|
||||
<div>v<t t-esc="version"/></div>
|
||||
<div class="url"><a href="https://github.com/odoo/owl">https://github.com/odoo/owl</a></div>
|
||||
<div class="note">
|
||||
<p>Note: these examples are using recent features of Javascript, and require a recent browser to work without a transpilation step!
|
||||
For example, it makes use of class fields and class static fields. These examples should work in a recent Chrome version.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content" t-ref="content"/>
|
||||
</div>
|
||||
</div>
|
||||
</templates>
|
||||
@@ -1,57 +1,79 @@
|
||||
<templates>
|
||||
<div t-name="TabbedEditor" class="tabbed-editor">
|
||||
<div class="tabBar" t-att-class="{resizeable: props.resizeable}" t-on-mousedown="onMouseDown">
|
||||
<t t-foreach="['js', 'xml', 'css']" t-as="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"/>
|
||||
</a>
|
||||
</t>
|
||||
</div>
|
||||
<div class="code-editor" t-ref="editor"></div>
|
||||
</div>
|
||||
|
||||
<div t-name="App" class="playground">
|
||||
<div class="left-bar" t-att-class="{split: state.splitLayout}"
|
||||
t-att-style="leftPaneStyle"
|
||||
t-on-updateCode="updateCode">
|
||||
<div class="menubar">
|
||||
<a class="btn run-code flash" t-on-click="runCode" title="Execute this Code">▶ Run</a>
|
||||
<select t-on-change="setSample">
|
||||
<option t-foreach="SAMPLES" t-as="sample" t-key="sample_index">
|
||||
<t t-esc="sample.description"/>
|
||||
</option>
|
||||
</select>
|
||||
<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>
|
||||
<div t-name="App" t-on-run-project="_run" class="h-full flex">
|
||||
|
||||
<Editor js="state.js" xml="state.xml" css="state.css" />
|
||||
|
||||
|
||||
<div class="flex flex-col w-1/2 h-full">
|
||||
|
||||
<div class="bg-purple-300 mx-2 my-1 p-2 rounded-sm" style="background-color: #865A7B">
|
||||
<div class="flex items-center">
|
||||
<div class="relative">
|
||||
<select t-on-change="_setSample" class="block appearance-none w-full bg-gray-200 border border-gray-200 text-gray-700 py-1 px-2 pr-8 rounded-sm leading-tight focus:outline-none focus:bg-white focus:border-gray-500">
|
||||
<t t-foreach="samples" t-as="sample" t-key="sample_index">
|
||||
<option>
|
||||
<t t-esc="sample.description"/>
|
||||
</option>
|
||||
</t>
|
||||
</select>
|
||||
<div class="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-gray-700">
|
||||
<svg class="fill-current h-4 w-4"
|
||||
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
|
||||
<path d="M9.293 12.95l.707.707L15.657 8l-1.414-1.414L10 10.828 5.757 6.586 4.343 8z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div t-ref="iframe-container" class="bg-white flex-grow m-2 rounded-sm shadow"></div>
|
||||
</div>
|
||||
<TabbedEditor
|
||||
js="state.js"
|
||||
css="!state.splitLayout and state.css"
|
||||
xml="!state.splitLayout and state.xml"
|
||||
t-att-style="topEditorStyle"/>
|
||||
<t t-if="state.splitLayout">
|
||||
<div class="separator horizontal"/>
|
||||
<TabbedEditor
|
||||
js="false"
|
||||
css="state.css"
|
||||
xml="state.xml"
|
||||
resizeable="true"
|
||||
t-on-updatePanelHeight="updatePanelHeight"/>
|
||||
</t>
|
||||
</div>
|
||||
<div class="separator vertical" t-on-mousedown="onMouseDown"/>
|
||||
<div class="right-pane" t-att-style="rightPaneStyle">
|
||||
<div class="welcome" t-if="state.displayWelcome">
|
||||
<div>🦉 Odoo Web Library 🦉</div>
|
||||
<div>v<t t-esc="version"/></div>
|
||||
<div class="url"><a href="https://github.com/odoo/owl">https://github.com/odoo/owl</a></div>
|
||||
<div class="note">
|
||||
<p>Note: these examples are using recent features of Javascript, and require a recent browser to work without a transpilation step!
|
||||
For example, it makes use of class fields and class static fields. These examples should work in a recent Chrome version.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<t t-name="Editor" t-on-open-file="_openFile">
|
||||
<div class="resize-x w-1/2">
|
||||
|
||||
<div class="flex justify-between items-center" style="background-color: #865A7B">
|
||||
<ul class="flex justify-start">
|
||||
<li>
|
||||
<Tab t-on-click="_openXML">
|
||||
xml
|
||||
</Tab>
|
||||
</li>
|
||||
<li>
|
||||
<Tab t-on-click="_openJS">
|
||||
js
|
||||
</Tab>
|
||||
</li>
|
||||
<li>
|
||||
<Tab t-on-click="_openCSS">
|
||||
css
|
||||
</Tab>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="flex justify-end m-2">
|
||||
<li>
|
||||
<button class="h-6 w-6 bg-white rounded-full p-1 shadow" t-on-click="_run">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
|
||||
</svg>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div id="container" class="h-full"></div>
|
||||
</div>
|
||||
<div class="content" t-ref="content"/>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<t t-name="Tab">
|
||||
<button class="bg-white hover:bg-gray-300 uppercase rounded-sm px-2 shadow mx-1">
|
||||
<t t-slot="default"/>
|
||||
</button>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
Reference in New Issue
Block a user