[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
This commit is contained in:
@@ -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<import("services").Services>} 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;
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user