304 lines
11 KiB
JavaScript
304 lines
11 KiB
JavaScript
/** @odoo-module **/
|
|
|
|
import { _t } from "@web/core/l10n/translation";
|
|
import { rpc } from "@web/core/network/rpc";
|
|
import { registry } from '@web/core/registry';
|
|
import { user } from "@web/core/user";
|
|
import { useService } from '@web/core/utils/hooks';
|
|
import { redirect } from "@web/core/utils/urls";
|
|
import { WebsiteDialog } from "@website/components/dialog/dialog";
|
|
import { AddPageDialog } from "@website/components/dialog/add_page_dialog";
|
|
import { useHotkey } from "@web/core/hotkeys/hotkey_hook";
|
|
import { sprintf } from '@web/core/utils/strings';
|
|
import { Component, xml, useState, onWillStart } from "@odoo/owl";
|
|
|
|
export const MODULE_STATUS = {
|
|
NOT_INSTALLED: 'NOT_INSTALLED',
|
|
INSTALLING: 'INSTALLING',
|
|
FAILED_TO_INSTALL: 'FAILED_TO_INSTALL',
|
|
INSTALLED: 'INSTALLED',
|
|
};
|
|
|
|
class NewContentElement extends Component {
|
|
static template = "website.NewContentElement";
|
|
static props = {
|
|
name: { type: String, optional: true },
|
|
title: String,
|
|
onClick: Function,
|
|
status: { type: String, optional: true },
|
|
moduleXmlId: { type: String, optional: true },
|
|
slots: Object,
|
|
};
|
|
static defaultProps = {
|
|
status: MODULE_STATUS.INSTALLED,
|
|
};
|
|
|
|
setup() {
|
|
this.MODULE_STATUS = MODULE_STATUS;
|
|
}
|
|
|
|
onClick(ev) {
|
|
ev.preventDefault();
|
|
ev.stopPropagation();
|
|
this.props.onClick();
|
|
}
|
|
}
|
|
|
|
class InstallModuleDialog extends Component {
|
|
static components = { WebsiteDialog };
|
|
static template = "website.InstallModuleDialog";
|
|
static props = {
|
|
title: String,
|
|
installationText: String,
|
|
installModule: Function,
|
|
close: Function,
|
|
};
|
|
|
|
setup() {
|
|
this.installButton = _t("Install");
|
|
}
|
|
|
|
onClickInstall() {
|
|
this.props.close();
|
|
this.props.installModule();
|
|
}
|
|
}
|
|
|
|
export class NewContentModal extends Component {
|
|
static template = "website.NewContentModal";
|
|
static components = { NewContentElement };
|
|
static props = {};
|
|
|
|
setup() {
|
|
this.orm = useService('orm');
|
|
this.dialogs = useService('dialog');
|
|
this.website = useService('website');
|
|
this.action = useService('action');
|
|
this.isSystem = user.isSystem;
|
|
|
|
this.newContentText = {
|
|
failed: _t('Failed to install "%s"'),
|
|
installInProgress: _t("The installation of an App is already in progress."),
|
|
installNeeded: _t('Do you want to install the "%s" App?'),
|
|
installPleaseWait: _t('Installing "%s"'),
|
|
};
|
|
|
|
this.state = useState({
|
|
newContentElements: [
|
|
{
|
|
moduleName: 'website_blog',
|
|
moduleXmlId: 'base.module_website_blog',
|
|
status: MODULE_STATUS.NOT_INSTALLED,
|
|
icon: xml`<i class="fa fa-newspaper-o"/>`,
|
|
title: _t('Blog Post'),
|
|
},
|
|
{
|
|
moduleName: 'website_event',
|
|
moduleXmlId: 'base.module_website_event',
|
|
status: MODULE_STATUS.NOT_INSTALLED,
|
|
icon: xml`<i class="fa fa-ticket"/>`,
|
|
title: _t('Event'),
|
|
},
|
|
{
|
|
moduleName: 'website_forum',
|
|
moduleXmlId: 'base.module_website_forum',
|
|
status: MODULE_STATUS.NOT_INSTALLED,
|
|
icon: xml`<i class="fa fa-comment"/>`,
|
|
redirectUrl: '/forum',
|
|
title: _t('Forum'),
|
|
},
|
|
{
|
|
moduleName: 'website_hr_recruitment',
|
|
moduleXmlId: 'base.module_website_hr_recruitment',
|
|
status: MODULE_STATUS.NOT_INSTALLED,
|
|
icon: xml`<i class="fa fa-briefcase"/>`,
|
|
title: _t('Job Position'),
|
|
},
|
|
{
|
|
moduleName: 'website_sale',
|
|
moduleXmlId: 'base.module_website_sale',
|
|
status: MODULE_STATUS.NOT_INSTALLED,
|
|
icon: xml`<i class="fa fa-shopping-cart"/>`,
|
|
title: _t('Product'),
|
|
},
|
|
{
|
|
moduleName: 'website_slides',
|
|
moduleXmlId: 'base.module_website_slides',
|
|
status: MODULE_STATUS.NOT_INSTALLED,
|
|
icon: xml`<i class="fa module_icon" style="background-image: url('/website/static/src/img/apps_thumbs/website_slide.svg');background-repeat: no-repeat; background-position: center;"/>`,
|
|
title: _t('Course'),
|
|
},
|
|
{
|
|
moduleName: 'website_livechat',
|
|
moduleXmlId: 'base.module_website_livechat',
|
|
status: MODULE_STATUS.NOT_INSTALLED,
|
|
icon: xml`<i class="fa fa-comments"/>`,
|
|
title: _t('Livechat Widget'),
|
|
redirectUrl: '/livechat'
|
|
},
|
|
]
|
|
});
|
|
|
|
this.websiteContext = useState(this.website.context);
|
|
useHotkey('escape', () => {
|
|
if (this.websiteContext.showNewContentModal) {
|
|
this.websiteContext.showNewContentModal = false;
|
|
}
|
|
});
|
|
|
|
onWillStart(this.onWillStart.bind(this));
|
|
}
|
|
|
|
async onWillStart() {
|
|
this.isDesigner = await user.hasGroup('website.group_website_designer');
|
|
this.canInstall = await user.isAdmin;
|
|
if (this.canInstall) {
|
|
const moduleNames = this.state.newContentElements.filter(({status}) => status === MODULE_STATUS.NOT_INSTALLED).map(({moduleName}) => moduleName);
|
|
this.modulesInfo = {};
|
|
for (const record of await this.orm.searchRead(
|
|
"ir.module.module",
|
|
[['name', 'in', moduleNames]],
|
|
["id", "name", "shortdesc"],
|
|
)) {
|
|
this.modulesInfo[record.name] = {id: record.id, name: record.shortdesc};
|
|
}
|
|
}
|
|
const modelsToCheck = [];
|
|
const elementsToUpdate = {};
|
|
for (const element of this.state.newContentElements) {
|
|
if (element.model) {
|
|
modelsToCheck.push(element.model);
|
|
elementsToUpdate[element.model] = element;
|
|
}
|
|
}
|
|
const accesses = await rpc("/website/check_new_content_access_rights", {
|
|
models: modelsToCheck,
|
|
});
|
|
for (const [model, access] of Object.entries(accesses)) {
|
|
elementsToUpdate[model].isDisplayed = access;
|
|
}
|
|
}
|
|
|
|
get sortedNewContentElements() {
|
|
return this.state.newContentElements.filter(({status}) => status !== MODULE_STATUS.NOT_INSTALLED).concat(this.state.newContentElements.filter(({status}) => status === MODULE_STATUS.NOT_INSTALLED));
|
|
}
|
|
|
|
createNewPage() {
|
|
this.dialogs.add(AddPageDialog, {
|
|
onAddPage: () => this.websiteContext.showNewContentModal = false,
|
|
websiteId: this.website.currentWebsite.id,
|
|
});
|
|
}
|
|
|
|
async installModule(id, redirectUrl) {
|
|
await this.orm.silent.call(
|
|
'ir.module.module',
|
|
'button_immediate_install',
|
|
[id],
|
|
);
|
|
if (redirectUrl) {
|
|
this.website.prepareOutLoader();
|
|
window.location.replace(redirectUrl);
|
|
} else {
|
|
const { id, metadata: { path, viewXmlid } } = this.website.currentWebsite;
|
|
const url = new URL(path);
|
|
if (viewXmlid === 'website.page_404') {
|
|
url.pathname = '';
|
|
}
|
|
// A reload is needed after installing a new module, to instantiate
|
|
// a NewContentModal with patches from the installed module.
|
|
this.website.prepareOutLoader();
|
|
redirect(`/odoo/action-website.website_preview?website_id=${id}&path=${encodeURIComponent(url.toString())}&display_new_content=true`);
|
|
}
|
|
}
|
|
|
|
onClickNewContent(element) {
|
|
if (element.createNewContent) {
|
|
return element.createNewContent();
|
|
}
|
|
|
|
const {id, name} = this.modulesInfo[element.moduleName];
|
|
const dialogProps = {
|
|
title: element.title,
|
|
installationText: sprintf(this.newContentText.installNeeded, name),
|
|
installModule: async () => {
|
|
// Update the NewContentElement with installing icon and text.
|
|
this.state.newContentElements = this.state.newContentElements.map(el => {
|
|
if (el.moduleXmlId === element.moduleXmlId) {
|
|
el.status = MODULE_STATUS.INSTALLING;
|
|
el.icon = xml`<i class="fa fa-spin fa-circle-o-notch"/>`;
|
|
el.title = sprintf(this.newContentText.installPleaseWait, name);
|
|
}
|
|
return el;
|
|
});
|
|
this.website.showLoader({ title: _t("Building your %s", name) });
|
|
try {
|
|
await this.installModule(id, element.redirectUrl);
|
|
} catch (error) {
|
|
this.website.hideLoader();
|
|
// Update the NewContentElement with failure icon and text.
|
|
this.state.newContentElements = this.state.newContentElements.map(el => {
|
|
if (el.moduleXmlId === element.moduleXmlId) {
|
|
el.status = MODULE_STATUS.FAILED_TO_INSTALL;
|
|
el.icon = xml`<i class="fa fa-exclamation-triangle"/>`;
|
|
el.title = sprintf(this.newContentText.failed, name);
|
|
}
|
|
return el;
|
|
});
|
|
console.error(error);
|
|
}
|
|
},
|
|
};
|
|
this.dialogs.add(InstallModuleDialog, dialogProps);
|
|
}
|
|
|
|
/**
|
|
* This method registers the action to perform when a new content is
|
|
* saved. The path must be computed once the record is saved, to
|
|
* perform the 'ir.act_window_close' action, which will be used when
|
|
* the dialog is closed to go to the correct website page.
|
|
*/
|
|
async onAddContent(action, edition = false, context = null) {
|
|
this.action.doAction(action, {
|
|
additionalContext: (context) ? context: {},
|
|
onClose: (infos) => {
|
|
if (infos) {
|
|
this.website.goToWebsite({ path: infos.path, edition: edition });
|
|
}
|
|
},
|
|
props: {
|
|
onSave: (record, params) => {
|
|
if (record.resId) {
|
|
const path = params.computePath();
|
|
this.action.doAction({
|
|
type: "ir.actions.act_window_close",
|
|
infos: { path }
|
|
});
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
class NewContentSystray extends Component {
|
|
static template = "website.NewContentSystray";
|
|
static components = { NewContentModal };
|
|
static props = {};
|
|
|
|
setup() {
|
|
this.website = useService('website');
|
|
this.websiteContext = useState(this.website.context);
|
|
}
|
|
|
|
onClick() {
|
|
this.websiteContext.showNewContentModal = !this.websiteContext.showNewContentModal;
|
|
}
|
|
}
|
|
|
|
export const systrayItem = {
|
|
Component: NewContentSystray,
|
|
};
|
|
|
|
registry.category("website_systray").add("NewContent", systrayItem, { sequence: 9 });
|