Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add "Save Snippet" sidebar button + display custom inner content in the sidebar #3952

Merged
merged 5 commits into from
Jan 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { useService } from "@web/core/utils/hooks";
import { addLoadingEffect as addButtonLoadingEffect } from "@web/core/utils/ui";
import { RPCError } from "@web/core/network/rpc";
import { escape } from "@web/core/utils/strings";
import { user } from "@web/core/user";
import { useSetupAction } from "@web/search/action_hook";
import { BuilderActionsPlugin } from "../plugins/builder_actions_plugin";
import { BuilderOptionsPlugin } from "../plugins/builder_options_plugin";
Expand Down Expand Up @@ -95,6 +96,7 @@ export class BuilderSidebar extends Component {
this.orm = useService("orm");
this.dialog = useService("dialog");
this.ui = useService("ui");
this.notification = useService("notification");

const editorBus = new EventBus();
// TODO: maybe do a different config for the translate mode and the
Expand Down Expand Up @@ -150,10 +152,17 @@ export class BuilderSidebar extends Component {
this.env.services
);

this.context = {
website_id: this.websiteService.currentWebsite.id,
lang: this.websiteService.currentWebsite.metadata.lang,
user_lang: user.context.lang,
sobo-odoo marked this conversation as resolved.
Show resolved Hide resolved
};

this.snippetModel = useState(
new SnippetModel(this.env.services, {
snippetsName: this.props.snippetsName,
installSnippetModule: this.installSnippetModule.bind(this),
context: this.context,
})
);

Expand Down Expand Up @@ -201,16 +210,17 @@ export class BuilderSidebar extends Component {
"If you discard the current edits, all unsaved changes will be lost. You can cancel to return to edit mode."
),
confirm: () => this.props.closeEditor(),
cancel: () => { },
cancel: () => {},
});
} else {
this.props.closeEditor();
}
}

getInvisibleSelector(isMobile = this.props.isMobile) {
return `.o_snippet_invisible, ${isMobile ? ".o_snippet_mobile_invisible" : ".o_snippet_desktop_invisible"
}`;
return `.o_snippet_invisible, ${
isMobile ? ".o_snippet_mobile_invisible" : ".o_snippet_desktop_invisible"
}`;
}

async save() {
Expand Down Expand Up @@ -369,7 +379,7 @@ export class BuilderSidebar extends Component {
}
},
confirmLabel: _t("Save and Install"),
cancel: () => { },
cancel: () => {},
});
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
<BlockTab snippetModel="snippetModel" installSnippetModule.bind="installSnippetModule"/>
</t>
<t t-if="state.activeTab === 'customize'">
<CustomizeTab currentOptionsContainers="state.currentOptionsContainers"/>
<CustomizeTab currentOptionsContainers="state.currentOptionsContainers" snippetModel="snippetModel"/>
</t>
<t t-if="state.activeTab === 'theme'">
theme
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Component } from "@odoo/owl";
import { useDraggable } from "@web/core/utils/draggable";
import { useService } from "@web/core/utils/hooks";
import { AddSnippetDialog } from "./add_snippet_dialog/add_snippet_dialog";
import { CustomInnerSnippet } from "./custom_inner_snippet";

