[IMP] website: convert configurator to client action
Before this commit, the configurator was its own frontend application. With the website edition UI moved to the backend, it makes also sense to move the configurator to the backend, as a client action, so that it is easy to trigger it after installing a website (or the website module), and returning to the website_preview client action after having completed it. This commit adds a new color_palettes.scss file, added in the assets_backend, that will print the values of the color palettes (so that some js can handle it). For now, it is needed for the configurator and the editor. It will probably need to be reviewed later to not put everything in backend assets. See merge commit for more information. task-2687506
This commit is contained in:
@@ -33,8 +33,6 @@
|
||||
'data/digest_data.xml',
|
||||
'views/assets.xml',
|
||||
'views/website_templates.xml',
|
||||
'views/website_navbar_templates.xml',
|
||||
'views/website_configurator_templates.xml',
|
||||
'views/snippets/snippets.xml',
|
||||
'views/snippets/s_title.xml',
|
||||
'views/snippets/s_cover.xml',
|
||||
@@ -156,6 +154,7 @@
|
||||
'website/static/tests/tours/**/*',
|
||||
],
|
||||
'web.assets_backend': [
|
||||
'website/static/src/scss/color_palettes.scss',
|
||||
'website/static/src/scss/view_hierarchy.scss',
|
||||
'website/static/src/scss/website.backend.scss',
|
||||
'website/static/src/scss/website_visitor_views.scss',
|
||||
@@ -291,15 +290,6 @@
|
||||
'http://test.external.link/javascript2.js',
|
||||
'http://test.external.link/style2.css',
|
||||
],
|
||||
'website.website_configurator_assets_js': [
|
||||
'/website/static/src/client_actions/configurator/configurator.js',
|
||||
],
|
||||
'website.website_configurator_assets_scss': [
|
||||
('include', 'web.assets_frontend'),
|
||||
'/website/static/src/client_actions/configurator/configurator.scss',
|
||||
'/website/static/src/scss/website.theme_install.scss',
|
||||
'/website/static/src/scss/website.wysiwyg.scss'
|
||||
]
|
||||
},
|
||||
'license': 'LGPL-3',
|
||||
}
|
||||
|
||||
@@ -255,7 +255,10 @@ class Website(Home):
|
||||
return request.redirect('/')
|
||||
if request.env.lang != website_id.default_lang_id.code:
|
||||
return request.redirect('/%s%s' % (website_id.default_lang_id.url_code, request.httprequest.path))
|
||||
return request.render('website.website_configurator')
|
||||
action_url = '/web#action=website.website_configurator&menu_id=%s' % request.env.ref('website.menu_website_configuration').id
|
||||
if step > 1:
|
||||
action_url += '&step=' + str(step)
|
||||
return request.redirect(action_url)
|
||||
|
||||
@http.route(['/website/social/<string:social>'], type='http', auth="public", website=True, sitemap=False)
|
||||
def social(self, social, **kwargs):
|
||||
|
||||
@@ -382,8 +382,6 @@ class IrModuleModule(models.Model):
|
||||
result = active_todo.action_launch()
|
||||
else:
|
||||
result = website.button_go_website(mode_edit=True)
|
||||
if result.get('url') and 'enable_editor' in result['url']:
|
||||
result['url'] = result['url'].replace('enable_editor', 'with_loader=1&enable_editor')
|
||||
return result
|
||||
|
||||
def button_remove_theme(self):
|
||||
|
||||
@@ -319,7 +319,7 @@ class Website(models.Model):
|
||||
def configurator_init(self):
|
||||
r = dict()
|
||||
company = self.get_current_website().company_id
|
||||
configurator_features = self.env['website.configurator.feature'].with_context(lang=self.get_current_website().default_lang_id.code).search([])
|
||||
configurator_features = self.env['website.configurator.feature'].search([])
|
||||
r['features'] = [{
|
||||
'id': feature.id,
|
||||
'name': feature.name,
|
||||
@@ -333,7 +333,7 @@ class Website(models.Model):
|
||||
if company.logo and company.logo != company._get_logo():
|
||||
r['logo'] = company.logo.decode('utf-8')
|
||||
try:
|
||||
result = self._website_api_rpc('/api/website/1/configurator/industries', {'lang': self.get_current_website().default_lang_id.code})
|
||||
result = self._website_api_rpc('/api/website/1/configurator/industries', {'lang': self.env.context.get('lang')})
|
||||
r['industries'] = result['industries']
|
||||
except AccessError as e:
|
||||
logger.warning(e.args[0])
|
||||
@@ -591,7 +591,7 @@ class Website(models.Model):
|
||||
|
||||
images = custom_resources.get('images', {})
|
||||
set_images(images)
|
||||
return url
|
||||
return {'url': url, 'website_id': website.id}
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# Page Management
|
||||
|
||||
@@ -2,14 +2,13 @@
|
||||
|
||||
import utils from 'web.utils';
|
||||
import weUtils from 'web_editor.utils';
|
||||
import session from 'web.session';
|
||||
import {ColorpickerWidget} from 'web.Colorpicker';
|
||||
import {_t, _lt} from 'web.core';
|
||||
import {_t, _lt, qweb} from 'web.core';
|
||||
import {svgToPNG} from 'website.utils';
|
||||
import {useService} from "@web/core/utils/hooks";
|
||||
import {renderToString} from "@web/core/utils/render";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { registry } from "@web/core/registry";
|
||||
|
||||
const { App, Component, onMounted, reactive, useEnv, useRef, useState, whenReady } = owl;
|
||||
const { Component, onMounted, reactive, useEnv, useRef, useState, useSubEnv, onWillStart } = owl;
|
||||
|
||||
const ROUTES = {
|
||||
descriptionScreen: 2,
|
||||
@@ -61,28 +60,20 @@ const PALETTE_NAMES = [
|
||||
// from CSS and added in each palette.
|
||||
const CUSTOM_BG_COLOR_ATTRS = ['menu', 'footer'];
|
||||
|
||||
const SESSION_STORAGE_ITEM_NAME = 'websiteConfigurator' + session.website_id;
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Components
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
class SkipButton extends Component {
|
||||
async skip() {
|
||||
await skipConfigurator(Component.env.services);
|
||||
}
|
||||
}
|
||||
|
||||
class SkipButton extends Component {}
|
||||
SkipButton.template = 'website.Configurator.SkipButton';
|
||||
|
||||
class WelcomeScreen extends Component {
|
||||
setup() {
|
||||
this.state = useStore();
|
||||
this.router = useRouter();
|
||||
}
|
||||
|
||||
goToDescription() {
|
||||
this.router.navigate(ROUTES.descriptionScreen);
|
||||
this.props.navigate(ROUTES.descriptionScreen);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,7 +86,6 @@ class DescriptionScreen extends Component {
|
||||
setup() {
|
||||
this.industrySelection = useRef('industrySelection');
|
||||
this.state = useStore();
|
||||
this.router = useRouter();
|
||||
this.labelToId = {};
|
||||
this.autocompleteHasResults = true;
|
||||
|
||||
@@ -278,7 +268,7 @@ class DescriptionScreen extends Component {
|
||||
checkDescriptionCompletion() {
|
||||
const {selectedType, selectedPurpose, selectedIndustry} = this.state;
|
||||
if (selectedType && selectedPurpose && selectedIndustry) {
|
||||
this.router.navigate(ROUTES.paletteSelectionScreen);
|
||||
this.props.navigate(ROUTES.paletteSelectionScreen);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -291,10 +281,10 @@ Object.assign(DescriptionScreen, {
|
||||
class PaletteSelectionScreen extends Component {
|
||||
setup() {
|
||||
this.state = useStore();
|
||||
this.router = useRouter();
|
||||
this.logoInputRef = useRef('logoSelectionInput');
|
||||
this.notification = useService("notification");
|
||||
this.rpc = useService("rpc");
|
||||
this.orm = useService('orm');
|
||||
|
||||
onMounted(() => {
|
||||
if (this.state.logo) {
|
||||
@@ -312,13 +302,10 @@ class PaletteSelectionScreen extends Component {
|
||||
if (logoSelectInput.files.length === 1) {
|
||||
const file = logoSelectInput.files[0];
|
||||
const data = await utils.getDataURLFromFile(file);
|
||||
const attachment = await this.rpc({
|
||||
route: '/web_editor/attachment/add_data',
|
||||
params: {
|
||||
'name': 'logo',
|
||||
'data': data.split(',')[1],
|
||||
'is_image': true,
|
||||
},
|
||||
const attachment = await this.rpc('/web_editor/attachment/add_data', {
|
||||
'name': 'logo',
|
||||
'data': data.split(',')[1],
|
||||
'is_image': true,
|
||||
});
|
||||
if (!attachment.error) {
|
||||
this.state.changeLogo(data, attachment.id);
|
||||
@@ -338,18 +325,17 @@ class PaletteSelectionScreen extends Component {
|
||||
img = await svgToPNG(img);
|
||||
}
|
||||
img = img.split(',')[1];
|
||||
const [color1, color2] = await this.rpc({
|
||||
model: 'base.document.layout',
|
||||
method: 'extract_image_primary_secondary_colors',
|
||||
args: [img],
|
||||
kwargs: {mitigate: 255},
|
||||
});
|
||||
const [color1, color2] = await this.orm.call('base.document.layout',
|
||||
'extract_image_primary_secondary_colors',
|
||||
[img],
|
||||
{mitigate: 255},
|
||||
);
|
||||
this.state.setRecommendedPalette(color1, color2);
|
||||
}
|
||||
|
||||
selectPalette(paletteName) {
|
||||
this.state.selectPalette(paletteName);
|
||||
this.router.navigate(ROUTES.featuresSelectionScreen);
|
||||
this.props.navigate(ROUTES.featuresSelectionScreen);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -358,32 +344,78 @@ Object.assign(PaletteSelectionScreen, {
|
||||
template: 'website.Configurator.PaletteSelectionScreen',
|
||||
});
|
||||
|
||||
class FeaturesSelectionScreen extends Component {
|
||||
class ApplyConfiguratorScreen extends Component {
|
||||
async applyConfigurator(themeName) {
|
||||
if (!this.state.selectedIndustry) {
|
||||
return this.props.navigate(ROUTES.descriptionScreen);
|
||||
}
|
||||
if (!this.state.selectedPalette) {
|
||||
return this.props.navigate(ROUTES.paletteSelectionScreen);
|
||||
}
|
||||
if (themeName !== undefined) {
|
||||
$('body').append(qweb.render('website.ThemePreview.Loader', {showTips: true}));
|
||||
const selectedFeatures = Object.values(this.state.features).filter((feature) => feature.selected).map((feature) => feature.id);
|
||||
let selectedPalette = this.state.selectedPalette.name;
|
||||
if (!selectedPalette) {
|
||||
selectedPalette = [
|
||||
this.state.selectedPalette.color1,
|
||||
this.state.selectedPalette.color2,
|
||||
this.state.selectedPalette.color3,
|
||||
this.state.selectedPalette.color4,
|
||||
this.state.selectedPalette.color5,
|
||||
];
|
||||
}
|
||||
|
||||
const resp = await this.orm.silent.call('website',
|
||||
'configurator_apply',
|
||||
[],
|
||||
{
|
||||
'selected_features': selectedFeatures,
|
||||
'industry_id': this.state.selectedIndustry.id,
|
||||
'selected_palette': selectedPalette,
|
||||
'theme_name': themeName,
|
||||
'website_purpose': WEBSITE_PURPOSES[this.state.selectedPurpose].name,
|
||||
'website_type': WEBSITE_TYPES[this.state.selectedType].name,
|
||||
'logo_attachment_id': this.state.logoAttachmentId,
|
||||
});
|
||||
|
||||
this.props.clearStorage();
|
||||
|
||||
// Here the website service goToWebsite method is not used because
|
||||
// the web client needs to be reloaded after the new modules have
|
||||
// been installed.
|
||||
window.location.replace(`/web#action=website.website_preview&website_id=${resp.website_id}&enable_editor=1`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class FeaturesSelectionScreen extends ApplyConfiguratorScreen {
|
||||
setup() {
|
||||
super.setup();
|
||||
|
||||
this.orm = useService("orm");
|
||||
this.state = useStore();
|
||||
this.router = useRouter();
|
||||
this.rpc = useService("rpc");
|
||||
}
|
||||
|
||||
async buildWebsite() {
|
||||
const industryId = this.state.selectedIndustry && this.state.selectedIndustry.id;
|
||||
if (!industryId) {
|
||||
return this.router.navigate(ROUTES.descriptionScreen);
|
||||
return this.props.navigate(ROUTES.descriptionScreen);
|
||||
}
|
||||
const themes = await this.rpc({
|
||||
model: 'website',
|
||||
method: 'configurator_recommended_themes',
|
||||
kwargs: {
|
||||
const themes = await this.orm.call('website',
|
||||
'configurator_recommended_themes',
|
||||
[],
|
||||
{
|
||||
'industry_id': industryId,
|
||||
'palette': this.state.selectedPalette,
|
||||
},
|
||||
});
|
||||
);
|
||||
|
||||
if (!themes.length) {
|
||||
await applyConfigurator(this, 'theme_default');
|
||||
await this.applyConfigurator('theme_default');
|
||||
} else {
|
||||
this.state.updateRecommendedThemes(themes);
|
||||
this.router.navigate(ROUTES.themeSelectionScreen);
|
||||
this.props.navigate(ROUTES.themeSelectionScreen);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -393,11 +425,12 @@ Object.assign(FeaturesSelectionScreen, {
|
||||
template: 'website.Configurator.FeatureSelection',
|
||||
});
|
||||
|
||||
class ThemeSelectionScreen extends Component {
|
||||
class ThemeSelectionScreen extends ApplyConfiguratorScreen {
|
||||
setup() {
|
||||
super.setup();
|
||||
|
||||
this.orm = useService('orm');
|
||||
this.state = useStore();
|
||||
this.router = useRouter();
|
||||
this.rpc = useService('rpc');
|
||||
this.themeSVGPreviews = [useRef('ThemePreview1'), useRef('ThemePreview2'), useRef('ThemePreview3')];
|
||||
|
||||
onMounted(() => {
|
||||
@@ -408,56 +441,19 @@ class ThemeSelectionScreen extends Component {
|
||||
}
|
||||
|
||||
async chooseTheme(themeName) {
|
||||
await applyConfigurator(this, themeName);
|
||||
await this.applyConfigurator(themeName);
|
||||
}
|
||||
}
|
||||
|
||||
ThemeSelectionScreen.template = 'website.Configurator.ThemeSelectionScreen';
|
||||
|
||||
class Configurator extends Component {
|
||||
setup() {
|
||||
this.router = useRouter();
|
||||
}
|
||||
}
|
||||
|
||||
Object.assign(Configurator, {
|
||||
components: {
|
||||
WelcomeScreen,
|
||||
DescriptionScreen,
|
||||
PaletteSelectionScreen,
|
||||
FeaturesSelectionScreen,
|
||||
ThemeSelectionScreen,
|
||||
},
|
||||
template: 'website.Configurator.Configurator',
|
||||
});
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Router
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
class Router {
|
||||
constructor() {
|
||||
this.location = window.location.pathname;
|
||||
}
|
||||
|
||||
navigate(id) {
|
||||
this.location = `/website/configurator${id ? '/' + id : ''}`;
|
||||
history.pushState({}, '', window.location.origin + this.location);
|
||||
}
|
||||
}
|
||||
|
||||
function useRouter() {
|
||||
const env = useEnv();
|
||||
return useState(env.router);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Store
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
class Store {
|
||||
async start() {
|
||||
Object.assign(this, await getInitialState(Component.env.services));
|
||||
async start(getInitialState) {
|
||||
Object.assign(this, await getInitialState());
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
@@ -572,193 +568,174 @@ class Store {
|
||||
}
|
||||
}
|
||||
|
||||
async function getInitialState(services) {
|
||||
// Load values from python and iap
|
||||
var results = await services.rpc({
|
||||
model: 'website',
|
||||
method: 'configurator_init',
|
||||
});
|
||||
const r = {
|
||||
industries: results.industries,
|
||||
logo: results.logo ? 'data:image/png;base64,' + results.logo : false,
|
||||
};
|
||||
r.industries = r.industries.map((industry, index) => ({
|
||||
...industry,
|
||||
wordCount: industry.label.split(" ").length,
|
||||
hitCountOrder: index,
|
||||
}));
|
||||
|
||||
// Load palettes from the current CSS
|
||||
const palettes = {};
|
||||
const style = window.getComputedStyle(document.documentElement);
|
||||
|
||||
PALETTE_NAMES.forEach((paletteName) => {
|
||||
const palette = {
|
||||
name: paletteName
|
||||
};
|
||||
for (let j = 1; j <= 5; j += 1) {
|
||||
palette[`color${j}`] = weUtils.getCSSVariableValue(`o-palette-${paletteName}-o-color-${j}`, style);
|
||||
}
|
||||
CUSTOM_BG_COLOR_ATTRS.forEach((attr) => {
|
||||
palette[attr] = weUtils.getCSSVariableValue(`o-palette-${paletteName}-${attr}-bg`, style);
|
||||
});
|
||||
palettes[paletteName] = palette;
|
||||
});
|
||||
|
||||
const localState = JSON.parse(window.sessionStorage.getItem(SESSION_STORAGE_ITEM_NAME));
|
||||
if (localState) {
|
||||
let themes = [];
|
||||
if (localState.selectedIndustry && localState.selectedPalette) {
|
||||
themes = await services.rpc({
|
||||
model: 'website',
|
||||
method: 'configurator_recommended_themes',
|
||||
kwargs: {
|
||||
'industry_id': localState.selectedIndustry.id,
|
||||
'palette': localState.selectedPalette,
|
||||
},
|
||||
});
|
||||
}
|
||||
return Object.assign(r, {...localState, palettes, themes});
|
||||
}
|
||||
|
||||
const features = {};
|
||||
results.features.forEach(feature => {
|
||||
features[feature.id] = Object.assign({}, feature, {selected: feature.module_state === 'installed'});
|
||||
const wtp = features[feature.id]['website_config_preselection'];
|
||||
features[feature.id]['website_config_preselection'] = wtp ? wtp.split(',') : [];
|
||||
});
|
||||
|
||||
// Palette color used by default as background color for menu and footer.
|
||||
// Needed to build the recommended palette.
|
||||
const defaultColors = {};
|
||||
CUSTOM_BG_COLOR_ATTRS.forEach((attr) => {
|
||||
const color = weUtils.getCSSVariableValue(`o-default-${attr}-bg`, style);
|
||||
const match = color.match(/o-color-(?<idx>[1-5])/);
|
||||
const colorIdx = parseInt(match.groups['idx']);
|
||||
defaultColors[attr] = `color${colorIdx}`;
|
||||
});
|
||||
|
||||
return Object.assign(r, {
|
||||
selectedType: undefined,
|
||||
selectedPurpose: undefined,
|
||||
selectedIndustry: undefined,
|
||||
selectedPalette: undefined,
|
||||
recommendedPalette: undefined,
|
||||
defaultColors: defaultColors,
|
||||
palettes: palettes,
|
||||
features: features,
|
||||
themes: [],
|
||||
logoAttachmentId: undefined,
|
||||
});
|
||||
}
|
||||
|
||||
function useStore() {
|
||||
const env = useEnv();
|
||||
return useState(env.state);
|
||||
return useState(env.store);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Helpers
|
||||
//------------------------------------------------------------------------------
|
||||
class Configurator extends Component {
|
||||
setup() {
|
||||
this.orm = useService('orm');
|
||||
this.action = useService('action');
|
||||
this.router = useService('router');
|
||||
|
||||
async function skipConfigurator(services) {
|
||||
await services.rpc({
|
||||
model: 'website',
|
||||
method: 'configurator_skip',
|
||||
});
|
||||
window.sessionStorage.removeItem(SESSION_STORAGE_ITEM_NAME);
|
||||
window.location = '/web#action=website.theme_install_kanban_action';
|
||||
}
|
||||
const initialStep = this.props.action.context.params && this.props.action.context.params.step;
|
||||
const store = reactive(new Store(), () => this.updateStorage(store));
|
||||
|
||||
async function applyConfigurator(self, themeName) {
|
||||
if (!self.state.selectedIndustry) {
|
||||
return self.router.navigate(ROUTES.descriptionScreen);
|
||||
this.state = useState({
|
||||
currentStep: initialStep,
|
||||
});
|
||||
|
||||
useSubEnv({ store });
|
||||
|
||||
onWillStart(async () => {
|
||||
this.websiteId = (await this.orm.call('website', 'get_current_website')).match(/\d+/)[0];
|
||||
|
||||
await store.start(() => this.getInitialState());
|
||||
this.updateStorage(store);
|
||||
if (!store.industries) {
|
||||
await this.skipConfigurator();
|
||||
}
|
||||
});
|
||||
|
||||
// This is a hack to overwrite the history state, modified by the
|
||||
// router service after executing an action. Ideally, the router
|
||||
// service would let us push a state with a new pathname.
|
||||
onMounted(() => {
|
||||
setTimeout(() => {
|
||||
this.router.cancelPushes();
|
||||
this.updateBrowserUrl();
|
||||
});
|
||||
});
|
||||
}
|
||||
if (!self.state.selectedPalette) {
|
||||
return self.router.navigate(ROUTES.paletteSelectionScreen);
|
||||
|
||||
get pathname() {
|
||||
return `/website/configurator${this.state.currentStep ? `/${this.state.currentStep}` : ''}`;
|
||||
}
|
||||
if (themeName !== undefined) {
|
||||
const loader = renderToString('website.ThemePreview.Loader', {showTips: true});
|
||||
$('body').append(loader);
|
||||
const selectedFeatures = Object.values(self.state.features).filter((feature) => feature.selected).map((feature) => feature.id);
|
||||
let selectedPalette = self.state.selectedPalette.name;
|
||||
if (!selectedPalette) {
|
||||
selectedPalette = [
|
||||
self.state.selectedPalette.color1,
|
||||
self.state.selectedPalette.color2,
|
||||
self.state.selectedPalette.color3,
|
||||
self.state.selectedPalette.color4,
|
||||
self.state.selectedPalette.color5,
|
||||
];
|
||||
|
||||
get storageItemName() {
|
||||
return `websiteConfigurator${this.websiteId}`;
|
||||
}
|
||||
|
||||
updateBrowserUrl() {
|
||||
history.pushState({}, '', this.pathname);
|
||||
}
|
||||
|
||||
navigate(step) {
|
||||
this.state.currentStep = step;
|
||||
this.updateBrowserUrl();
|
||||
}
|
||||
|
||||
clearStorage() {
|
||||
window.sessionStorage.removeItem(this.storageItemName);
|
||||
}
|
||||
|
||||
async getInitialState() {
|
||||
// Load values from python and iap
|
||||
var results = await this.orm.call('website', 'configurator_init');
|
||||
const r = {
|
||||
industries: results.industries,
|
||||
logo: results.logo ? 'data:image/png;base64,' + results.logo : false,
|
||||
};
|
||||
r.industries = r.industries.map((industry, index) => ({
|
||||
...industry,
|
||||
wordCount: industry.label.split(" ").length,
|
||||
hitCountOrder: index,
|
||||
}));
|
||||
|
||||
// Load palettes from the current CSS
|
||||
const palettes = {};
|
||||
const style = window.getComputedStyle(document.documentElement);
|
||||
|
||||
PALETTE_NAMES.forEach((paletteName) => {
|
||||
const palette = {
|
||||
name: paletteName
|
||||
};
|
||||
for (let j = 1; j <= 5; j += 1) {
|
||||
palette[`color${j}`] = weUtils.getCSSVariableValue(`o-palette-${paletteName}-o-color-${j}`, style);
|
||||
}
|
||||
CUSTOM_BG_COLOR_ATTRS.forEach((attr) => {
|
||||
palette[attr] = weUtils.getCSSVariableValue(`o-palette-${paletteName}-${attr}-bg`, style);
|
||||
});
|
||||
palettes[paletteName] = palette;
|
||||
});
|
||||
|
||||
const localState = JSON.parse(window.sessionStorage.getItem(this.storageItemName));
|
||||
if (localState) {
|
||||
let themes = [];
|
||||
if (localState.selectedIndustry && localState.selectedPalette) {
|
||||
themes = await this.orm.call('website', 'configurator_recommended_themes', [], {
|
||||
'industry_id': localState.selectedIndustry.id,
|
||||
'palette': localState.selectedPalette,
|
||||
});
|
||||
}
|
||||
return Object.assign(r, {...localState, palettes, themes});
|
||||
}
|
||||
const resp = await self.rpc({
|
||||
model: 'website',
|
||||
method: 'configurator_apply',
|
||||
kwargs: {
|
||||
'selected_features': selectedFeatures,
|
||||
'industry_id': self.state.selectedIndustry.id,
|
||||
'selected_palette': selectedPalette,
|
||||
'theme_name': themeName,
|
||||
'website_purpose': WEBSITE_PURPOSES[self.state.selectedPurpose].name,
|
||||
'website_type': WEBSITE_TYPES[self.state.selectedType].name,
|
||||
'logo_attachment_id': self.state.logoAttachmentId,
|
||||
},
|
||||
|
||||
const features = {};
|
||||
results.features.forEach(feature => {
|
||||
features[feature.id] = Object.assign({}, feature, {selected: feature.module_state === 'installed'});
|
||||
const wtp = features[feature.id]['website_config_preselection'];
|
||||
features[feature.id]['website_config_preselection'] = wtp ? wtp.split(',') : [];
|
||||
});
|
||||
|
||||
// Palette color used by default as background color for menu and footer.
|
||||
// Needed to build the recommended palette.
|
||||
const defaultColors = {};
|
||||
CUSTOM_BG_COLOR_ATTRS.forEach((attr) => {
|
||||
const color = weUtils.getCSSVariableValue(`o-default-${attr}-bg`, style);
|
||||
const match = color.match(/o-color-(?<idx>[1-5])/);
|
||||
const colorIdx = parseInt(match.groups['idx']);
|
||||
defaultColors[attr] = `color${colorIdx}`;
|
||||
});
|
||||
|
||||
return Object.assign(r, {
|
||||
selectedType: undefined,
|
||||
selectedPurpose: undefined,
|
||||
selectedIndustry: undefined,
|
||||
selectedPalette: undefined,
|
||||
recommendedPalette: undefined,
|
||||
defaultColors: defaultColors,
|
||||
palettes: palettes,
|
||||
features: features,
|
||||
themes: [],
|
||||
logoAttachmentId: undefined,
|
||||
});
|
||||
}
|
||||
|
||||
updateStorage(state) {
|
||||
const newState = JSON.stringify({
|
||||
defaultColors: state.defaultColors,
|
||||
features: state.features,
|
||||
logo: state.logo,
|
||||
logoAttachmentId: state.logoAttachmentId,
|
||||
selectedIndustry: state.selectedIndustry,
|
||||
selectedPalette: state.selectedPalette,
|
||||
selectedPurpose: state.selectedPurpose,
|
||||
selectedType: state.selectedType,
|
||||
recommendedPalette: state.recommendedPalette,
|
||||
});
|
||||
window.sessionStorage.setItem(this.storageItemName, newState);
|
||||
}
|
||||
|
||||
async skipConfigurator() {
|
||||
await this.orm.call('website', 'configurator_skip');
|
||||
this.clearStorage();
|
||||
this.action.doAction('website.theme_install_kanban_action', {
|
||||
clearBreadcrumbs: true,
|
||||
});
|
||||
window.sessionStorage.removeItem(SESSION_STORAGE_ITEM_NAME);
|
||||
window.location = resp.url;
|
||||
}
|
||||
}
|
||||
|
||||
function updateStorage(state) {
|
||||
const newState = JSON.stringify({
|
||||
defaultColors: state.defaultColors,
|
||||
features: state.features,
|
||||
logo: state.logo,
|
||||
logoAttachmentId: state.logoAttachmentId,
|
||||
selectedIndustry: state.selectedIndustry,
|
||||
selectedPalette: state.selectedPalette,
|
||||
selectedPurpose: state.selectedPurpose,
|
||||
selectedType: state.selectedType,
|
||||
recommendedPalette: state.recommendedPalette,
|
||||
});
|
||||
window.sessionStorage.setItem(SESSION_STORAGE_ITEM_NAME, newState);
|
||||
}
|
||||
Object.assign(Configurator, {
|
||||
components: {
|
||||
WelcomeScreen,
|
||||
DescriptionScreen,
|
||||
PaletteSelectionScreen,
|
||||
FeaturesSelectionScreen,
|
||||
ThemeSelectionScreen,
|
||||
},
|
||||
template: 'website.Configurator.Configurator',
|
||||
});
|
||||
|
||||
async function makeEnvironment() {
|
||||
const state = reactive(new Store(), () => updateStorage(state));
|
||||
await state.start();
|
||||
updateStorage(state);
|
||||
const env = {
|
||||
state,
|
||||
router: reactive(new Router()),
|
||||
services: Component.env.services,
|
||||
};
|
||||
await session.is_bound;
|
||||
return env;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Setup
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
async function setup() {
|
||||
const env = await makeEnvironment();
|
||||
if (!env.state.industries) {
|
||||
await skipConfigurator(env.services);
|
||||
} else {
|
||||
const templates = await (await fetch('/website/static/src/client_actions/configurator/configurator.xml')).text();
|
||||
const app = new App(Configurator, {
|
||||
env,
|
||||
dev: env.debug,
|
||||
templates,
|
||||
translatableAttributes: ["data-tooltip"],
|
||||
translateFn: _t,
|
||||
});
|
||||
const loaderTemplate = await (await fetch('/website/static/src/xml/theme_preview.xml')).text();
|
||||
app.addTemplates(loaderTemplate);
|
||||
renderToString.app = app;
|
||||
await app.mount(document.body);
|
||||
}
|
||||
}
|
||||
|
||||
whenReady(setup);
|
||||
registry.category('actions').add('website_configurator', Configurator);
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
<t t-name="website.Configurator.SkipButton" owl="1">
|
||||
<div class="container-fluid py-2 pb-md-3 text-right pr-lg-5">
|
||||
<button class="btn btn-link" t-on-click="skip">Skip and start from scratch</button>
|
||||
<button class="btn btn-link" t-on-click="this.props.skip">Skip and start from scratch</button>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
<button class="o_configurator_show btn btn-primary btn-lg px-4 py-2" t-on-click="goToDescription">Let's do it</button>
|
||||
</div>
|
||||
</div>
|
||||
<SkipButton/>
|
||||
<SkipButton skip="this.props.skip"/>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
@@ -83,7 +83,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<SkipButton/>
|
||||
<SkipButton skip="this.props.skip"/>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
@@ -145,7 +145,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<SkipButton/>
|
||||
<SkipButton skip="this.props.skip"/>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
@@ -187,7 +187,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<SkipButton/>
|
||||
<SkipButton skip="this.props.skip"/>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
@@ -238,11 +238,11 @@
|
||||
|
||||
<t t-name="website.Configurator.Configurator" owl="1">
|
||||
<div class="o_configurator_container">
|
||||
<DescriptionScreen t-if="router.location === '/website/configurator/2'"/>
|
||||
<PaletteSelectionScreen t-elif="router.location === '/website/configurator/3'"/>
|
||||
<FeaturesSelectionScreen t-elif="router.location === '/website/configurator/4'"/>
|
||||
<ThemeSelectionScreen t-elif="router.location === '/website/configurator/5'"/>
|
||||
<WelcomeScreen t-else=""/>
|
||||
<DescriptionScreen t-if="state.currentStep === 2" navigate="(screen) => this.navigate(screen)" skip.bind="skipConfigurator"/>
|
||||
<PaletteSelectionScreen t-elif="state.currentStep === 3" navigate="(screen) => this.navigate(screen)" skip.bind="skipConfigurator"/>
|
||||
<FeaturesSelectionScreen t-elif="state.currentStep === 4" navigate="(screen) => this.navigate(screen)" skip.bind="skipConfigurator" clearStorage.bind="clearStorage"/>
|
||||
<ThemeSelectionScreen t-elif="state.currentStep === 5" navigate="(screen) => this.navigate(screen)" clearStorage.bind="clearStorage"/>
|
||||
<WelcomeScreen t-else="" navigate="(screen) => this.navigate(screen)" skip.bind="skipConfigurator"/>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
|
||||
@@ -26,7 +26,10 @@ const ThemePreviewControllerCommon = {
|
||||
this._addLoader();
|
||||
switch (action) {
|
||||
case 'button_choose_theme':
|
||||
actionCallback = result => this.do_action(result);
|
||||
actionCallback = result => {
|
||||
this.do_action(result);
|
||||
this._removeLoader();
|
||||
};
|
||||
break;
|
||||
case 'button_refresh_theme':
|
||||
actionCallback = () => this._removeLoader();
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
:root {
|
||||
// Override css variables to influence the default style of the editor
|
||||
// without duplicating the css.
|
||||
@include print-variable('o-we-toolbar-height', $o-navbar-height);
|
||||
|
||||
// Need the colors, name, menu background color and footer background color of each color palette
|
||||
$o-palette-names: ();
|
||||
$-default-cc: ();
|
||||
$-bg-attrs: 'menu', 'footer';
|
||||
@each $attr in $-bg-attrs {
|
||||
// Since the variables built here are currently only used for theme previews we
|
||||
// decided to keep things simple and we deliberately ignore potential overrides
|
||||
// from #{$-bg-attrs}-custom and #{$-bg-attrs}-gradient.
|
||||
$-default-cc: map-merge($-default-cc, ($attr: map-get($o-base-color-palette, $attr)))
|
||||
}
|
||||
|
||||
@each $-selected-palette-name in $o-selected-color-palettes-names {
|
||||
$-palette: map-get($o-color-palettes, $-selected-palette-name);
|
||||
|
||||
@if $-selected-palette-name != 'user-palette' {
|
||||
@each $key, $value in $-palette {
|
||||
@include print-variable('o-palette-#{$-selected-palette-name}-#{$key}', $value);
|
||||
}
|
||||
|
||||
@each $attr-name, $default-cc-idx in $-default-cc {
|
||||
$-cc-idx: $default-cc-idx;
|
||||
@if map-has-key($-palette, $attr-name) {
|
||||
$-cc-idx: map-get($-palette, $attr-name);
|
||||
}
|
||||
$-bg: $body-bg;
|
||||
@if $-cc-idx != null {
|
||||
$-cc: nth($o-color-combinations, $-cc-idx);
|
||||
$-bg: o-safe-get($-palette, 'o-cc#{$-cc-idx}-bg', map-get($-cc, 'bg'));
|
||||
@if type-of($-bg) != color {
|
||||
$-bg: map-get($-palette, $-bg);
|
||||
}
|
||||
}
|
||||
@include print-variable('o-palette-#{$-selected-palette-name}-#{$attr-name}-bg', $-bg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Need the palette colors used as default menu and footer background color
|
||||
@each $attr-name, $default-cc-idx in $-default-cc {
|
||||
$-cc: nth($o-color-combinations, $default-cc-idx);
|
||||
$-default-bg: map-get($-cc, 'bg');
|
||||
@include print-variable('o-default-#{$attr-name}-bg', $-default-bg);
|
||||
}
|
||||
|
||||
@include print-variable('palette-names', $o-selected-color-palettes-names);
|
||||
|
||||
// Need info about the base grays which are used to compute the final grays
|
||||
@each $name, $color in $o-base-gray-color-palette {
|
||||
@include print-variable('base-#{$name}', $color);
|
||||
}
|
||||
}
|
||||
@@ -6,63 +6,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
:root {
|
||||
// Override css variables to influence the default style of the editor
|
||||
// without duplicating the css.
|
||||
@include print-variable('o-we-toolbar-height', $o-navbar-height);
|
||||
|
||||
// Need the colors, name, menu background color and footer background color of each color palette
|
||||
$o-palette-names: ();
|
||||
$-default-cc: ();
|
||||
$-bg-attrs: 'menu', 'footer';
|
||||
@each $attr in $-bg-attrs {
|
||||
// Since the variables built here are currently only used for theme previews we
|
||||
// decided to keep things simple and we deliberately ignore potential overrides
|
||||
// from #{$-bg-attrs}-custom and #{$-bg-attrs}-gradient.
|
||||
$-default-cc: map-merge($-default-cc, ($attr: map-get($o-base-color-palette, $attr)))
|
||||
}
|
||||
|
||||
@each $-selected-palette-name in $o-selected-color-palettes-names {
|
||||
$-palette: map-get($o-color-palettes, $-selected-palette-name);
|
||||
|
||||
@if $-selected-palette-name != 'user-palette' {
|
||||
@each $key, $value in $-palette {
|
||||
@include print-variable('o-palette-#{$-selected-palette-name}-#{$key}', $value);
|
||||
}
|
||||
|
||||
@each $attr-name, $default-cc-idx in $-default-cc {
|
||||
$-cc-idx: $default-cc-idx;
|
||||
@if map-has-key($-palette, $attr-name) {
|
||||
$-cc-idx: map-get($-palette, $attr-name);
|
||||
}
|
||||
$-bg: $body-bg;
|
||||
@if $-cc-idx != null {
|
||||
$-cc: nth($o-color-combinations, $-cc-idx);
|
||||
$-bg: o-safe-get($-palette, 'o-cc#{$-cc-idx}-bg', map-get($-cc, 'bg'));
|
||||
@if type-of($-bg) != color {
|
||||
$-bg: map-get($-palette, $-bg);
|
||||
}
|
||||
}
|
||||
@include print-variable('o-palette-#{$-selected-palette-name}-#{$attr-name}-bg', $-bg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Need the palette colors used as default menu and footer background color
|
||||
@each $attr-name, $default-cc-idx in $-default-cc {
|
||||
$-cc: nth($o-color-combinations, $default-cc-idx);
|
||||
$-default-bg: map-get($-cc, 'bg');
|
||||
@include print-variable('o-default-#{$attr-name}-bg', $-default-bg);
|
||||
}
|
||||
|
||||
@include print-variable('palette-names', $o-selected-color-palettes-names);
|
||||
|
||||
// Need info about the base grays which are used to compute the final grays
|
||||
@each $name, $color in $o-base-gray-color-palette {
|
||||
@include print-variable('base-#{$name}', $color);
|
||||
}
|
||||
}
|
||||
|
||||
#oe_snippets {
|
||||
top: 0;
|
||||
.oe-toolbar {
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<template id="website_configurator" name="Website Configurator" inherit_id="web.frontend_layout" primary="True">
|
||||
<xpath expr="//head/t[@t-call-assets][last()]" position="after">
|
||||
<t t-call-assets="website.website_configurator_assets_js" lazy_load="True"/>
|
||||
</xpath>
|
||||
<xpath expr="//t[@t-call-assets='web.assets_frontend']" position="replace">
|
||||
<t t-call-assets="website.website_configurator_assets_scss" t-js="false"/>
|
||||
</xpath>
|
||||
<xpath expr="//nav" position="replace"/>
|
||||
<xpath expr="//div[@id='wrapwrap']" position="replace"/>
|
||||
</template>
|
||||
|
||||
</odoo>
|
||||
@@ -1,18 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<!-- Front-end/Back-end integration -->
|
||||
<template id="user_navbar" inherit_id="website.layout" name="User Navbar">
|
||||
<xpath expr="//div[@id='wrapwrap']" position="before">
|
||||
<div t-if="'with_loader' in request.params" class="o_theme_install_loader_container position-fixed fixed-top fixed-left
|
||||
h-100 w-100 d-flex flex-column align-items-center text-white font-weight-bold text-center">
|
||||
Building your website...
|
||||
<div class="o_theme_install_loader"/>
|
||||
<p class="o_theme_install_loader_tip w-25">
|
||||
TIP: Once loaded, follow the
|
||||
<span class="o_tooltip o_tooltip_visible bottom o_animated position-relative"></span>
|
||||
<br/>pointer to build the perfect page in 7 steps.
|
||||
</p>
|
||||
</div>
|
||||
</xpath>
|
||||
</template>
|
||||
</odoo>
|
||||
@@ -583,15 +583,15 @@
|
||||
action="website.website_preview"/>
|
||||
|
||||
<!-- Configurator actions -->
|
||||
<record id="start_configurator_act_url" model="ir.actions.act_url">
|
||||
<record id="website_configurator" model="ir.actions.client">
|
||||
<field name="name">Website Configurator</field>
|
||||
<field name="url">/website/configurator</field>
|
||||
<field name="target">self</field>
|
||||
<field name="tag">website_configurator</field>
|
||||
<field name="target">fullscreen</field>
|
||||
</record>
|
||||
|
||||
<record id="website_configurator_todo" model="ir.actions.todo">
|
||||
<field name="name">Start Website Configurator</field>
|
||||
<field name="action_id" ref="start_configurator_act_url"/>
|
||||
<field name="action_id" ref="website_configurator"/>
|
||||
<field name="sequence">0</field>
|
||||
</record>
|
||||
</data>
|
||||
|
||||
Reference in New Issue
Block a user