From 485bcb82bf0cf2104a2c14de0efc4570848239bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexandre=20K=C3=BChn?= Date: Tue, 26 Sep 2023 16:49:42 +0200 Subject: [PATCH] [REF] mail: make store service a model This simplifies managing relations on the store, and gives incentive to define them there instead of another prop that is a record, e.g. `discuss`. Part-of: odoo/odoo#136539 --- .../static/src/core/common/store_service.js | 176 +++++++++--------- 1 file changed, 88 insertions(+), 88 deletions(-) diff --git a/addons/mail/static/src/core/common/store_service.js b/addons/mail/static/src/core/common/store_service.js index e6be598141e..f5c13443875 100644 --- a/addons/mail/static/src/core/common/store_service.js +++ b/addons/mail/static/src/core/common/store_service.js @@ -8,7 +8,12 @@ import { registry } from "@web/core/registry"; import { debounce } from "@web/core/utils/timing"; import { modelRegistry, Record, RecordInverses, RecordList } from "./record"; -export class Store { +export class Store extends Record { + /** @returns {import("models").Store} */ + static insert() { + return this.get() ?? this.new(); + } + /** @type {typeof import("@mail/core/web/activity_model").Activity} */ Activity; /** @type {typeof import("@mail/core/common/attachment_model").Attachment} */ @@ -44,6 +49,56 @@ export class Store { /** @type {typeof import("@mail/core/common/thread_model").Thread} */ Thread; + lastChannelSubscription = ""; + /** + * This is the current logged partner + * + * @type {import("models").Persona} + */ + user = null; + /** + * This is the current logged guest + * + * @type {import("models").Persona} + */ + guest = null; + /** + * The last id of bus notification at the time for fetch init_messaging. + * When receiving a notification: + * - if id greater than this value: the notification is newer than init_messaging state. + * - if same id or lower: the notification is older than init_messaging state. + * This is useful to determine whether we should increment or decrement a counter based + * on init_messaging state. + */ + initBusId = 0; + /** + * Indicates whether the current user is using the application through the + * public page. + */ + inPublicPage = false; + companyName = ""; + /** @type {import("models").Persona} */ + odoobot = null; + odoobotOnboarding; + users = {}; + internalUserGroupId = null; + registeredImStatusPartners = null; + hasLinkPreviewFeature = true; + // messaging menu + menu = { counter: 0 }; + discuss = Record.one("DiscussApp"); + activityCounter = 0; + isMessagingReady = false; + + get self() { + return this.guest ?? this.user; + } + + setup() { + super.setup(); + this.updateBusSubscription = debounce(this.updateBusSubscription, 0); // Wait for thread fully inserted. + } + /** * @param {string} localId * @returns {Record} @@ -56,22 +111,6 @@ export class Store { return this[modelName].records[localId]; } - /** - * @param {import("@web/env").OdooEnv} env - */ - constructor(env) { - this.setup(env); - this.lastChannelSubscription = ""; - this.updateBusSubscription = debounce(this.updateBusSubscription, 0); // Wait for thread fully inserted. - } - - /** - * @param {import("@web/env").OdooEnv} env - */ - setup(env) { - this.env = env; - } - updateBusSubscription() { const channelIds = []; const ids = Object.keys(this.Thread.records).sort(); // Ensure channels processed in same order. @@ -87,64 +126,8 @@ export class Store { } this.lastChannelSubscription = channels; } - - get self() { - return this.guest ?? this.user; - } - - // base data - - /** - * This is the current logged partner - * - * @type {import("models").Persona} - */ - user = null; - /** - * This is the current logged guest - * - * @type {import("models").Persona} - */ - guest = null; - - /** - * The last id of bus notification at the time for fetch init_messaging. - * When receiving a notification: - * - if id greater than this value: the notification is newer than init_messaging state. - * - if same id or lower: the notification is older than init_messaging state. - * This is useful to determine whether we should increment or decrement a counter based - * on init_messaging state. - */ - initBusId = 0; - - /** - * Indicates whether the current user is using the application through the - * public page. - */ - inPublicPage = false; - - companyName = ""; - - /** @type {import("models").Persona} */ - odoobot = null; - odoobotOnboarding; - users = {}; - internalUserGroupId = null; - registeredImStatusPartners = null; - - hasLinkPreviewFeature = true; - - // messaging menu - menu = { - counter: 0, - }; - - discuss = Record.one("DiscussApp"); - - activityCounter = 0; - - isMessagingReady = false; } +Store.register(); export const storeService = { dependencies: ["bus_service", "ui"], @@ -153,11 +136,19 @@ export const storeService = { * @param {Partial} services */ start(env, services) { - const res = reactive(new Store(env, services)); + const res = { + // fake store for now, until it becomes a model + /** @type {Store} */ + store: { + env, + get: (...args) => Store.prototype.get.call(this, ...args), + }, + }; + const Models = {}; for (const [name, _Model] of modelRegistry.getEntries()) { /** @type {typeof Record} */ const Model = _Model; - if (res[name]) { + if (res.store[name]) { throw new Error( `There must be no duplicated Model Names (duplicate found: ${name})` ); @@ -165,7 +156,7 @@ export const storeService = { // classes cannot be made reactive because they are functions and they are not supported. // work-around: make an object whose prototype is the class, so that static props become // instance props. - const entry = Object.assign(Object.create(Model), { env, store: res }); + const entry = Object.assign(Object.create(Model), { env, store: res.store }); // Produce another class with changed prototype, so that there are automatic get/set on relational fields let detecting = true; const Class = { @@ -187,7 +178,7 @@ export const storeService = { } if (this[name] === Record.many()) { newVal = new RecordList(); - newVal.__store__ = res; + newVal.__store__ = res.store; newVal.name = name; newVal.owner = this; } @@ -203,7 +194,7 @@ export const storeService = { if (l1 instanceof RecordList) { return l1; } - return res.get(l1); + return res.store.get(l1); } return Reflect.get(target, name, receiver); }, @@ -211,7 +202,7 @@ export const storeService = { if (name !== "__rels__" && target.__rels__.has(name)) { const r1 = target; const l1 = r1.__rels__.get(name); - const r2 = res.get(l1); + const r2 = res.store.get(l1); if (r2) { r2.__invs__.delete(r1.localId, name); } @@ -242,7 +233,7 @@ export const storeService = { } else { const r1 = receiver; const l1 = r1.__rels__.get(name); - const r2 = res.get(l1); + const r2 = res.store.get(l1); /** @type {Record} */ const r3 = val; if (r2 && r2.notEq(r3)) { @@ -269,7 +260,8 @@ export const storeService = { }[Model.name]; entry.Class = Class; entry.records = JSON.parse(JSON.stringify(Model.records)); - res[name] = entry; + Models[name] = entry; + res.store[name] = entry; // Detect relational fields with a dummy record and setup getter/setters on them const obj = new Model(); detecting = false; @@ -280,17 +272,25 @@ export const storeService = { Class.__rels__.add(name); } } - res.discuss = res.DiscussApp.insert(); - res.discuss.activeTab = env.services.ui.isSmall ? "mailbox" : "all"; - onChange(res.Thread, "records", () => res.updateBusSubscription()); + // Make true store (as a model) + res.store = reactive(res.store.Store.insert()); + res.store.env = env; + for (const Model of Object.values(Models)) { + Model.store = res.store; + res.store[Model.name] = Model; + } + const store = res.store; + store.discuss = store.DiscussApp.insert(); + store.discuss.activeTab = env.services.ui.isSmall ? "mailbox" : "all"; + onChange(store.Thread, "records", () => store.updateBusSubscription()); services.ui.bus.addEventListener("resize", () => { if (!services.ui.isSmall) { - res.discuss.activeTab = "all"; + store.discuss.activeTab = "all"; } else { - res.discuss.activeTab = res.discuss.thread?.type ?? "all"; + store.discuss.activeTab = store.discuss.thread?.type ?? "all"; } }); - return res; + return store; }, };