// TODO move it in web (copy from web_studio)
function copyElementOnDrag() {
Expand Down Expand Up @@ -32,6 +33,7 @@ function copyElementOnDrag() {

export class BlockTab extends Component {
static template = "html_builder.BlockTab";
static components = { CustomInnerSnippet };
static props = {
snippetModel: { type: Object },
installSnippetModule: { type: Function },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
.block {
background-color: rgba(62, 62, 70, 0.9);

&.to_install{
&.to_install {
.img-container {
position: relative;

Expand All @@ -26,4 +26,66 @@
}
}
}
}

// TODO improve later + better class names
.custom-inner-content {
display: flex;
width: 100%;

.thumbnail,
.rename-delete-buttons {
display: flex;
align-items: center;
background-color: rgba(62, 62, 70, 0.9);
}

.thumbnail {
flex-grow: 1;
min-width: 0; // Ensure text-overflow on flex children
}

.thumbnail-title {
white-space: nowrap;
}

.thumbnail-img {
flex-shrink: 0;
width: 41px;
height: 30px; // 82x60 -> 41x30
padding: 0;
background-repeat: no-repeat;
background-size: contain;
background-position: top center;
overflow: hidden;
}

.rename-delete-buttons button {
// @extend %we-generic-link;
padding-left: $o-we-sidebar-content-field-button-group-button-spacing;
padding-right: $o-we-sidebar-content-field-button-group-button-spacing;
}

&:not(:hover) .rename-delete-buttons button {
display: none;
}

.rename-input {
// @extend %we-generic-text-input;
display: flex;
cursor: pointer;

input {
cursor: text;
}

button {
// @extend %we-generic-clickable;
cursor: pointer;
flex: 1 1 auto;
padding: 0 $o-we-sidebar-content-field-button-group-button-spacing;
line-height: 17px;
text-align: center;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,14 @@
</div>
</div>

<div>Inner content</div>
<t t-if="this.props.snippetModel.hasCustomInnerContents">
<div class="mt-1">Custom Inner Content</div>
<t t-foreach="this.props.snippetModel.snippetCustomInnerContents" t-as="snippet" t-key="snippet.id">
<CustomInnerSnippet snippet="snippet" snippetModel="this.props.snippetModel"/>
</t>
</t>

<div class="mt-1">Inner Content</div>
<div class="container">
<div class="row">
<div t-foreach="this.props.snippetModel.snippetInnerContents" t-as="snippet" t-key="snippet.id" class="col-4 p-1 o_draggable" t-att-name="snippet.title" t-att-data-category="'snippet_content'" t-att-data-id="snippet.id">
Expand All @@ -33,4 +40,5 @@
</div>
</t>


</templates>
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Component, useState, useRef } from "@odoo/owl";

export class CustomInnerSnippet extends Component {
static template = "html_builder.CustomInnerSnippet";
static props = {
snippetModel: { type: Object },
snippet: { type: Object },
};

setup() {
this.renameInputRef = useRef("rename-input");
this.state = useState({ isRenaming: false });
}

get snippet() {
return this.props.snippet;
}

toggleRenamingState() {
this.state.isRenaming = !this.state.isRenaming;
}

onConfirmRename() {
this.props.snippetModel.renameCustomSnippet(this.snippet, this.renameInputRef.el.value);
this.toggleRenamingState();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">

<t t-name="html_builder.CustomInnerSnippet">
<div class="p-1 o_draggable custom-inner-content" t-att-name="snippet.title" t-att-data-category="'snippet_custom_content'" t-att-data-id="snippet.id">
<div class="thumbnail">
<div class="thumbnail-img" t-attf-style="background-image: url({{snippet.thumbnailSrc}});"/>
<t t-if="state.isRenaming">
<div class="rename-input w-100 mx-1">
<input t-ref="rename-input" type="text" autocomplete="chrome-off" t-att-value="snippet.title" class="text-start" t-on-pointerdown.stop=""/>
<button class="o_we_text_success fa fa-check btn btn-outline-success border-0"
data-tooltip="Confirm"
t-on-click="onConfirmRename"/>
<button class="o_we_text_danger fa fa-times btn btn-outline-danger border-0"
data-tooltip="Cancel"
t-on-click="toggleRenamingState"/>
</div>
</t>
<t t-else="">
<span class="thumbnail-title" t-esc="snippet.title"/>
</t>
</div>
<div t-if="!state.isRenaming" class="rename-delete-buttons float-end">
<button class="fa fa-pencil btn o_we_hover_success btn-outline-info border-0"
t-attf-data-tooltip="Rename {{snippet.title}}"
t-on-click.stop="toggleRenamingState"/>
<button class="fa fa-trash btn o_we_hover_danger btn-outline-danger border-0"
t-attf-data-tooltip="Delete {{snippet.title}}"
t-on-click="() => this.props.snippetModel.deleteCustomSnippet(snippet)"/>
</div>
</div>
</t>

</templates>
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export class CustomizeTab extends Component {
static components = { OptionsContainer };
static props = {
currentOptionsContainers: { type: Array, optional: true },
snippetModel: { type: Object },
};
static defaultProps = {
currentOptionsContainers: [],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
<div t-ref="content">
<t t-foreach="props.currentOptionsContainers" t-as="optionsContainer" t-key="optionsContainer.id">
<OptionsContainer
snippetModel="props.snippetModel"
editingElement="optionsContainer.element"
options="optionsContainer.options"
isRemovable="optionsContainer.isRemovable"/>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { Component, useSubEnv } from "@odoo/owl";
import { Component, useSubEnv, markup } from "@odoo/owl";
import { useService } from "@web/core/utils/hooks";
import { _t } from "@web/core/l10n/translation";
import { defaultBuilderComponents } from "../builder_components/default_builder_components";
import { globalBuilderOptions } from "../builder_components/global_builder_options";
import { useVisibilityObserver, useApplyVisibility } from "../builder_components/utils";
Expand All @@ -9,12 +11,15 @@ export class OptionsContainer extends Component {
static template = "html_builder.OptionsContainer";
static components = { ...defaultBuilderComponents, ...globalBuilderOptions };
static props = {
snippetModel: { type: Object },
options: { type: Array },
editingElement: true, // HTMLElement from iframe
isRemovable: false,
};

setup() {
this.notification = useService("notification");

useSubEnv({
dependencyManager: new DependencyManager(),
getEditingElement: () => this.props.editingElement,
Expand All @@ -28,6 +33,15 @@ export class OptionsContainer extends Component {
return getSnippetName(this.env.getEditingElement());
}

// Checks if the element can be saved as a custom snippet.
get isSavable() {
const selector = "[data-snippet], a.btn";
// TODO `so_submit_button_selector` ?
const exclude = ".o_no_save, .s_donation_donate_btn, .s_website_form_send";
const el = this.props.editingElement;
return el.matches(selector) && !el.matches(exclude);
}

selectElement() {
this.env.editor.shared["builder-options"].updateContainers(this.props.editingElement);
}
Expand Down Expand Up @@ -58,4 +72,23 @@ export class OptionsContainer extends Component {
cloneElement() {
this.env.editor.shared.clone.cloneElement(this.props.editingElement);
}

async saveSnippet() {
const savedName = await this.props.snippetModel.saveSnippet(
this.props.editingElement,
this.env.editor.resources["clean_for_save_handlers"]
);
if (savedName) {
const message = markup(
_t(
"Your custom snippet was successfully saved as <strong>%s</strong>. Find it in your snippets collection.",
savedName
)
);
this.notification.add(message, {
type: "success",
autocloseDelay: 5000,
});
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@
<span t-out="title" role="button" t-on-click="() => this.selectElement()"/>
<!-- TODO CSS + remove temporary BS classes -->
<div class="float-end">
<t t-if="isSavable">
<button class="fa fa-fw fa-save oe_snippet_save o_we_link o_we_hover_warning btn btn-outline-warning border-0 px-0 me-1"
title="Save the block to use it elsewhere"
t-on-click="() => this.saveSnippet()"/>
</t>
<t t-if="props.isRemovable">
<button class="fa fa-fw fa-clone oe_snippet_clone o_we_link o_we_hover_success btn btn-outline-success border-0 px-0 me-1"
title="Duplicate Container"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,23 @@ export const device_visibility_option_selector = "section .row > div";

class DeviceVisibilityOptionPlugin extends Plugin {
static id = "DeviceVisibilityOption";
static dependencies = ["builder-options"];
static dependencies = ["builder-options", "visibilityPlugin"];
websiteService = this.services.website;
selector = "section .row > div";
resources = {
builder_options: {
template: "html_builder.DeviceVisibilityOption",
selector: this.selector,
exclude: ".s_col_no_resize.row > div, .s_masonry_block .s_col_no_resize",
clean_for_save_handlers_options: this.cleanForSave,
cleanForSave: this.cleanForSave.bind(this),
},
builder_actions: this.getActions(),
target_show: this.onTargetShow.bind(this),
target_hide: this.onTargetHide.bind(this),
};

cleanForSave(editingEl) {
this.dependencies.visibilityPlugin.cleanForSaveVisibility(editingEl);
editingEl.classList.remove("o_snippet_override_invisible");
}
getActions() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ class ProgressBarOptionPlugin extends Plugin {
builder_options: {
template: "html_builder.ProgressBarOption",
selector: this.selector,
cleanForSave: this.cleanForSave.bind(this),
},
builder_actions: this.getActions(),
clean_for_save_handlers_options: this.cleanForSave,
};

cleanForSave(editingEl) {
Expand Down
Loading