[MERGE] website: use new OWL components for page properties

Follows the merge of the 'website in backend' task at [1] and the new
OWL views structure at [2].

See sub-commits for details.

[1]: https://github.com/odoo/odoo/commit/31cc10b91dc7762e23b4bde9b945be0c4ce3fe3b
[2]: https://github.com/odoo/odoo/commit/48ef812a635f70571b395f82ffdb2969ce99da9e

task-2687506

closes odoo/odoo#97343

Signed-off-by: Quentin Smetz (qsm) <qsm@odoo.com>
This commit is contained in:
Odoo's Mergebot
2022-08-16 16:28:05 +02:00
committed by GitHub
10 changed files with 218 additions and 155 deletions
@@ -4,7 +4,6 @@ import { registry } from '@web/core/registry';
import { useService } from '@web/core/utils/hooks';
import core from 'web.core';
import { AceEditorAdapterComponent } from '../../components/ace_editor/ace_editor';
import { PagePropertiesDialogWrapper } from '../../components/dialog/page_properties';
import { WebsiteEditorComponent } from '../../components/editor/editor';
import { WebsiteTranslator } from '../../components/translator/translator';
import {OptimizeSEODialog} from '@website/components/dialog/seo';
@@ -307,7 +306,6 @@ WebsitePreview.components = {
BlockIframe,
WebsiteTranslator,
AceEditorAdapterComponent,
PagePropertiesDialogWrapper,
};
registry.category('actions').add('website_preview', WebsitePreview);
@@ -34,7 +34,6 @@
reloadIframe.bind="this.reloadIframe"
removeWelcomeMessage.bind="this.removeWelcomeMessage"/>
<AceEditorAdapterComponent t-if="websiteContext.showAceEditor"/>
<PagePropertiesDialogWrapper t-if="websiteContext.showPageProperties"/>
</div>
</t>
@@ -4,11 +4,10 @@ import {CheckBox} from '@web/core/checkbox/checkbox';
import {useService, useAutofocus} from "@web/core/utils/hooks";
import {useWowlService} from '@web/legacy/utils';
import {WebsiteDialog} from './dialog';
import {FormViewDialog} from 'web.view_dialogs';
import {standaloneAdapter} from 'web.OwlCompatibility';
import {qweb} from 'web.core';
import {FormViewDialog} from "@web/views/view_dialogs/form_view_dialog";
import {qweb, _t} from 'web.core';
const {Component, onWillStart, useState, xml, useRef, markup, onRendered} = owl;
const {Component, onWillStart, onRendered, useState, xml, useRef, markup} = owl;
export class PageDependencies extends Component {
setup() {
@@ -55,7 +54,7 @@ export class PageDependencies extends Component {
showDependencies() {
$(this.action.el).popover({
title: this.env._t('Dependencies'),
title: this.env._t("Dependencies"),
boundary: 'viewport',
placement: 'right',
trigger: 'focus',
@@ -101,9 +100,9 @@ export class DeletePageDialog extends Component {
await this.orm.unlink("website.page", [
this.props.pageId,
]);
this.website.goToWebsite();
this.website.goToWebsite({path: '/'});
this.props.close();
this.props.onClose();
this.props.onDelete();
}
}
DeletePageDialog.components = {
@@ -114,7 +113,7 @@ DeletePageDialog.components = {
DeletePageDialog.template = 'website.DeletePageDialog';
DeletePageDialog.props = {
pageId: Number,
onClose: Function,
onDelete: {type: Function, optional: true},
close: Function,
};
@@ -138,7 +137,7 @@ export class DuplicatePageDialog extends Component {
);
this.website.goToWebsite({path: res, edition: true});
}
this.props.onClose();
this.props.onDuplicate();
}
}
DuplicatePageDialog.components = {WebsiteDialog};
@@ -155,108 +154,108 @@ DuplicatePageDialog.template = xml`
</WebsiteDialog>
`;
DuplicatePageDialog.props = {
onClose: Function,
onDuplicate: {type: Function, optional: true},
close: Function,
pageId: Number,
};
export class PagePropertiesDialogWrapper extends Component {
export class PagePropertiesDialog extends FormViewDialog {
setup() {
try {
this.websiteService = useService('website');
this.dialogService = useService('dialog');
this.orm = useService('orm');
} catch {
// Use services in legacy environment.
this.websiteService = useWowlService('website');
this.dialogService = useWowlService('dialog');
this.orm = useWowlService('orm');
}
super.setup();
this.dialog = useService('dialog');
this.website = useService('website');
this.viewProps.resId = this.resId;
}
get resId() {
return this.props.resId || (this.website.currentWebsite && this.website.currentWebsite.metadata.mainObject.id);
}
clonePage() {
this.dialog.add(DuplicatePageDialog, {
pageId: this.resId,
onDuplicate: () => {
this.props.close();
this.props.onClose();
},
});
}
deletePage() {
this.dialog.add(DeletePageDialog, {
pageId: this.resId,
onDelete: () => {
this.props.close();
this.props.onClose();
},
});
}
}
PagePropertiesDialog.template = 'website.PagePropertiesDialog';
PagePropertiesDialog.props = {
...FormViewDialog.props,
onClose: {type: Function, optional: true},
resModel: {type: String, optional: true},
};
PagePropertiesDialog.defaultProps = {
...FormViewDialog.defaultProps,
resModel: 'website.page',
title: _t("Page Properties"),
context: {
form_view_ref: 'website.website_page_properties_view_form',
},
onClose: () => {},
};
// TODO remove this once the 'website.page' listView is adapted to OWL.
export class PagePropertiesDialogManager extends Component {
setup() {
this.dialog = useService('dialog');
onRendered(this.createDialog);
}
createDialog() {
if (this.props.mode === 'clone') {
this.dialogService.add(DuplicatePageDialog, {
pageId: this.pageId,
onClose: this.props.onClose,
});
} else if (this.props.mode === 'delete') {
this.dialogService.add(DeletePageDialog, {
pageId: this.pageId,
onClose: this.props.onClose,
});
} else {
const parent = standaloneAdapter({Component});
const formViewDialog = new FormViewDialog(parent, this.dialogOptions);
formViewDialog.buttons = [...formViewDialog.buttons, ...this.extraButtons];
formViewDialog.on('form_dialog_discarded', parent, () => this.websiteService.context.showPageProperties = false);
formViewDialog.open();
switch (this.props.mode) {
case 'clone':
this.dialog.add(DuplicatePageDialog, {
pageId: this.props.resId,
onDuplicate: this.props.onClose,
});
break;
case 'delete':
this.dialog.add(DeletePageDialog, {
pageId: this.props.resId,
onDelete: this.props.onClose,
});
break;
default:
this.dialog.add(PagePropertiesDialog, {
resId: this.props.resId,
onClose: this.props.onClose,
onRecordSaved: this.props.onRecordSaved,
});
break;
}
}
get pageId() {
return this.props.currentPage || (this.websiteService.currentWebsite && this.websiteService.currentWebsite.metadata.mainObject.id);
}
get dialogOptions() {
return {
res_model: "website.page",
res_id: this.pageId,
context: {
form_view_ref: 'website.website_page_properties_view_form'
},
title: this.env._t("Page Properties"),
size: 'medium',
on_saved: (record, changed) => {
if (changed) {
return this.orm.read("website.page", [this.pageId], ['url']).then(res => {
this.websiteService.goToWebsite({websiteId: record.data.website_id.res_id, path: res[0]['url']});
});
}
},
};
}
get extraButtons() {
const wrapper = this;
return [{
text: this.env._t("Duplicate Page"),
icon: 'fa-clone',
classes: 'btn-link ms-auto',
click: function () {
wrapper.dialogService.add(DuplicatePageDialog, {
pageId: wrapper.pageId,
onClose: this.close.bind(this),
});
},
},
{
text: this.env._t("Delete Page"),
icon: 'fa-trash',
classes: 'btn-link',
click: function () {
wrapper.dialogService.add(DeletePageDialog, {
pageId: wrapper.pageId,
onClose: this.close.bind(this),
});
},
}];
}
}
PagePropertiesDialogWrapper.template = xml``;
PagePropertiesDialogWrapper.props = {
PagePropertiesDialogManager.template = xml``;
PagePropertiesDialogManager.props = {
onClose: {
type: Function,
optional: true,
},
currentPage: {
type: Number,
optional: true,
},
mode: {
type: String,
optional: true,
},
resId: {
type: [Number, Boolean],
optional: true,
},
onRecordSaved: {
type: Function,
optional: true,
}
};
@@ -60,7 +60,7 @@
<p>Are you sure you want to delete this page ?</p>
<p class="text-warning">Don't forget to update all links referring to this page.</p>
<PageDependencies pageId="props.pageId" mode="'collapse'"/>
<CheckBox onChange.bind="onConfirmCheckboxChange">
<CheckBox value="state.confirm" onChange.bind="onConfirmCheckboxChange">
<p class="text-warning">I am sure about this.</p>
</CheckBox>
@@ -75,4 +75,11 @@
</WebsiteDialog>
</t>
<t t-name="website.PagePropertiesDialog" t-inherit="web.FormViewDialog" owl="1">
<xpath expr="//t[@t-set-slot='footer']" position="inside">
<button class="btn btn-link order-last ms-auto" t-on-click="clonePage"><i class="fa fa-fw fa-clone"/>Duplicate Page</button>
<button class="btn btn-link order-last" t-on-click="deletePage"><i class="fa fa-fw fa-trash"/>Delete Page</button>
</xpath>
</t>
</templates>
@@ -1,24 +1,32 @@
/** @odoo-module **/
import {PageDependencies} from '@website/components/dialog/page_properties';
import {standardFieldProps} from '@web/views/fields/standard_field_props';
import {useInputField} from '@web/views/fields/input_field_hook';
import {useService} from '@web/core/utils/hooks';
import {Switch} from '@website/components/switch/switch';
import AbstractFieldOwl from 'web.AbstractFieldOwl';
import fieldRegistry from 'web.field_registry_owl';
import {registry} from '@web/core/registry';
import {formatChar} from '@web/views/fields/formatters';
const {useState, onWillStart} = owl;
const {Component, useState, onWillStart} = owl;
class FieldPageUrl extends AbstractFieldOwl {
/**
* Displays website page dependencies and URL redirect options when the page URL
* is updated.
*/
class PageUrlField extends Component {
setup() {
super.setup();
this.state = useState({
redirect_old_url: false,
url: this.value,
url: this.props.value,
redirect_type: '301',
});
useInputField({getValue: () => this.props.value.url || this.props.value});
this.serverUrl = window.location.origin;
this.pageUrl = this.value;
this.pageUrl = this.props.value;
}
get enableRedirect() {
@@ -27,41 +35,55 @@ class FieldPageUrl extends AbstractFieldOwl {
onChangeRedirectOldUrl(value) {
this.state.redirect_old_url = value;
this.updateValues();
}
/**
* @override
*/
commitChanges() {
if (this.enableRedirect) {
this._setValue(this.state);
}
return super.commitChanges();
updateValues() {
// HACK: update redirect data from the URL field.
// TODO: remove this and use a transient model with redirect fields.
this.props.update(this.state);
}
}
FieldPageUrl.components = {Switch, PageDependencies};
FieldPageUrl.supportedFieldTypes = ['char'];
FieldPageUrl.template = 'website.FieldPageUrl';
PageUrlField.components = {Switch, PageDependencies};
PageUrlField.template = 'website.PageUrlField';
PageUrlField.props = {
...standardFieldProps,
placeholder: {type: String, optional: true},
};
PageUrlField.extractProps = ({attrs}) => {
return {
placeholder: attrs.placeholder,
};
};
PageUrlField.supportedTypes = ['char'];
class FieldPageName extends AbstractFieldOwl {
registry.category("fields").add("page_url", PageUrlField);
/**
* Used to display key dependencies and warn user about changing a special file
* (website.page & supported mimetype) name, since the key will be updated too.
*/
class PageNameField extends Component {
setup() {
super.setup();
this.orm = useService('orm');
useInputField({getValue: () => this.props.value || ''});
this.state = useState({
name: this.value,
name: this.props.value,
});
this.pageName = this.value;
this.pageName = this.props.value;
this.supportedMimetypes = {};
onWillStart(() => this.onWillStart());
}
get formattedPageName() {
return formatChar(this.props.value);
}
async onWillStart() {
this.supportedMimetypes = await this.rpc({
model: 'website',
method: 'guess_mimetype',
});
this.supportedMimetypes = await this.orm.call('website', 'guess_mimetype', []);
}
get warnAboutCall() {
@@ -76,20 +98,21 @@ class FieldPageName extends AbstractFieldOwl {
const ext = '.' + this.pageName.split('.').pop();
return ext in this.supportedMimetypes && ext !== '.html';
}
/**
* @override
*/
commitChanges() {
if (this.nameChanged) {
this._setValue(this.state.name);
}
return super.commitChanges();
}
}
FieldPageName.components = {PageDependencies};
FieldPageName.supportedFieldTypes = ['char'];
FieldPageName.template = 'website.FieldPageName';
PageNameField.components = {PageDependencies};
PageNameField.template = 'website.PageNameField';
PageNameField.props = {
...standardFieldProps,
placeholder: {type: String, optional: true},
};
PageNameField.extractProps = ({attrs}) => {
return {
placeholder: attrs.placeholder,
};
};
PageNameField.supportedTypes = ['char'];
registry.category("fields").add("page_name", PageNameField);
/**
* Displays 'char' field's value prefixed by a FA icon.
@@ -111,6 +134,4 @@ class FieldFaPrefix extends AbstractFieldOwl {
FieldFaPrefix.supportedFieldTypes = ['char'];
FieldFaPrefix.template = 'website.FieldFaPrefix';
fieldRegistry.add('page_url', FieldPageUrl);
fieldRegistry.add('page_name', FieldPageName);
fieldRegistry.add('fa_prefix', FieldFaPrefix);
@@ -1,35 +1,65 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates>
<t t-name="website.FieldPageUrl" owl="1">
<div>
<t t-name="website.PageUrlField" owl="1">
<t t-if="props.readonly">
<a class="o_form_uri o_text_overflow" t-att-href="props.value" t-esc="props.value || ''"/>
</t>
<t t-else="">
<div class="d-flex">
<div class="input-group-text rounded-start text-lowercase border-0" t-esc="serverUrl.length > 30 ? serverUrl.slice(0,14) + '..' + serverUrl.slice(-14) : serverUrl"/>
<input t-model="state.url" type="text" class="o_input"/>
<div
class="input-group-text rounded-start text-lowercase border-0"
t-esc="serverUrl.length > 30 ? serverUrl.slice(0,14) + '..' + serverUrl.slice(-14) : serverUrl"
/>
<input
class="o_input"
type="text"
t-att-placeholder="props.placeholder"
t-att-required="props.required"
t-att-id="props.id"
t-model="state.url"
t-ref="input"
/>
</div>
<div t-if="enableRedirect">
<div class="d-flex mt-4">
<Switch extraClasses="'me-auto'" label="'Redirect Old URL'" value="state.redirect_old_url" onChange="(value) => this.onChangeRedirectOldUrl(value)"/>
<PageDependencies pageId="resId" mode="'popover'"/>
<Switch
extraClasses="'me-auto'"
label="'Redirect Old URL'"
value="state.redirect_old_url"
onChange="(value) => this.onChangeRedirectOldUrl(value)"
/>
<PageDependencies pageId="props.record.resId" mode="'popover'"/>
</div>
<div t-if="state.redirect_old_url" class="d-flex mt-4">
<label class="my-0 me-4 fw-bold" for="redirect_type">Type</label>
<select class="o_input w-50" id="redirect_type" t-model="state.redirect_type">
<select class="o_input w-50" id="redirect_type" t-model="state.redirect_type" t-on-change="updateValues">
<option value="301">301 Moved permanently</option>
<option value="302">302 Moved temporarily</option>
</select>
</div>
</div>
</div>
</t>
</t>
<t t-name="website.FieldPageName" owl="1">
<div>
<input t-model="state.name" type="text" class="o_input"/>
<t t-name="website.PageNameField" owl="1">
<t t-if="props.readonly">
<span t-esc="formattedPageName"/>
</t>
<t t-else="">
<input
class="o_input"
type="text"
t-att-placeholder="props.placeholder"
t-att-required="props.required"
t-att-id="props.id"
t-model="state.name"
t-ref="input"
/>
<div t-if="warnAboutCall" class="mt-4">
<PageDependencies pageId="resId" mode="'popover'" type="'key'"/>
<PageDependencies pageId="props.record.resId" mode="'popover'" type="'key'"/>
</div>
</div>
</t>
</t>
<t t-name="website.FieldFaPrefix" owl="1">
@@ -6,6 +6,7 @@ import { registry } from "@web/core/registry";
import { patch } from 'web.utils';
import { EditMenuDialog } from '@website/components/dialog/edit_menu';
import { OptimizeSEODialog } from '@website/components/dialog/seo';
import {PagePropertiesDialog} from '@website/components/dialog/page_properties';
const websiteSystrayRegistry = registry.category('website_systray');
const { useState } = owl;
@@ -13,6 +14,7 @@ const { useState } = owl;
patch(NavBar.prototype, 'website_navbar', {
setup() {
this._super();
this.orm = useService('orm');
this.websiteService = useService('website');
this.websiteContext = useState(this.websiteService.context);
@@ -41,8 +43,15 @@ patch(NavBar.prototype, 'website_navbar', {
isDisplayed: () => this.canShowAceEditor(),
},
'website.menu_page_properties': {
openWidget: () => this.websiteContext.showPageProperties = true,
Component: PagePropertiesDialog,
isDisplayed: () => this.canShowPageProperties(),
getProps: () => ({
onRecordSaved: (record) => {
return this.orm.read('website.page', [record.resId], ['url']).then(res => {
this.websiteService.goToWebsite({websiteId: record.data.website_id[0], path: res[0]['url']});
});
},
})
},
};
},
@@ -4,7 +4,7 @@ import ListController from 'web.ListController';
import viewRegistry from 'web.view_registry';
import ListView from 'web.ListView';
import {ComponentWrapper} from 'web.OwlCompatibility';
import {PagePropertiesDialogWrapper} from '@website/components/dialog/page_properties';
import {PagePropertiesDialogManager} from '@website/components/dialog/page_properties';
import {qweb} from 'web.core';
const WebsitePageListController = ListController.extend({
@@ -86,9 +86,10 @@ const WebsitePageListController = ListController.extend({
* @private
*/
async _addDialog(record, mode = '') {
this._pagePropertiesDialog = new ComponentWrapper(this, PagePropertiesDialogWrapper, {
currentPage: record.data.id,
this._pagePropertiesDialog = new ComponentWrapper(this, PagePropertiesDialogManager, {
resId: record.data.id,
onClose: this._onCloseDialog.bind(this),
onRecordSaved: this.reload.bind(this),
mode: mode,
});
await this._pagePropertiesDialog.mount(this.el);
@@ -46,7 +46,6 @@ export const websiteService = {
isPublicRootReady: false,
snippetsLoaded: false,
isMobile: false,
showPageProperties: false,
});
const bus = new EventBus();
+1 -1
View File
@@ -136,7 +136,7 @@
<notebook>
<page string="Name">
<group class="mt-4">
<field name="name" string="Page Name" widget="page_name"/>
<field name="name" string="Page Name" widget="page_name" placeholder="e.g. Home Page"/>
</group>
<group>
<field name="website_id" invisible="1"/>