mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e1ff7ac6a4 | |||
| fbf4c4add2 | |||
| 78d6ff735e | |||
| a7f51fa666 | |||
| 5175f95289 | |||
| 3c9f4a8ae9 | |||
| 310730782c | |||
| 77a413d750 | |||
| fe31f93c94 | |||
| 9cc0f88e02 | |||
| 412fda10fd | |||
| aa441274c7 |
+1
-2
@@ -10,7 +10,7 @@
|
|||||||
<link rel="stylesheet" href="assets/milligram.css">
|
<link rel="stylesheet" href="assets/milligram.css">
|
||||||
<link rel="stylesheet" href="assets/highlight.tomorrow.css">
|
<link rel="stylesheet" href="assets/highlight.tomorrow.css">
|
||||||
<link rel="stylesheet" href="assets/main.css">
|
<link rel="stylesheet" href="assets/main.css">
|
||||||
<script src="./owl.js"></script>
|
<script type="importmap">{ "imports": { "@odoo/owl": "./owl.js" } }</script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<header class="container">
|
<header class="container">
|
||||||
@@ -68,7 +68,6 @@
|
|||||||
<p><a href=".">OWL</a> is licensed under LGPLv3.<br>Logo from <a href="https://github.com/googlefonts/noto-emoji">Google Noto Emoji Font</a>, licensed under Apache License 2.0</p>
|
<p><a href=".">OWL</a> is licensed under LGPLv3.<br>Logo from <a href="https://github.com/googlefonts/noto-emoji">Google Noto Emoji Font</a>, licensed under Apache License 2.0</p>
|
||||||
</footer>
|
</footer>
|
||||||
<script src="assets/highlight.pack.js"></script>
|
<script src="assets/highlight.pack.js"></script>
|
||||||
<script type="importmap">{ "imports": { "@odoo/owl": "./owl.js" } }</script>
|
|
||||||
<script type="module" src="display_code.js"></script>
|
<script type="module" src="display_code.js"></script>
|
||||||
<script type="module" src="counter.js"></script>
|
<script type="module" src="counter.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -447,6 +447,11 @@ export class CodeGenerator {
|
|||||||
.join("");
|
.join("");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
translate(str: string): string {
|
||||||
|
const match = translationRE.exec(str) as any;
|
||||||
|
return match[1] + this.translateFn(match[2]) + match[3];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns the newly created block name, if any
|
* @returns the newly created block name, if any
|
||||||
*/
|
*/
|
||||||
@@ -527,8 +532,7 @@ export class CodeGenerator {
|
|||||||
|
|
||||||
let value = ast.value;
|
let value = ast.value;
|
||||||
if (value && ctx.translate !== false) {
|
if (value && ctx.translate !== false) {
|
||||||
const match = translationRE.exec(value) as any;
|
value = this.translate(value);
|
||||||
value = match[1] + this.translateFn(match[2]) + match[3];
|
|
||||||
}
|
}
|
||||||
if (!ctx.inPreTag) {
|
if (!ctx.inPreTag) {
|
||||||
value = value.replace(whitespaceRE, " ");
|
value = value.replace(whitespaceRE, " ");
|
||||||
@@ -1095,10 +1099,11 @@ export class CodeGenerator {
|
|||||||
} else {
|
} else {
|
||||||
let value: string;
|
let value: string;
|
||||||
if (ast.defaultValue) {
|
if (ast.defaultValue) {
|
||||||
|
const defaultValue = ctx.translate ? this.translate(ast.defaultValue) : ast.defaultValue;
|
||||||
if (ast.value) {
|
if (ast.value) {
|
||||||
value = `withDefault(${expr}, \`${ast.defaultValue}\`)`;
|
value = `withDefault(${expr}, \`${defaultValue}\`)`;
|
||||||
} else {
|
} else {
|
||||||
value = `\`${ast.defaultValue}\``;
|
value = `\`${defaultValue}\``;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
value = expr;
|
value = expr;
|
||||||
|
|||||||
@@ -336,10 +336,10 @@ function parseDOMNode(node: Element, ctx: ParsingContext): AST | null {
|
|||||||
|
|
||||||
for (let attr of nodeAttrsNames) {
|
for (let attr of nodeAttrsNames) {
|
||||||
const value = node.getAttribute(attr)!;
|
const value = node.getAttribute(attr)!;
|
||||||
if (attr.startsWith("t-on")) {
|
if (attr === "t-on" || attr === "t-on-") {
|
||||||
if (attr === "t-on") {
|
|
||||||
throw new OwlError("Missing event name with t-on directive");
|
throw new OwlError("Missing event name with t-on directive");
|
||||||
}
|
}
|
||||||
|
if (attr.startsWith("t-on-")) {
|
||||||
on = on || {};
|
on = on || {};
|
||||||
on[attr.slice(5)] = value;
|
on[attr.slice(5)] = value;
|
||||||
} else if (attr.startsWith("t-model")) {
|
} else if (attr.startsWith("t-model")) {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { Scheduler } from "./scheduler";
|
|||||||
import { validateProps } from "./template_helpers";
|
import { validateProps } from "./template_helpers";
|
||||||
import { TemplateSet, TemplateSetConfig } from "./template_set";
|
import { TemplateSet, TemplateSetConfig } from "./template_set";
|
||||||
import { validateTarget } from "./utils";
|
import { validateTarget } from "./utils";
|
||||||
|
import { toRaw, reactive } from "./reactivity";
|
||||||
|
|
||||||
// reimplement dev mode stuff see last change in 0f7a8289a6fb8387c3c1af41c6664b2a8448758f
|
// reimplement dev mode stuff see last change in 0f7a8289a6fb8387c3c1af41c6664b2a8448758f
|
||||||
|
|
||||||
@@ -39,6 +40,8 @@ declare global {
|
|||||||
apps: Set<App>;
|
apps: Set<App>;
|
||||||
Fiber: typeof Fiber;
|
Fiber: typeof Fiber;
|
||||||
RootFiber: typeof RootFiber;
|
RootFiber: typeof RootFiber;
|
||||||
|
toRaw: typeof toRaw;
|
||||||
|
reactive: typeof reactive;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -47,6 +50,8 @@ window.__OWL_DEVTOOLS__ ||= {
|
|||||||
apps: new Set<App>(),
|
apps: new Set<App>(),
|
||||||
Fiber: Fiber,
|
Fiber: Fiber,
|
||||||
RootFiber: RootFiber,
|
RootFiber: RootFiber,
|
||||||
|
toRaw: toRaw,
|
||||||
|
reactive: reactive,
|
||||||
};
|
};
|
||||||
|
|
||||||
export class App<
|
export class App<
|
||||||
|
|||||||
+13
-6
@@ -59,28 +59,35 @@ export function useChildSubEnv(envExtension: Env) {
|
|||||||
// useEffect
|
// useEffect
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
type EffectDeps<T extends any[]> = T | (T extends [...infer H, never] ? EffectDeps<H> : never);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {...any} dependencies the dependencies computed by computeDependencies
|
* @template T
|
||||||
|
* @param {...T} dependencies the dependencies computed by computeDependencies
|
||||||
* @returns {void|(()=>void)} a cleanup function that reverses the side
|
* @returns {void|(()=>void)} a cleanup function that reverses the side
|
||||||
* effects of the effect callback.
|
* effects of the effect callback.
|
||||||
*/
|
*/
|
||||||
type Effect = (...dependencies: any[]) => void | (() => void);
|
type Effect<T extends [...T]> = (...dependencies: EffectDeps<T>) => void | (() => void);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This hook will run a callback when a component is mounted and patched, and
|
* This hook will run a callback when a component is mounted and patched, and
|
||||||
* will run a cleanup function before patching and before unmounting the
|
* will run a cleanup function before patching and before unmounting the
|
||||||
* the component.
|
* the component.
|
||||||
*
|
*
|
||||||
* @param {Effect} effect the effect to run on component mount and/or patch
|
* @template T
|
||||||
* @param {()=>any[]} [computeDependencies=()=>[NaN]] a callback to compute
|
* @param {Effect<T>} effect the effect to run on component mount and/or patch
|
||||||
|
* @param {()=>T} [computeDependencies=()=>[NaN]] a callback to compute
|
||||||
* dependencies that will decide if the effect needs to be cleaned up and
|
* dependencies that will decide if the effect needs to be cleaned up and
|
||||||
* run again. If the dependencies did not change, the effect will not run
|
* run again. If the dependencies did not change, the effect will not run
|
||||||
* again. The default value returns an array containing only NaN because
|
* again. The default value returns an array containing only NaN because
|
||||||
* NaN !== NaN, which will cause the effect to rerun on every patch.
|
* NaN !== NaN, which will cause the effect to rerun on every patch.
|
||||||
*/
|
*/
|
||||||
export function useEffect(effect: Effect, computeDependencies: () => any[] = () => [NaN]) {
|
export function useEffect<T extends [...T]>(
|
||||||
|
effect: Effect<T>,
|
||||||
|
computeDependencies: () => T = () => [NaN] as never
|
||||||
|
) {
|
||||||
let cleanup: (() => void) | void;
|
let cleanup: (() => void) | void;
|
||||||
let dependencies: any[];
|
let dependencies: T;
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
dependencies = computeDependencies();
|
dependencies = computeDependencies();
|
||||||
cleanup = effect(...dependencies);
|
cleanup = effect(...dependencies);
|
||||||
|
|||||||
@@ -8,6 +8,12 @@ import { OwlError } from "./error_handling";
|
|||||||
import type { ComponentNode } from "./component_node";
|
import type { ComponentNode } from "./component_node";
|
||||||
|
|
||||||
const ObjectCreate = Object.create;
|
const ObjectCreate = Object.create;
|
||||||
|
const ObjectGetPrototypeOf = Object.getPrototypeOf;
|
||||||
|
const ObjectGetOwnPropertyDescriptors = Object.getOwnPropertyDescriptors;
|
||||||
|
const ObjectDefineProperty = Object.defineProperty;
|
||||||
|
const ObjectEntries = Object.entries;
|
||||||
|
const hasOwnProperty = (obj: Object, prop: PropertyKey) =>
|
||||||
|
Object.prototype.hasOwnProperty.call(obj, prop);
|
||||||
/**
|
/**
|
||||||
* This file contains utility functions that will be injected in each template,
|
* This file contains utility functions that will be injected in each template,
|
||||||
* to perform various useful tasks in the compiled code.
|
* to perform various useful tasks in the compiled code.
|
||||||
@@ -49,8 +55,14 @@ function callSlot(
|
|||||||
|
|
||||||
function capture(ctx: any): any {
|
function capture(ctx: any): any {
|
||||||
const result = ObjectCreate(ctx);
|
const result = ObjectCreate(ctx);
|
||||||
for (let k in ctx) {
|
let current = ctx;
|
||||||
result[k] = ctx[k];
|
while (current && current !== Object.prototype) {
|
||||||
|
for (const [key, descriptor] of ObjectEntries(ObjectGetOwnPropertyDescriptors(current))) {
|
||||||
|
if (!hasOwnProperty(result, key) && "value" in descriptor) {
|
||||||
|
ObjectDefineProperty(result, key, descriptor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
current = ObjectGetPrototypeOf(current);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,79 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`translation support body of t-sets are translated 1`] = `
|
||||||
|
"function anonymous(app, bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||||
|
let { isBoundary, withDefault, setContextValue } = helpers;
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
ctx = Object.create(ctx);
|
||||||
|
ctx[isBoundary] = 1
|
||||||
|
setContextValue(ctx, \\"label\\", \`translated\`);
|
||||||
|
return text(ctx['label']);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`translation support body of t-sets inside translation=off are not translated 1`] = `
|
||||||
|
"function anonymous(app, bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||||
|
let { isBoundary, withDefault, setContextValue } = helpers;
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
ctx = Object.create(ctx);
|
||||||
|
ctx[isBoundary] = 1
|
||||||
|
setContextValue(ctx, \\"label\\", \`untranslated\`);
|
||||||
|
return text(ctx['label']);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`translation support body of t-sets with html content are translated 1`] = `
|
||||||
|
"function anonymous(app, bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||||
|
let { isBoundary, withDefault, LazyValue, safeOutput } = helpers;
|
||||||
|
|
||||||
|
let block1 = createBlock(\`<div>translated</div>\`);
|
||||||
|
|
||||||
|
function value1(ctx, node, key = \\"\\") {
|
||||||
|
return block1();
|
||||||
|
}
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
ctx = Object.create(ctx);
|
||||||
|
ctx[isBoundary] = 1
|
||||||
|
ctx[\`label\`] = new LazyValue(value1, ctx, this, node, key);
|
||||||
|
return safeOutput(ctx['label']);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`translation support body of t-sets with text and html content are translated 1`] = `
|
||||||
|
"function anonymous(app, bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||||
|
let { isBoundary, withDefault, LazyValue, safeOutput } = helpers;
|
||||||
|
|
||||||
|
let block3 = createBlock(\`<div>translated</div>\`);
|
||||||
|
|
||||||
|
function value1(ctx, node, key = \\"\\") {
|
||||||
|
const b2 = text(\` translated \`);
|
||||||
|
const b3 = block3();
|
||||||
|
return multi([b2, b3]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
ctx = Object.create(ctx);
|
||||||
|
ctx[isBoundary] = 1
|
||||||
|
ctx[\`label\`] = new LazyValue(value1, ctx, this, node, key);
|
||||||
|
return safeOutput(ctx['label']);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`translation support can set and remove translatable attributes 1`] = `
|
exports[`translation support can set and remove translatable attributes 1`] = `
|
||||||
"function anonymous(app, bdom, helpers
|
"function anonymous(app, bdom, helpers
|
||||||
) {
|
) {
|
||||||
@@ -52,6 +126,21 @@ exports[`translation support some attributes are translated 1`] = `
|
|||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`translation support t-set and falsy t-value: t-body are translated 1`] = `
|
||||||
|
"function anonymous(app, bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||||
|
let { isBoundary, withDefault, setContextValue } = helpers;
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
ctx = Object.create(ctx);
|
||||||
|
ctx[isBoundary] = 1
|
||||||
|
setContextValue(ctx, \\"label\\", withDefault(false, \`translated\`));
|
||||||
|
return text(ctx['label']);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`translation support translation is done on the trimmed text, with extra spaces readded after 1`] = `
|
exports[`translation support translation is done on the trimmed text, with extra spaces readded after 1`] = `
|
||||||
"function anonymous(app, bdom, helpers
|
"function anonymous(app, bdom, helpers
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -1147,6 +1147,24 @@ describe("qweb parser", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("t-onclick without dash", async () => {
|
||||||
|
expect(() => parse(`<button t-onclick="add">Click</button>`)).toThrowError(
|
||||||
|
"Unknown QWeb directive: 't-onclick'"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("t-on without event", async () => {
|
||||||
|
expect(() => parse(`<button t-on="add">Click</button>`)).toThrowError(
|
||||||
|
"Missing event name with t-on directive"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("t-on- without event", async () => {
|
||||||
|
expect(() => parse(`<button t-on-="add">Click</button>`)).toThrowError(
|
||||||
|
"Missing event name with t-on directive"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// t-model
|
// t-model
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -1275,6 +1293,12 @@ describe("qweb parser", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("component with event handler", async () => {
|
||||||
|
expect(() => parse(`<MyComponent t-onclick="someMethod"/>`)).toThrowError(
|
||||||
|
"unsupported directive on Component: t-onclick"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
test("component with t-ref", async () => {
|
test("component with t-ref", async () => {
|
||||||
expect(() => parse(`<MyComponent t-ref="something"/>`)).toThrow(
|
expect(() => parse(`<MyComponent t-ref="something"/>`)).toThrow(
|
||||||
"t-ref is no longer supported on components. Consider exposing only the public part of the component's API through a callback prop."
|
"t-ref is no longer supported on components. Consider exposing only the public part of the component's API through a callback prop."
|
||||||
|
|||||||
@@ -100,4 +100,74 @@ describe("translation support", () => {
|
|||||||
expect(translateFn).toHaveBeenCalledWith("some word");
|
expect(translateFn).toHaveBeenCalledWith("some word");
|
||||||
expect(fixture.innerHTML).toBe("<div>un mot</div>");
|
expect(fixture.innerHTML).toBe("<div>un mot</div>");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("body of t-sets are translated", async () => {
|
||||||
|
class SomeComponent extends Component {
|
||||||
|
static template = xml`
|
||||||
|
<t t-set="label">untranslated</t>
|
||||||
|
<t t-esc="label"/>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const translateFn = () => "translated";
|
||||||
|
|
||||||
|
await mount(SomeComponent, fixture, { translateFn });
|
||||||
|
expect(fixture.innerHTML).toBe("translated");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("body of t-sets inside translation=off are not translated", async () => {
|
||||||
|
class SomeComponent extends Component {
|
||||||
|
static template = xml`
|
||||||
|
<t t-translation="off">
|
||||||
|
<t t-set="label">untranslated</t>
|
||||||
|
<t t-esc="label"/>
|
||||||
|
</t>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const translateFn = () => "translated";
|
||||||
|
|
||||||
|
await mount(SomeComponent, fixture, { translateFn });
|
||||||
|
expect(fixture.innerHTML).toBe("untranslated");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("body of t-sets with html content are translated", async () => {
|
||||||
|
class SomeComponent extends Component {
|
||||||
|
static template = xml`
|
||||||
|
<t t-set="label"><div>untranslated</div></t>
|
||||||
|
<t t-out="label"/>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const translateFn = () => "translated";
|
||||||
|
|
||||||
|
await mount(SomeComponent, fixture, { translateFn });
|
||||||
|
expect(fixture.innerHTML).toBe("<div>translated</div>");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("body of t-sets with text and html content are translated", async () => {
|
||||||
|
class SomeComponent extends Component {
|
||||||
|
static template = xml`
|
||||||
|
<t t-set="label">
|
||||||
|
some text
|
||||||
|
<div>untranslated</div>
|
||||||
|
</t>
|
||||||
|
<t t-out="label"/>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const translateFn = () => "translated";
|
||||||
|
|
||||||
|
await mount(SomeComponent, fixture, { translateFn });
|
||||||
|
expect(fixture.innerHTML).toBe(" translated <div>translated</div>");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("t-set and falsy t-value: t-body are translated", async () => {
|
||||||
|
class SomeComponent extends Component {
|
||||||
|
static template = xml`
|
||||||
|
<t t-set="label" t-value="false">untranslated</t>
|
||||||
|
<t t-esc="label"/>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const translateFn = () => "translated";
|
||||||
|
|
||||||
|
await mount(SomeComponent, fixture, { translateFn });
|
||||||
|
expect(fixture.innerHTML).toBe("translated");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,50 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`t-call component with an enumerable getter, t-call inside slot 1`] = `
|
||||||
|
"function anonymous(app, bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||||
|
let { capture, markRaw } = helpers;
|
||||||
|
const callTemplate_1 = app.getTemplate(\`sub\`);
|
||||||
|
const comp1 = app.createComponent(\`Child\`, true, true, false, []);
|
||||||
|
|
||||||
|
function slot1(ctx, node, key = \\"\\") {
|
||||||
|
return callTemplate_1.call(this, ctx, node, key + \`__1\`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
const ctx1 = capture(ctx);
|
||||||
|
return comp1({slots: markRaw({'default': {__render: slot1.bind(this), __ctx: ctx1}})}, key + \`__2\`, node, this, null);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`t-call component with an enumerable getter, t-call inside slot 2`] = `
|
||||||
|
"function anonymous(app, bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||||
|
|
||||||
|
let block1 = createBlock(\`<div><block-text-0/></div>\`);
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
let txt1 = ctx['foo'];
|
||||||
|
return block1([txt1]);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`t-call component with an enumerable getter, t-call inside slot 3`] = `
|
||||||
|
"function anonymous(app, bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||||
|
let { callSlot } = helpers;
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
return callSlot(ctx, node, key, 'default', false, {});
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`t-call dynamic t-call 1`] = `
|
exports[`t-call dynamic t-call 1`] = `
|
||||||
"function anonymous(app, bdom, helpers
|
"function anonymous(app, bdom, helpers
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -425,4 +425,26 @@ describe("t-call", () => {
|
|||||||
await nextTick();
|
await nextTick();
|
||||||
expect(fixture.innerHTML).toBe("Bchild");
|
expect(fixture.innerHTML).toBe("Bchild");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("component with an enumerable getter, t-call inside slot", async () => {
|
||||||
|
class Child extends Component {
|
||||||
|
static template = xml`<t t-slot="default"/>`;
|
||||||
|
}
|
||||||
|
class Parent extends Component {
|
||||||
|
static components = { Child };
|
||||||
|
static template = xml`<Child><t t-call="sub"/></Child>`;
|
||||||
|
}
|
||||||
|
// simulate adding a getter with patch in odoo: getter will be enumarable
|
||||||
|
Object.defineProperty(Parent.prototype, "foo", {
|
||||||
|
get() {
|
||||||
|
return 1;
|
||||||
|
},
|
||||||
|
enumerable: true,
|
||||||
|
});
|
||||||
|
const app = new App(Parent);
|
||||||
|
app.addTemplate("sub", `<div t-esc="foo"/>`);
|
||||||
|
|
||||||
|
await app.mount(fixture);
|
||||||
|
expect(fixture.innerHTML).toBe("<div>1</div>");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "Owl devtools",
|
"name": "Owl devtools",
|
||||||
"version": "1.0",
|
"version": "1.1.1",
|
||||||
"manifest_version": 3,
|
"manifest_version": 3,
|
||||||
"description": "Chrome devtools extension for Odoo Owl framework",
|
"description": "Chrome devtools extension for Odoo Owl framework",
|
||||||
"icons": {
|
"icons": {
|
||||||
|
|||||||
+1
-1
@@ -72,7 +72,7 @@
|
|||||||
</t>
|
</t>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div t-if="store.contextMenu.activeMenu === contextMenuId" class="custom-menu" t-attf-style="top: {{store.contextMenu.top}}; left: {{store.contextMenu.left}}" t-ref="contextmenu">
|
<div t-if="store.contextMenu.activeMenu === contextMenuId" class="custom-menu" t-ref="contextmenu">
|
||||||
<ul class="my-1">
|
<ul class="my-1">
|
||||||
<li t-on-click.stop="() => this.store.inspectComponent('source', store.activeComponent.path)" class="custom-menu-item py-1 px-4">Inspect source code</li>
|
<li t-on-click.stop="() => this.store.inspectComponent('source', store.activeComponent.path)" class="custom-menu-item py-1 px-4">Inspect source code</li>
|
||||||
<t t-if="store.activeComponent.path.length !== 1">
|
<t t-if="store.activeComponent.path.length !== 1">
|
||||||
|
|||||||
+3
-3
@@ -30,7 +30,7 @@
|
|||||||
</t>
|
</t>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div t-if="store.contextMenu.activeMenu === contextMenuId" class="custom-menu" t-attf-style="top: {{store.contextMenu.top}}; left: {{store.contextMenu.left}}" t-ref="contextmenu">
|
<div t-if="store.contextMenu.activeMenu === contextMenuId" class="custom-menu" t-ref="contextmenu">
|
||||||
<ul class="my-1">
|
<ul class="my-1">
|
||||||
<li t-on-click="() => this.store.logObjectInConsole(this.props.object.path)" class="custom-menu-item py-1 px-4 text-nowrap">Store as global variable</li>
|
<li t-on-click="() => this.store.logObjectInConsole(this.props.object.path)" class="custom-menu-item py-1 px-4 text-nowrap">Store as global variable</li>
|
||||||
<t t-if='props.object.contentType == "function"'>
|
<t t-if='props.object.contentType == "function"'>
|
||||||
@@ -38,8 +38,8 @@
|
|||||||
</t>
|
</t>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<t t-if="props.object.toggled">
|
<t t-if="props.object.toggled" t-key="contextMenuId">
|
||||||
<t t-foreach="props.object.children" t-as="child" t-key="child.name">
|
<t t-foreach="props.object.children" t-as="child" t-key="child_index">
|
||||||
<ObjectTreeElement t-if="props.object.objectType === 'subscription'" object="child" keys="props.keys"/>
|
<ObjectTreeElement t-if="props.object.objectType === 'subscription'" object="child" keys="props.keys"/>
|
||||||
<ObjectTreeElement t-else="" object="child"/>
|
<ObjectTreeElement t-else="" object="child"/>
|
||||||
</t>
|
</t>
|
||||||
|
|||||||
+29
-1
@@ -1,9 +1,11 @@
|
|||||||
/** @odoo-module **/
|
/** @odoo-module **/
|
||||||
|
|
||||||
import { isElementInCenterViewport, minimizeKey } from "../../../../utils";
|
import { isElementInCenterViewport, minimizeKey, IS_FIREFOX } from "../../../../utils";
|
||||||
import { useStore } from "../../../store/store";
|
import { useStore } from "../../../store/store";
|
||||||
import { HighlightText } from "./highlight_text/highlight_text";
|
import { HighlightText } from "./highlight_text/highlight_text";
|
||||||
|
|
||||||
|
const browserInstance = IS_FIREFOX ? browser : chrome;
|
||||||
|
|
||||||
const { Component, useRef, useState, useEffect, onMounted } = owl;
|
const { Component, useRef, useState, useEffect, onMounted } = owl;
|
||||||
|
|
||||||
export class TreeElement extends Component {
|
export class TreeElement extends Component {
|
||||||
@@ -105,4 +107,30 @@ export class TreeElement extends Component {
|
|||||||
this.store.selectComponent(this.props.component.path);
|
this.store.selectComponent(this.props.component.path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Adds the component name to the components toggle blacklist if not already present
|
||||||
|
// Else, remove it from the blacklist
|
||||||
|
toggleComponentToBlacklist() {
|
||||||
|
if (this.store.settings.componentsToggleBlacklist.has(this.props.component.name)) {
|
||||||
|
if (!this.props.component.toggled) {
|
||||||
|
this.props.component.toggled = !this.props.component.toggled;
|
||||||
|
}
|
||||||
|
this.store.settings.componentsToggleBlacklist.delete(this.props.component.name);
|
||||||
|
browserInstance.storage.local.set({
|
||||||
|
owlDevtoolsComponentsToggleBlacklist: Array.from(
|
||||||
|
this.store.settings.componentsToggleBlacklist
|
||||||
|
),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if (this.props.component.toggled) {
|
||||||
|
this.props.component.toggled = !this.props.component.toggled;
|
||||||
|
}
|
||||||
|
this.store.settings.componentsToggleBlacklist.add(this.props.component.name);
|
||||||
|
browserInstance.storage.local.set({
|
||||||
|
owlDevtoolsComponentsToggleBlacklist: Array.from(
|
||||||
|
this.store.settings.componentsToggleBlacklist
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-1
@@ -26,7 +26,7 @@
|
|||||||
<span t-if="props.component.depth">></span>
|
<span t-if="props.component.depth">></span>
|
||||||
<span class="version" t-else="">owl=<t t-esc="props.component.version"/></span>
|
<span class="version" t-else="">owl=<t t-esc="props.component.version"/></span>
|
||||||
</div>
|
</div>
|
||||||
<div t-if="store.contextMenu.activeMenu === contextMenuId" class="custom-menu" t-attf-style="top: {{store.contextMenu.top}}; left: {{store.contextMenu.left}}" t-ref="contextmenu">
|
<div t-if="store.contextMenu.activeMenu === contextMenuId" class="custom-menu" t-ref="contextmenu">
|
||||||
<ul class="my-1">
|
<ul class="my-1">
|
||||||
<li t-on-click.stop="() => this.store.toggleComponentAndChildren(props.component, true)" class="custom-menu-item py-1 px-4">Expand children</li>
|
<li t-on-click.stop="() => this.store.toggleComponentAndChildren(props.component, true)" class="custom-menu-item py-1 px-4">Expand children</li>
|
||||||
<li t-on-click.stop="() => this.store.toggleComponentAndChildren(props.component, false)" class="custom-menu-item py-1 px-4">Fold all children</li>
|
<li t-on-click.stop="() => this.store.toggleComponentAndChildren(props.component, false)" class="custom-menu-item py-1 px-4">Fold all children</li>
|
||||||
@@ -43,6 +43,10 @@
|
|||||||
<t t-else="">
|
<t t-else="">
|
||||||
<li t-on-click.stop="() => this.store.logObjectInConsole([...props.component.path])" class="custom-menu-item py-1 px-4">Store as global variable</li>
|
<li t-on-click.stop="() => this.store.logObjectInConsole([...props.component.path])" class="custom-menu-item py-1 px-4">Store as global variable</li>
|
||||||
</t>
|
</t>
|
||||||
|
<li t-on-click.stop="() => this.toggleComponentToBlacklist()" class="custom-menu-item py-1 px-4">
|
||||||
|
<t t-if="store.settings.componentsToggleBlacklist.has(props.component.name)">Don't fold component by default</t>
|
||||||
|
<t t-else="">Fold component by default</t>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -43,7 +43,7 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</t>
|
</t>
|
||||||
<div t-if="store.contextMenu.activeMenu === componentContextMenuId" class="custom-menu" t-attf-style="top: {{store.contextMenu.top}}; left: {{store.contextMenu.left}}" t-ref="componentContextmenu">
|
<div t-if="store.contextMenu.activeMenu === componentContextMenuId" class="custom-menu" t-ref="componentContextmenu">
|
||||||
<ul class="my-1">
|
<ul class="my-1">
|
||||||
<li t-on-click.stop="() => this.store.inspectComponent('source', props.event.path)" class="custom-menu-item py-1 px-4">Inspect source code</li>
|
<li t-on-click.stop="() => this.store.inspectComponent('source', props.event.path)" class="custom-menu-item py-1 px-4">Inspect source code</li>
|
||||||
<t t-if="props.event.path.length !== 1">
|
<t t-if="props.event.path.length !== 1">
|
||||||
|
|||||||
+2
-2
@@ -28,14 +28,14 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div t-if="store.contextMenu.activeMenu === nodeContextMenuId" class="custom-menu" t-attf-style="top: {{store.contextMenu.top}}; left: {{store.contextMenu.left}}" t-ref="nodeContextMenu">
|
<div t-if="store.contextMenu.activeMenu === nodeContextMenuId" class="custom-menu" t-ref="nodeContextMenu">
|
||||||
<ul class="my-1">
|
<ul class="my-1">
|
||||||
<li t-on-click.stop="() => this.store.toggleEventAndChildren(props.event, true)" class="custom-menu-item py-1 px-4">Expand children</li>
|
<li t-on-click.stop="() => this.store.toggleEventAndChildren(props.event, true)" class="custom-menu-item py-1 px-4">Expand children</li>
|
||||||
<li t-on-click.stop="() => this.store.toggleEventAndChildren(props.event, false)" class="custom-menu-item py-1 px-4">Fold all children</li>
|
<li t-on-click.stop="() => this.store.toggleEventAndChildren(props.event, false)" class="custom-menu-item py-1 px-4">Fold all children</li>
|
||||||
<li t-on-click.stop="() => this.store.foldDirectChildren(props.event)" class="custom-menu-item py-1 px-4">Fold direct children</li>
|
<li t-on-click.stop="() => this.store.foldDirectChildren(props.event)" class="custom-menu-item py-1 px-4">Fold direct children</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div t-if="store.contextMenu.activeMenu === componentContextMenuId" class="custom-menu" t-attf-style="top: {{store.contextMenu.top}}; left: {{store.contextMenu.left}}" t-ref="componentContextmenu">
|
<div t-if="store.contextMenu.activeMenu === componentContextMenuId" class="custom-menu" t-ref="componentContextmenu">
|
||||||
<ul class="my-1">
|
<ul class="my-1">
|
||||||
<li t-on-click.stop="() => this.store.inspectComponent('source', props.event.path)" class="custom-menu-item py-1 px-4">Inspect source code</li>
|
<li t-on-click.stop="() => this.store.inspectComponent('source', props.event.path)" class="custom-menu-item py-1 px-4">Inspect source code</li>
|
||||||
<t t-if="props.event.path.length !== 1">
|
<t t-if="props.event.path.length !== 1">
|
||||||
|
|||||||
@@ -11,10 +11,9 @@ export const store = reactive({
|
|||||||
expandByDefault: true,
|
expandByDefault: true,
|
||||||
toggleOnSelected: false,
|
toggleOnSelected: false,
|
||||||
darkmode: false,
|
darkmode: false,
|
||||||
|
componentsToggleBlacklist: new Set(),
|
||||||
},
|
},
|
||||||
contextMenu: {
|
contextMenu: {
|
||||||
top: 0,
|
|
||||||
left: 0,
|
|
||||||
id: 0,
|
id: 0,
|
||||||
activeMenu: -1,
|
activeMenu: -1,
|
||||||
// Opens the context menu corresponding with the given menu html element
|
// Opens the context menu corresponding with the given menu html element
|
||||||
@@ -29,9 +28,9 @@ export const store = reactive({
|
|||||||
if (y + menuHeight > window.innerHeight) {
|
if (y + menuHeight > window.innerHeight) {
|
||||||
y = window.innerHeight - menuHeight;
|
y = window.innerHeight - menuHeight;
|
||||||
}
|
}
|
||||||
this.left = x + "px";
|
menu.style.left = x + "px";
|
||||||
// Need 25px offset because of the main navbar from the browser devtools
|
// Need 25px offset because of the main navbar from the browser devtools
|
||||||
this.top = y - 25 + "px";
|
menu.style.top = y - 25 + "px";
|
||||||
},
|
},
|
||||||
// Close the currently displayed context menu
|
// Close the currently displayed context menu
|
||||||
close() {
|
close() {
|
||||||
@@ -110,7 +109,7 @@ export const store = reactive({
|
|||||||
);
|
);
|
||||||
this.apps = apps ? apps : [];
|
this.apps = apps ? apps : [];
|
||||||
if (!fromOld && this.settings.expandByDefault) {
|
if (!fromOld && this.settings.expandByDefault) {
|
||||||
this.apps.forEach((tree) => expandNodes(tree));
|
this.apps.forEach((tree) => expandNodes(tree, true));
|
||||||
}
|
}
|
||||||
const component = await evalFunctionInWindow(
|
const component = await evalFunctionInWindow(
|
||||||
"getComponentDetails",
|
"getComponentDetails",
|
||||||
@@ -503,13 +502,18 @@ export const store = reactive({
|
|||||||
},
|
},
|
||||||
|
|
||||||
// Reset all the relevant data about the page currently stored
|
// Reset all the relevant data about the page currently stored
|
||||||
resetData() {
|
async resetData() {
|
||||||
|
await loadSettings();
|
||||||
this.loadComponentsTree(false);
|
this.loadComponentsTree(false);
|
||||||
this.events = [];
|
this.events = [];
|
||||||
this.eventsTree = [];
|
this.eventsTree = [];
|
||||||
this.activeRecorder = false;
|
this.activeRecorder = false;
|
||||||
evalFunctionInWindow("toggleEventsRecording", [false, 0]);
|
evalFunctionInWindow("toggleEventsRecording", [false, 0]);
|
||||||
|
this.traceRenderings = false;
|
||||||
evalFunctionInWindow("toggleTracing", [false]);
|
evalFunctionInWindow("toggleTracing", [false]);
|
||||||
|
this.traceSubscriptions = false;
|
||||||
|
evalFunctionInWindow("toggleSubscriptionTracing", [false]);
|
||||||
|
this.updateIFrameList();
|
||||||
},
|
},
|
||||||
|
|
||||||
// Triggers manually the rendering of the selected component
|
// Triggers manually the rendering of the selected component
|
||||||
@@ -605,7 +609,7 @@ export const store = reactive({
|
|||||||
// Refresh the whole extension
|
// Refresh the whole extension
|
||||||
async refreshExtension() {
|
async refreshExtension() {
|
||||||
await loadScripts();
|
await loadScripts();
|
||||||
this.resetData();
|
await this.resetData();
|
||||||
},
|
},
|
||||||
|
|
||||||
// Toggle dark mode in the extension and store result in the storage
|
// Toggle dark mode in the extension and store result in the storage
|
||||||
@@ -616,7 +620,7 @@ export const store = reactive({
|
|||||||
} else {
|
} else {
|
||||||
document.querySelector("html").classList.remove("dark-mode");
|
document.querySelector("html").classList.remove("dark-mode");
|
||||||
}
|
}
|
||||||
browserInstance.storage.local.set({ owl_devtools_dark_mode: this.settings.darkMode });
|
browserInstance.storage.local.set({ owlDevtoolsDarkMode: this.settings.darkMode });
|
||||||
},
|
},
|
||||||
|
|
||||||
openDocumentation() {
|
openDocumentation() {
|
||||||
@@ -636,6 +640,8 @@ async function init() {
|
|||||||
|
|
||||||
evalInWindow("__OWL__DEVTOOLS_GLOBAL_HOOK__.devtoolsId = " + store.devtoolsId + ";");
|
evalInWindow("__OWL__DEVTOOLS_GLOBAL_HOOK__.devtoolsId = " + store.devtoolsId + ";");
|
||||||
|
|
||||||
|
await loadSettings();
|
||||||
|
|
||||||
// We want to load the base components tree when the devtools tab is first opened
|
// We want to load the base components tree when the devtools tab is first opened
|
||||||
store.loadComponentsTree(false);
|
store.loadComponentsTree(false);
|
||||||
|
|
||||||
@@ -651,8 +657,6 @@ async function init() {
|
|||||||
evalFunctionInWindow("toggleEventsRecording", [false, 0], frame);
|
evalFunctionInWindow("toggleEventsRecording", [false, 0], frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
loadSettings();
|
|
||||||
|
|
||||||
browserInstance.runtime.sendMessage({ type: "newDevtoolsPanel", id: store.devtoolsId });
|
browserInstance.runtime.sendMessage({ type: "newDevtoolsPanel", id: store.devtoolsId });
|
||||||
|
|
||||||
// Heartbeat message to test whether the extension context is still valid or not
|
// Heartbeat message to test whether the extension context is still valid or not
|
||||||
@@ -677,14 +681,14 @@ browserInstance.runtime.onConnect.addListener((port) => {
|
|||||||
store.owlStatus = await evalInWindow("window.__OWL__DEVTOOLS_GLOBAL_HOOK__ !== undefined;");
|
store.owlStatus = await evalInWindow("window.__OWL__DEVTOOLS_GLOBAL_HOOK__ !== undefined;");
|
||||||
if (store.owlStatus) {
|
if (store.owlStatus) {
|
||||||
evalInWindow("__OWL__DEVTOOLS_GLOBAL_HOOK__.devtoolsId = " + store.devtoolsId + ";");
|
evalInWindow("__OWL__DEVTOOLS_GLOBAL_HOOK__.devtoolsId = " + store.devtoolsId + ";");
|
||||||
store.resetData();
|
await store.resetData();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Received when a frame has been delayed when loading the scripts due to owl being lazy loaded
|
// Received when a frame has been delayed when loading the scripts due to owl being lazy loaded
|
||||||
if (msg.type === "FrameReady") {
|
if (msg.type === "FrameReady") {
|
||||||
store.updateIFrameList();
|
store.updateIFrameList();
|
||||||
store.owlStatus = true;
|
store.owlStatus = true;
|
||||||
store.resetData();
|
await store.resetData();
|
||||||
}
|
}
|
||||||
// We need to reload the components tree when the set of apps in the page is modified
|
// We need to reload the components tree when the set of apps in the page is modified
|
||||||
if (msg.type === "RefreshApps") {
|
if (msg.type === "RefreshApps") {
|
||||||
@@ -740,11 +744,12 @@ browserInstance.runtime.onConnect.addListener((port) => {
|
|||||||
// Load all settings from the chrome sync storage
|
// Load all settings from the chrome sync storage
|
||||||
async function loadSettings() {
|
async function loadSettings() {
|
||||||
let storage = await browserInstance.storage.local.get();
|
let storage = await browserInstance.storage.local.get();
|
||||||
if (storage.owl_devtools_dark_mode === undefined) {
|
// Darkmode
|
||||||
|
if (storage.owlDevtoolsDarkMode === undefined) {
|
||||||
// Load dark mode based on the global settings of the chrome devtools
|
// Load dark mode based on the global settings of the chrome devtools
|
||||||
darkMode = browserInstance.devtools.panels.themeName === "dark";
|
darkMode = browserInstance.devtools.panels.themeName === "dark";
|
||||||
} else {
|
} else {
|
||||||
darkMode = storage.owl_devtools_dark_mode;
|
darkMode = storage.owlDevtoolsDarkMode;
|
||||||
}
|
}
|
||||||
store.settings.darkMode = darkMode;
|
store.settings.darkMode = darkMode;
|
||||||
if (darkMode) {
|
if (darkMode) {
|
||||||
@@ -752,6 +757,12 @@ async function loadSettings() {
|
|||||||
} else {
|
} else {
|
||||||
document.querySelector("html").classList.remove("dark-mode");
|
document.querySelector("html").classList.remove("dark-mode");
|
||||||
}
|
}
|
||||||
|
// Components toggle blacklist
|
||||||
|
if (storage.owlDevtoolsComponentsToggleBlacklist !== undefined) {
|
||||||
|
store.settings.componentsToggleBlacklist = new Set(
|
||||||
|
storage.owlDevtoolsComponentsToggleBlacklist
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function to handle and store a batch of events coming from the page
|
// Function to handle and store a batch of events coming from the page
|
||||||
@@ -871,10 +882,14 @@ function highlightChildren(component) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Expand the node given in entry and all of its children
|
// Expand the node given in entry and all of its children
|
||||||
function expandNodes(node) {
|
function expandNodes(node, blacklist = false) {
|
||||||
|
if (blacklist && store.settings.componentsToggleBlacklist.has(node.name)) {
|
||||||
|
node.toggled = false;
|
||||||
|
} else {
|
||||||
node.toggled = true;
|
node.toggled = true;
|
||||||
|
}
|
||||||
for (const child of node.children) {
|
for (const child of node.children) {
|
||||||
expandNodes(child);
|
expandNodes(child, blacklist);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,10 @@
|
|||||||
this.Fiber = window.__OWL_DEVTOOLS__.Fiber;
|
this.Fiber = window.__OWL_DEVTOOLS__.Fiber;
|
||||||
// Same but for RootFiber
|
// Same but for RootFiber
|
||||||
this.RootFiber = window.__OWL_DEVTOOLS__.RootFiber;
|
this.RootFiber = window.__OWL_DEVTOOLS__.RootFiber;
|
||||||
|
// This is for retrocompatibility purposes since new versions of owl should always expose toRaw and reactive
|
||||||
|
// in __OWL_DEVTOOLS__
|
||||||
|
this.toRaw = window.__OWL_DEVTOOLS__.toRaw ?? window.owl?.toRaw;
|
||||||
|
this.reactive = window.__OWL_DEVTOOLS__.reactive ?? window.owl?.reactive;
|
||||||
// Set to keep track of the fibers that are in the flush queue
|
// Set to keep track of the fibers that are in the flush queue
|
||||||
this.queuedFibers = new WeakSet();
|
this.queuedFibers = new WeakSet();
|
||||||
// Set to keep track of the HTML elements we added to the page
|
// Set to keep track of the HTML elements we added to the page
|
||||||
@@ -46,6 +50,9 @@
|
|||||||
this.appsPatched = false;
|
this.appsPatched = false;
|
||||||
this.destroyPatched = false;
|
this.destroyPatched = false;
|
||||||
this.patchAppsSetMethods();
|
this.patchAppsSetMethods();
|
||||||
|
if (this.apps.size > 0) {
|
||||||
|
this.patchAppMethods();
|
||||||
|
}
|
||||||
this.recordEvents = false;
|
this.recordEvents = false;
|
||||||
this.traceRenderings = false;
|
this.traceRenderings = false;
|
||||||
this.traceSubscriptions = false;
|
this.traceSubscriptions = false;
|
||||||
@@ -241,13 +248,22 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let app = this.apps.values().next().value;
|
let app = this.apps.values().next().value;
|
||||||
if (!app) {
|
const self = this;
|
||||||
return;
|
if (app.root) {
|
||||||
|
this.patchDestroyMethod(app.root);
|
||||||
|
} else {
|
||||||
|
const originalMount = app.constructor.prototype.mount;
|
||||||
|
app.constructor.prototype.mount = async function (...args) {
|
||||||
|
const result = await originalMount.call(this, ...args);
|
||||||
|
const root = this.root;
|
||||||
|
self.patchDestroyMethod(root);
|
||||||
|
app.constructor.prototype.mount = originalMount;
|
||||||
|
return result;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
const originalFlush = app.scheduler.constructor.prototype.flush;
|
const originalFlush = app.scheduler.constructor.prototype.flush;
|
||||||
let inFlush = false;
|
let inFlush = false;
|
||||||
let _render = false;
|
let _render = false;
|
||||||
const self = this;
|
|
||||||
app.scheduler.constructor.prototype.flush = function () {
|
app.scheduler.constructor.prototype.flush = function () {
|
||||||
// Used to know when a render is triggered inside the flush method or not
|
// Used to know when a render is triggered inside the flush method or not
|
||||||
inFlush = true;
|
inFlush = true;
|
||||||
@@ -388,7 +404,7 @@
|
|||||||
let targetToKeysToCallbacks;
|
let targetToKeysToCallbacks;
|
||||||
|
|
||||||
// Step 1: extract internal values from owl
|
// Step 1: extract internal values from owl
|
||||||
const obj = owl.reactive({}, () => {});
|
const obj = self.reactive({}, () => {});
|
||||||
let count = 0;
|
let count = 0;
|
||||||
WeakMap.prototype.get = function () {
|
WeakMap.prototype.get = function () {
|
||||||
count++;
|
count++;
|
||||||
@@ -716,7 +732,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (obj) {
|
if (obj) {
|
||||||
obj = owl.toRaw(obj);
|
obj = this.toRaw(obj);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return obj;
|
return obj;
|
||||||
@@ -824,7 +840,7 @@
|
|||||||
child.contentType = "undefined";
|
child.contentType = "undefined";
|
||||||
child.hasChildren = false;
|
child.hasChildren = false;
|
||||||
} else {
|
} else {
|
||||||
obj = owl.toRaw(obj);
|
obj = this.toRaw(obj);
|
||||||
switch (true) {
|
switch (true) {
|
||||||
case obj instanceof Map:
|
case obj instanceof Map:
|
||||||
child.contentType = "map";
|
child.contentType = "map";
|
||||||
@@ -1000,19 +1016,6 @@
|
|||||||
children.push(entries);
|
children.push(entries);
|
||||||
index++;
|
index++;
|
||||||
}
|
}
|
||||||
const size = this.serializeObjectChild(
|
|
||||||
obj,
|
|
||||||
{ type: "item", value: "size", childIndex: children.length },
|
|
||||||
depth,
|
|
||||||
objType,
|
|
||||||
path,
|
|
||||||
oldBranch.children[index],
|
|
||||||
oldTree
|
|
||||||
);
|
|
||||||
if (size) {
|
|
||||||
children.push(size);
|
|
||||||
index++;
|
|
||||||
}
|
|
||||||
Reflect.ownKeys(obj).forEach((key) => {
|
Reflect.ownKeys(obj).forEach((key) => {
|
||||||
const child = this.serializeObjectChild(
|
const child = this.serializeObjectChild(
|
||||||
obj,
|
obj,
|
||||||
@@ -1389,7 +1392,7 @@
|
|||||||
}
|
}
|
||||||
getter.hasChildren = false;
|
getter.hasChildren = false;
|
||||||
} else {
|
} else {
|
||||||
obj = owl.toRaw(obj);
|
obj = this.toRaw(obj);
|
||||||
switch (true) {
|
switch (true) {
|
||||||
case obj instanceof Map:
|
case obj instanceof Map:
|
||||||
getter.contentType = "map";
|
getter.contentType = "map";
|
||||||
@@ -1484,7 +1487,7 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (objectType === "subscription") {
|
if (objectType === "subscription") {
|
||||||
owl.reactive(obj)[key] = value;
|
this.reactive(obj)[key] = value;
|
||||||
} else {
|
} else {
|
||||||
obj[key] = value;
|
obj[key] = value;
|
||||||
if (objectType === "props" || objectType === "instance") {
|
if (objectType === "props" || objectType === "instance") {
|
||||||
|
|||||||
Reference in New Issue
Block a user