DEV Community

vuejstest
vuejstest

Posted on

custom row task step by step

src/dataresolver
CustomImageCmsElementResolver

<?php
declare(strict_types=1);

namespace CmsTheme2\DataResolver;

use Shopware\Core\Content\Cms\Aggregate\CmsSlot\CmsSlotEntity;
use Shopware\Core\Content\Cms\DataResolver\Element\AbstractCmsElementResolver;
use Shopware\Core\Content\Cms\DataResolver\Element\ElementDataCollection;
use Shopware\Core\Content\Cms\DataResolver\ResolverContext\ResolverContext;
use Shopware\Core\Content\Cms\DataResolver\CriteriaCollection;
use Shopware\Core\Content\Media\MediaDefinition;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
use Shopware\Core\Content\Cms\DataResolver\FieldConfig;
use Shopware\Core\Content\Cms\DataResolver\ResolverContext\EntityResolverContext;
use Shopware\Core\Content\Cms\SalesChannel\Struct\ImageStruct;
use Shopware\Core\Content\Media\MediaEntity;
use Shopware\Core\Framework\Struct\ArrayEntity;
use Shopware\Core\Framework\Uuid\Uuid;

class CustomImageCmsElementResolver extends AbstractCmsElementResolver
{
    public function getType(): string
    {
        return 'custom-image';
    }
    public function collect(CmsSlotEntity $slot, ResolverContext $resolverContext): ?CriteriaCollection
    {
        $config = $slot->getFieldConfig();
        $imageConfig = $config->get('media');
        $backgroundImageConfig = $config->get('mobileMedia');

        $ids = [];
        if (!$imageConfig || $imageConfig->isMapped() || $imageConfig->getValue() === null) {
            //
        } else {
            array_push($ids, $imageConfig->getValue());
        }

        if (!$backgroundImageConfig || $backgroundImageConfig->isMapped() || $backgroundImageConfig->getValue() === null) {
            //
        } else {
            array_push($ids, $backgroundImageConfig->getValue());
        }
        if (count($ids) === 0) {
            return null;
        }
        $criteria = new Criteria($ids);
        $criteriaCollection = new CriteriaCollection();
        $criteriaCollection->add('media_' . $slot->getUniqueIdentifier(), MediaDefinition::class, $criteria);
        return $criteriaCollection;
    }
    public function enrich(CmsSlotEntity $slot, ResolverContext $resolverContext, ElementDataCollection $result): void
    {
        $config = $slot->getFieldConfig();
        $data = new ArrayEntity();
        $data->setUniqueIdentifier(Uuid::randomHex());
        $slot->setData($data);
        $image = new ImageStruct();
        $backgroundImage = new ImageStruct();
        $imageConfig = $config->get('media');
        $backgroundImageConfig = $config->get('mobileMedia');
        if ($imageConfig && $imageConfig->getValue()) {
            $this->addMediaEntity($slot, $image, $result, $imageConfig, $resolverContext);
        }
        $data->set('media', $image);

        if ($backgroundImageConfig && $backgroundImageConfig->getValue()) {
            $this->addMediaEntity($slot, $backgroundImage, $result, $backgroundImageConfig, $resolverContext);
        }
        $data->set('mobileMedia', $backgroundImage);
    }

    private function addMediaEntity(
        CmsSlotEntity         $slot,
        ImageStruct           $image,
        ElementDataCollection $result,
        FieldConfig           $config,
        ResolverContext       $resolverContext
    ): void
    {
        if ($config->isMapped() && $resolverContext instanceof EntityResolverContext) {
            /** @var MediaEntity|null $media */
            $media = $this->resolveEntityValue($resolverContext->getEntity(), $config->getValue());

            if ($media !== null) {
                $image->setMediaId($media->getUniqueIdentifier());
                $image->setMedia($media);
            }
        }

        if ($config->isStatic()) {
            $image->setMediaId($config->getValue());
            $searchResult = $result->get('media_' . $slot->getUniqueIdentifier());
            if (!$searchResult) {
                return;
            }

            /** @var MediaEntity|null $media */
            $media = $searchResult->get($config->getValue());

            if (!$media) {
                return;
            }
            $image->setMedia($media);
        }
    }
}

res
app
 admin/src
  module/sw-cms
   blocks/text-image
    custom-image-text-gallery
      component
        index

import template from './sw-cms-block-custom-image-text-gallery.html.twig';
import './sw-cms-block-custom-image-text-gallery.scss';

Shopware.Component.register('sw-cms-block-custom-image-text-gallery', template, {
    template,

    compatConfig: Shopware.compatConfig,
});
twig

{% block sw_cms_block_custom_image_text_gallery %}
    <div class="sw-cms-block-image-text-gallery">
        <div class="sw-cms-block-image-text-gallery__left">
            <slot name="left-image"></slot>
            <slot name="left-text"></slot>
            <slot name="left-button"></slot>
        </div>
        <div class="sw-cms-block-image-text-gallery__center-left">
            <slot name="center-left-image"></slot>
            <slot name="center-left-text"></slot>
            <slot name="center-left-button"></slot>
        </div>
        <div class="sw-cms-block-image-text-gallery__center-right">
            <slot name="center-right-image"></slot>
            <slot name="center-right-text"></slot>
            <slot name="center-right-button"></slot>
        </div>
        <div class="sw-cms-block-image-text-gallery__right">
            <slot name="right-image"></slot>
            <slot name="right-text"></slot>
            <slot name="right-button"></slot>
        </div>
    </div>
{% endblock %}
scss

.sw-cms-block-image-text-gallery {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(195px, 1fr));
  align-content: stretch;
  align-items: start;
  grid-gap: 40px;

  .sw-cms-el-text {
    padding: 20px;
  }

  .sw-cms-el-image.is--cover {
    min-height: 100px;
  }

  .sw-cms-block-image-text-gallery__left,
  .sw-cms-block-image-text-gallery__center-left,
  .sw-cms-block-image-text-gallery__center-right,
  .sw-cms-block-image-text-gallery__right {
    box-shadow: 0 0 4px 0 rgba(0, 0, 0, 20%);
  }
}
.sw-cms-block-image-text-gallery-shop-button {
  display: grid;
  align-items: center;
}

      preview
index

import template from './sw-cms-preview-custom-image-text-gallery.html.twig';
import './sw-cms-preview-custom-image-text-gallery.scss';

Shopware.Component.register("sw-cms-preview-custom-image-text-gallery", {
    template,
    compatConfig: Shopware.compatConfig,
    computed: {
        assetFilter() {
            return Shopware.Filter.getByName('asset');
        },
    },
});

twig

{% block sw_cms_block_custom_image_text_gallery_preview %}
    <div class="sw-cms-preview-custom-image-text-gallery">
        <div class="sw-cms-preview-custom-image-text-gallery__left">
            <div class="sw-cms-preview-custom-image-text-gallery__image">
                <img :src="assetFilter('/administration/static/img/cms/preview_camera_small.jpg')" alt="">
            </div>
            <div class="sw-cms-preview-custom-image-text-gallery__text">
                <h2>block 1</h2>
                <p>this is block1</p>
            </div>
            <div class="sw-cms-slot sw-cms-slot-left-button">
                <div class="sw-cms-el-buy-button">
                    <div class="sw-cms-el-buy-button__actions">
                        <a class="sw-cms-el-buy-button__buy-action sw-button sw-button--small">Shop</a>
                    </div>
                </div>
                <div class="sw-cms-slot__preview-overlay"></div>
            </div>
        </div>

        <div class="sw-cms-preview-custom-image-text-gallery__center-left">
            <div class="sw-cms-preview-custom-image-text-gallery__image">
                <img :src="assetFilter('/administration/static/img/cms/preview_plant_small.jpg')" alt="">
            </div>
            <div class="sw-cms-preview-custom-image-text-gallery__text">
                <h2>block 2</h2>
                <p>this is block2</p>
            </div>
            <div class="sw-cms-slot sw-cms-slot-center-left-button">
                <div class="sw-cms-el-buy-button">
                    <div class="sw-cms-el-buy-button__actions">
                        <a class="sw-cms-el-buy-button__buy-action sw-button sw-button--small">Shop</a>
                    </div>
                </div>
                <div class="sw-cms-slot__preview-overlay"></div>
            </div>
        </div>

        <div class="sw-cms-preview-custom-image-text-gallery__center-right">
            <div class="sw-cms-preview-custom-image-text-gallery__image">
                <img :src="assetFilter('/administration/static/img/cms/preview_glasses_small.jpg')" alt="">
            </div>
            <div class="sw-cms-preview-custom-image-text-gallery__text">
                <h2>block 3</h2>
                <p>this is block3</p>
            </div>
            <div class="sw-cms-slot sw-cms-slot-center-right-button">
                <div class="sw-cms-el-buy-button">
                    <div class="sw-cms-el-buy-button__actions">
                        <a class="sw-cms-el-buy-button__buy-action sw-button sw-button--small">Shop</a>
                    </div>
                </div>
                <div class="sw-cms-slot__preview-overlay"></div>
            </div>
        </div>

        <div class="sw-cms-preview-custom-image-text-gallery__right">
            <div class="sw-cms-preview-custom-image-text-gallery__image">
                <img :src="assetFilter('/administration/static/img/cms/preview_glasses_small.jpg')" alt="">
            </div>
            <div class="sw-cms-preview-custom-image-text-gallery__text">
                <h2>block 4</h2>
                <p>this is block4</p>
            </div>
            <div class="sw-cms-slot sw-cms-slot-right-button">
                <div class="sw-cms-el-buy-button">
                    <div class="sw-cms-el-buy-button__actions">
                        <a class="sw-cms-el-buy-button__buy-action sw-button sw-button--small">Shop</a>
                    </div>
                </div>
                <div class="sw-cms-slot__preview-overlay"></div>
            </div>
        </div>
    </div>
{% endblock %}

scss

.sw-cms-preview-custom-image-text-gallery {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  grid-gap: 10px;
  padding: 15px;

  .sw-cms-preview-custom-image-text-gallery__left,
  .sw-cms-preview-custom-image-text-gallery__center-left,
  .sw-cms-preview-custom-image-text-gallery__center-right,
  .sw-cms-preview-custom-image-text-gallery__right {
    box-shadow: 0 0 3px 0 rgba(0, 0, 0, 20%);
  }

  .sw-cms-preview-custom-image-text-gallery__image {
    height: 60px;
  }

  .sw-cms-preview-custom-image-text-gallery__text {
    padding: 8px;
    text-align: center;
  }

  img {
    display: block;
    object-fit: cover;
    width: 100%;
    height: 100%;
  }
}
.sw-cms-block-image-text-gallery-shop-button {
  display: grid;
  align-items: center;
  .sw-button {
    height: 30px;
    width: auto;
    padding: 25px;
  }
  .sw-cms-el-buy-button__buy-action{
    font-size: smaller;
  }
}

      index.js

import CMS from '../../../constant/sw-cms.constant';
import "./preview";
import "./component";

Shopware.Service("cmsService").registerCmsBlock({
    name: "custom-image-text-gallery",
    label: "Custom Image Text Gallery Block",
    category: "text-image",
    component: "sw-cms-block-custom-image-text-gallery",
    previewComponent: "sw-cms-preview-custom-image-text-gallery",
    defaultConfig: {
        marginBottom: "20px",
        marginTop: "20px",
        marginLeft: "20px",
        marginRight: "20px",
        sizingMode: "boxed",
    },
    slots: {
        "left-image": {
            type: "image",
            default: {
                config: {
                    displayMode: { source: "static", value: "cover" },
                },
                data: {
                    media: {
                        value: "bundles/administration/static/img/cms/preview_camera_large.jpg",
                        source: "default",
                    },
                },
            },
        },
        "left-text": {
            type: "text",
            default: {
                config: {
                    content: {
                        source: "static",
                        value: `
              <h2 style="text-align: center;">block 1</h2>
              <p style="text-align: center;">this is block1</p>
            `.trim(),
                    },
                },
            },
        },
        "left-button": {
            type: "buy-button",
            default: {
                config: {
                    name: {
                        source: "static",
                        value: "Shop",
                        required: true,
                    },
                    link: {
                        source: "static",
                        value: null,
                    },
                },
            },
        },

        "center-left-image": {
            type: "image",
            default: {
                config: {
                    displayMode: { source: "static", value: "cover" },
                },
                data: {
                    media: {
                        value: "bundles/administration/static/img/cms/preview_glasses_large.jpg",
                        source: "default",
                    },
                },
            },
        },
        "center-left-text": {
            type: "text",
            default: {
                config: {
                    content: {
                        source: "static",
                        value: `
              <h2 style="text-align: center;">block 2</h2>
              <p style="text-align: center;">this is block2</p>
            `.trim(),
                    },
                },
            },
        },
        "center-left-button": {
            type: "buy-button",
            default: {
                config: {
                    name: {
                        source: "static",
                        value: "Shop",
                        required: true,
                    },
                    link: {
                        source: "static",
                        value: null,
                    },
                },
            },
        },

        "center-right-image": {
            type: "image",
            default: {
                config: {
                    displayMode: { source: "static", value: "cover" },
                },
                data: {
                    media: {
                        value: "bundles/administration/static/img/cms/preview_plant_large.jpg",
                        source: "default",
                    },
                },
            },
        },
        "center-right-text": {
            type: "text",
            default: {
                config: {
                    content: {
                        source: "static",
                        value: `
              <h2 style="text-align: center;">block3</h2>
              <p style="text-align: center;">this is block3</p>
            `.trim(),
                    },
                },
            },
        },
        "center-right-button": {
            type: "buy-button",
            default: {
                config: {
                    name: {
                        source: "static",
                        value: "Shop",
                        required: true,
                    },
                    link: {
                        source: "static",
                        value: null,
                    },
                },
            },
        },

        "right-image": {
            type: "image",
            default: {
                config: {
                    displayMode: { source: "static", value: "cover" },
                },
                data: {
                    media: {
                        value: "bundles/administration/static/img/cms/preview_camera_large.jpg",
                        source: "default",
                    },
                },
            },
        },
        "right-text": {
            type: "text",
            default: {
                config: {
                    content: {
                        source: "static",
                        value: `
              <h2 style="text-align: center;">block 4</h2>
              <p style="text-align: center;">this is block4</p>
            `.trim(),
                    },
                },
            },
        },
        "right-button": {
            type: "buy-button",
            default: {
                config: {
                    name: {
                        source: "static",
                        value: "Shop",
                        required: true,
                    },
                    link: {
                        source: "static",
                        value: null,
                    },
                },
            },
        },
    },
});

    image-text-gallery/custom-image-text-gallery
index.js

import CMS from '../../../constant/sw-cms.constant';
import "./preview";
import "./component";

Shopware.Service("cmsService").registerCmsBlock({
    name: "custom-image-text-gallery",
    label: "Custom Image Text Gallery Block",
    category: "text-image",
    component: "sw-cms-block-custom-image-text-gallery",
    previewComponent: "sw-cms-preview-custom-image-text-gallery",
    defaultConfig: {
        marginBottom: "20px",
        marginTop: "20px",
        marginLeft: "20px",
        marginRight: "20px",
        sizingMode: "boxed",
    },
    slots: {
        "left-image": {
            type: "image",
            default: {
                config: {
                    displayMode: { source: "static", value: "cover" },
                },
                data: {
                    media: {
                        value: "bundles/administration/static/img/cms/preview_camera_large.jpg",
                        source: "default",
                    },
                },
            },
        },
        "left-text": {
            type: "text",
            default: {
                config: {
                    content: {
                        source: "static",
                        value: `
              <h2 style="text-align: center;">block 1</h2>
              <p style="text-align: center;">this is block1</p>
            `.trim(),
                    },
                },
            },
        },
        "left-button": {
            type: "buy-button",
            default: {
                config: {
                    name: {
                        source: "static",
                        value: "Shop",
                        required: true,
                    },
                    link: {
                        source: "static",
                        value: null,
                    },
                },
            },
        },

        "center-left-image": {
            type: "image",
            default: {
                config: {
                    displayMode: { source: "static", value: "cover" },
                },
                data: {
                    media: {
                        value: "bundles/administration/static/img/cms/preview_glasses_large.jpg",
                        source: "default",
                    },
                },
            },
        },
        "center-left-text": {
            type: "text",
            default: {
                config: {
                    content: {
                        source: "static",
                        value: `
              <h2 style="text-align: center;">block 2</h2>
              <p style="text-align: center;">this is block2</p>
            `.trim(),
                    },
                },
            },
        },
        "center-left-button": {
            type: "buy-button",
            default: {
                config: {
                    name: {
                        source: "static",
                        value: "Shop",
                        required: true,
                    },
                    link: {
                        source: "static",
                        value: null,
                    },
                },
            },
        },

        "center-right-image": {
            type: "image",
            default: {
                config: {
                    displayMode: { source: "static", value: "cover" },
                },
                data: {
                    media: {
                        value: "bundles/administration/static/img/cms/preview_plant_large.jpg",
                        source: "default",
                    },
                },
            },
        },
        "center-right-text": {
            type: "text",
            default: {
                config: {
                    content: {
                        source: "static",
                        value: `
              <h2 style="text-align: center;">block 3</h2>
              <p style="text-align: center;">this is block3</p>
            `.trim(),
                    },
                },
            },
        },
        "center-right-button": {
            type: "buy-button",
            default: {
                config: {
                    name: {
                        source: "static",
                        value: "Shop",
                        required: true,
                    },
                    link: {
                        source: "static",
                        value: null,
                    },
                },
            },
        },

        "right-image": {
            type: "image",
            default: {
                config: {
                    displayMode: { source: "static", value: "cover" },
                },
                data: {
                    media: {
                        value: "bundles/administration/static/img/cms/preview_camera_large.jpg",
                        source: "default",
                    },
                },
            },
        },
        "right-text": {
            type: "text",
            default: {
                config: {
                    content: {
                        source: "static",
                        value: `
              <h2 style="text-align: center;">block 4</h2>
              <p style="text-align: center;">this is block4</p>
            `.trim(),
                    },
                },
            },
        },
        "right-button": {
            type: "buy-button",
            default: {
                config: {
                    name: {
                        source: "static",
                        value: "Shop",
                        required: true,
                    },
                    link: {
                        source: "static",
                        value: null,
                    },
                },
            },
        },
    },
});

    text-image-text
component
index.js

import template from './sw-cms-block-text-image-text.html.twig';
import './sw-cms-block-text-image-text.scss';

Shopware.Component.register('sw-cms-block-text-image-text', {
    template,

    compatConfig: Shopware.compatConfig,
});
twig

{% block sw_cms_block_text_image_text %}
    <div class="sw-cms-block-text-image-text">
        <div class="sw-cms-block-text-image-text__text-left">
            <slot name="left"></slot>
        </div>
        <div class="sw-cms-block-text-image-text__image">
            <slot name="center"></slot>
        </div>
        <div class="sw-cms-block-text-image-text__text-right">
            <slot name="right"></slot>
        </div>
    </div>
{% endblock %}
scss

.sw-cms-block-text-image-text {
  display: flex;
  flex-wrap: wrap;

  .sw-cms-block-text-image-text__text-left,
  .sw-cms-block-text-image-text__text-right {
    flex-grow: 2;
    flex-shrink: 0;
    flex-basis: 300px;
  }
  .sw-cms-block-text-image-text__image {
    flex-grow: 1;
    flex-shrink: 0;
    flex-basis: 240px;
  }
  .sw-cms-el-image.is--cover {
    min-height: 340px;
  }
  .sw-cms-el-text {
    padding: 30px;
  }
}

preview
inddex.js

import template from './sw-cms-preview-text-image-text.html.twig';
import './sw-cms-preview-text-image-text.scss';

Shopware.Component.register('sw-cms-preview-text-image-text', {
    template,

    compatConfig: Shopware.compatConfig,

    computed: {
        assetFilter() {
            return Shopware.Filter.getByName('asset');
        },
    },
});

twig

{% block sw_cms_block_text_image_text_preview %}
    <div class="sw-cms-preview-text-image-text">
        <div class="sw-cms-preview-text-image-text__text-left">
            <h2>Hello</h2>
            <p>world</p>
        </div>
        <img :src="assetFilter('/administration/static/img/cms/preview_glasses_small.jpg')"
                alt="">
        <div class="sw-cms-preview-text-image-text__text-right">
            <h2>Hello</h2>
            <p>world</p>
        </div>
    </div>
{% endblock %}

scss

.sw-cms-preview-text-image-text {
  display: grid;
  grid-template-columns: 1fr 2fr 1fr;
  padding: 15px;

  .sw-cms-preview-text-image-text__text-left,
  .sw-cms-preview-text-image-text__text-right {
    padding: 15px;
    text-align: center;
    height: 120px;
  }

  img {
    display: block;
    object-fit: cover;
    width: 100%;
    height: 100%;
  }
}

index.js

import CMS from "../../../constant/sw-cms.constant";
import "./preview";
import "./component";

Shopware.Service("cmsService").registerCmsBlock({
    name: "text-image-text",
    label: "Custom text-image-text",
    category: "text-image",
    component: "sw-cms-block-text-image-text",
    previewComponent: "sw-cms-preview-text-image-text",
    defaultConfig: {
        marginBottom: "20px",
        marginTop: "20px",
        marginLeft: "20px",
        marginRight: "20px",
        sizingMode: "boxed",
    },
    slots: {
        left: {
            type: "text",
            default: {
                config: {
                    content: {
                        source: "static",
                        value: `
                        <h2 style="text-align: center;">Hello</h2>
                        <p style="text-align: center;">world</p>
                        `.trim(),
                    },
                },
            },
        },
        center: {
            type: "custom-image",
            default: {
                config: {
                    displayMode: { source: "static", value: "cover" },
                },
                data: {
                    media: {
                        value: CMS.MEDIA.previewGlasses,
                        source: "default",
                    },
                },
            },
        },
        right: {
            type: "text",
            default: {
                config: {
                    content: {
                        source: "static",
                        value: `
                        <h2 style="text-align: center;">hello</h2>
                        <p style="text-align: center;">world</p>
                        `.trim(),
                    },
                },
            },
        },
    },
});

   constant
sw-cms.constant.ts

const uniqueSlotsKebab = [
    'buy-box',
    'product-description-reviews',
    'cross-selling',
];

/**
 * @sw-package discovery
 */
// eslint-disable-next-line sw-deprecation-rules/private-feature-declarations
export default Object.freeze({
    REQUIRED_FIELD_ERROR_CODE: 'c1051bb4-d103-4f74-8988-acbcafc7fdc3',
    PAGE_TYPES: {
        SHOP: 'page',
        LANDING: 'landingpage',
        LISTING: 'product_list',
        PRODUCT_DETAIL: 'product_detail',
    },
    TYPE_MAPPING_ENTITIES: {
        product_detail: {
            entity: 'product',
            mode: 'single',
        },
        product_list: {
            entity: 'category',
            mode: 'single',
        },
    },
    UNIQUE_SLOTS: uniqueSlotsKebab.map((slotName) => slotName.replace(/-./g, (char) => char.toUpperCase()[1])),
    UNIQUE_SLOTS_KEBAB: uniqueSlotsKebab,
    SLOT_POSITIONS: {
        left: 0,
        'left-image': 100,
        'left-top': 200,
        'left-text': 300,
        'left-bottom': 400,
        'center-left': 1000,
        center: 1100,
        'center-image': 1200,
        'center-top': 1300,
        'center-text': 1400,
        'center-bottom': 1500,
        'center-right': 1600,
        right: 2000,
        'right-image': 2100,
        'right-top': 2200,
        'right-text': 2300,
        'right-bottom': 2400,
        content: 3000,
        image: 3100,
        video: 3200,
        imageSlider: 3300,
        default: 5000,
    },
    MEDIA: {
        previewCamera: 'bundles/administration/static/img/cms/preview_camera_large.jpg',
        previewMountain: 'bundles/administration/static/img/cms/preview_mountain_large.jpg',
        previewPlant: 'bundles/administration/static/img/cms/preview_plant_large.jpg',
        previewGlasses: 'bundles/administration/static/img/cms/preview_glasses_large.jpg',
        SMALL:{
            previewCamera: 'framework/assets/default/cms/preview_camera_small.jpg',
            previewMountain: 'framework/assets/default/cms/preview_mountain_small.jpg',
            previewPlant: 'framework/assets/default/cms/preview_plant_small.jpg',
            previewGlasses: 'framework/assets/default/cms/preview_glasses_small.jpg',
        }
    },
});

   elements
custom-image
componeny
index.js

import CMS from '../../../constant/sw-cms.constant';
import template from './sw-cms-el-custom-image.html.twig';
import './sw-cms-el-custom-image.scss';

const { Component, Mixin, Filter } = Shopware;

Component.register('sw-cms-el-custom-image', {
    template,

    mixins: [
        Mixin.getByName('cms-element'),
    ],

    computed: {
        mediaUrl() {
            const fallBackImageFileName = CMS.MEDIA.previewPlant.slice(CMS.MEDIA.previewPlant.lastIndexOf('/') + 1);
            const staticFallBackImage = this.assetFilter(`administration/static/img/cms/${fallBackImageFileName}`);
            const elemData = this.element?.data?.media || {};
            const elemConfig = this.element?.config?.media || {};

            if (elemConfig.source === 'mapped') {
                const demoMedia = this.getDemoValue(elemConfig.value);
                if (demoMedia?.url) {
                    return demoMedia.url;
                }
                return staticFallBackImage;
            }

            if (elemConfig.source === 'default' && typeof elemConfig.value === 'string') {
                const fileName = elemConfig.value.slice(elemConfig.value.lastIndexOf('/') + 1);
                return this.assetFilter(`/administration/static/img/cms/${fileName}`);
            }

            if (elemData?.id) {
                return this.element.data.media.url;
            }

            if (elemData?.url) {
                return elemData.url;
            }

            return staticFallBackImage;
        },

        assetFilter() {
            return Filter.getByName('asset');
        },

        mediaConfigValue() {
            return this.element?.config?.media?.value;
        },
    },

    watch: {
        'cmsPageState.currentDemoEntity': {
            handler() {
                this.updateDemoValue(this.mediaConfigValue);
            },
        },

        mediaConfigValue(value) {
            this.updateDemoValue(value);
        },
    },

    created() {
        this.createdComponent();
    },

    methods: {
        createdComponent() {
            this.initElementConfig('custom-image');
            this.initElementData('custom-image');
        },
        updateDemoValue(value) {
            const mediaId = this.element?.data?.media?.id;
            const isSourceStatic = this.element?.config?.media?.source === 'static';

            if (isSourceStatic && mediaId && value !== mediaId) {
                this.element.config.media.value = mediaId;
            }
        },
    },
});

twig

{% block sw_cms_element_custom_image %}
    <div class="sw-cms-el-custom-image">
        {% block sw_cms_element_custom_image_content %}
            <img :src="mediaUrl" alt="">
        {% endblock %}
    </div>
{% endblock %}

scss

.sw-cms-el-custom-image {
    display: flex;
    max-width: 100%;

    img {
        display: block;
        max-width: 100%;
    }

    &.is--cover {
        height: 100%;
        width: 100%;
        position: relative;
        margin: auto;
        border-radius: 50%;
        background-clip: padding-box;
        overflow: hidden;

        img {
            object-fit: cover;
            width: 100%;
            height: 100%;
            position: absolute;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
        }
    }

    &.is--stretch {
        img {
            width: 100%;
        }
    }
}

config
index.js
import template from './sw-cms-el-config-custom-image.html.twig';
import './sw-cms-el-config-custom-image.scss';

const { Component, Mixin } = Shopware;

Component.register('sw-cms-el-config-custom-image', {
    template,

    compatConfig: Shopware.compatConfig,

    inject: ['repositoryFactory'],

    emits: ['element-update'],

    mixins: [
        Mixin.getByName('cms-element'),
    ],

    data() {
        return {
            mediaModalIsOpen: false,
            initialFolderId: null,

        };
    },
    computed: {
        mediaRepository() {
            return this.repositoryFactory.create('media');

        },
        uploadTag() {
            return `cms-element-media-config-${this.element.id}`;
        },
        previewSource() {
            if (this.element?.data?.media?.id) {
                return this.element.data.media;
            }
            return this.element.config.media.value;
        },
    },
    created() {
        this.createdComponent();
    },

    methods: {
        createdComponent() {
            this.initElementConfig('image');
        },

        async onImageUpload({ targetId }) {
            const mediaEntity = await this.mediaRepository.get(targetId);

            this.element.config.media.value = mediaEntity.id;
            this.element.config.media.source = 'static';

            this.updateElementData(mediaEntity);

            this.$emit('element-update', this.element);
        },

        onImageRemove() {
            this.element.config.media.value = null;

            this.updateElementData();

            this.$emit('element-update', this.element);
        },

        onCloseModal() {
            this.mediaModalIsOpen = false;
        },

        onSelectionChanges(mediaEntity) {
            const media = mediaEntity[0];
            this.element.config.media.value = media.id;
            this.element.config.media.source = 'static';

            this.updateElementData(media);

            this.$emit('element-update', this.element);
        },

        updateElementData(media = null) {
            const mediaId = media === null ? null : media.id;
            if (!this.element.data) {
                if (this.isCompatEnabled('INSTANCE_SET')) {
                    this.$set(this.element, 'data', { mediaId, media });
                } else {
                    this.element.data = { mediaId, media };
                }
                return;
            }
            if (this.isCompatEnabled('INSTANCE_SET')) {
                this.$set(this.element.data, 'mediaId', mediaId);
                this.$set(this.element.data, 'media', media);
            } else {
                this.element.data.mediaId = mediaId;
                this.element.data.media = media;
            }
        },

        onOpenMediaModal() {
            this.mediaModalIsOpen = true;
        },

        onChangeMinHeight(value) {
            this.element.config.minHeight.value = value === null ? '' : value;

            this.$emit('element-update', this.element);
        },

        onChangeDisplayMode() {
            this.$emit('element-update', this.element);
        },

        onChangeIsDecorative(value) {
            this.element.config.isDecorative.value = value;

            this.$emit('element-update', this.element);
        },
    },
});
twig


{% block sw_cms_element_image_config %}
    <div class="sw-cms-el-config-image">
        {% block sw_cms_element_image_config_media_upload %}
            <sw-cms-mapping-field v-model:config="element.config.media" :label="$tc('sw-cms.elements.image.label')" value-types="entity" entity="media">
                <sw-media-upload-v2
                        variant="regular"
                        :upload-tag="uploadTag"
                        :source="previewSource"
                        :allow-multi-select="false"
                        :default-folder="cmsPageState.pageEntityName"
                        :caption="$tc('sw-cms.elements.general.config.caption.mediaUpload')"
                        @media-upload-sidebar-open="onOpenMediaModal"
                        @media-upload-remove-image="onImageRemove"/>

                <template #preview="{ demoValue }">
                    <div class="sw-cms-el-config-image__mapping-preview">
                        <img v-if="demoValue.url" :src="demoValue.url" alt="">
                        <sw-alert v-else class="sw-cms-el-config-image__preview-info" variant="info">
                            {{ $tc('sw-cms.detail.label.mappingEmptyPreview') }}
                        </sw-alert>
                    </div>
                </template>
            </sw-cms-mapping-field>
            <sw-upload-listener
                    :upload-tag="uploadTag"
                    auto-upload @media-upload-finish="onImageUpload"/>
        {% endblock %}
        {% block sw_cms_element_image_config_display_mode %}
            <sw-select-field v-model:value="element.config.displayMode.value"
                             class="sw-cms-el-config-image__display-mode"
                             :label="$tc('sw-cms.elements.general.config.label.displayMode')"
                             :help-text="$tc('sw-cms.elements.general.config.helpText.displayMode')"
                             @update:value="onChangeDisplayMode">
                <option value="standard">
                    {{ $tc('sw-cms.elements.general.config.label.displayModeStandard') }}
                </option>
                <option value="stretch">
                    {{ $tc('sw-cms.elements.general.config.label.displayModeStretch') }}
                </option>
                <option value="cover">
                    {{ $tc('sw-cms.elements.general.config.label.displayModeCover') }}
                </option>
            </sw-select-field>
        {% endblock %}

        <template v-if="element.config.displayMode.value === 'cover'">
            {% block sw_cms_element_image_config_min_height %}
                <sw-text-field v-model:value="element.config.minHeight.value" :label="$tc('sw-cms.elements.image.config.label.minHeight')" :placeholder="$tc('sw-cms.elements.image.config.placeholder.enterMinHeight')" @update:value="onChangeMinHeight"/>
            {% endblock %}
        </template>

        {% block sw_cms_element_image_config_vertical_align %}
            <sw-select-field v-model:value="element.config.verticalAlign.value" :label="$tc('sw-cms.elements.general.config.label.verticalAlign')" :placeholder="$tc('sw-cms.elements.general.config.label.verticalAlign')" :disabled="['cover'].includes(element.config.displayMode.value)">
                <option value="flex-start">
                    {{ $tc('sw-cms.elements.general.config.label.verticalAlignTop') }}
                </option>
                <option value="center">
                    {{ $tc('sw-cms.elements.general.config.label.verticalAlignCenter') }}
                </option>
                <option value="flex-end">
                    {{ $tc('sw-cms.elements.general.config.label.verticalAlignBottom') }}
                </option>
            </sw-select-field>
        {% endblock %}

        {% block sw_cms_element_image_config_horizontal_align %}
            <sw-select-field
                    v-model:value="element.config.horizontalAlign.value"
                    :label="$tc('sw-cms.elements.general.config.label.horizontalAlign')"
                    :placeholder="$tc('sw-cms.elements.general.config.label.horizontalAlign')"
                    :disabled="element.config.displayMode.value === 'cover'">
                <option value="flex-start">
                    {{ $tc('sw-cms.elements.general.config.label.horizontalAlignLeft') }}
                </option>
                <option value="center">
                    {{ $tc('sw-cms.elements.general.config.label.horizontalAlignCenter') }}
                </option>
                <option value="flex-end">
                    {{ $tc('sw-cms.elements.general.config.label.horizontalAlignRight') }}
                </option>
            </sw-select-field>
        {% endblock %}

        {% block sw_cms_element_image_config_link %}
            <div class="sw-cms-el-config-image__link">
                <sw-dynamic-url-field
                        v-model:value="element.config.url.value"/>
                <sw-switch-field
                        v-model:value="element.config.newTab.value"
                        class="sw-cms-el-config-image__link-tab"
                        :label="$tc('sw-cms.elements.image.config.label.linkNewTab')"/>
            </div>
        {% endblock %}

        <mt-switch class="sw-cms-el-config-image__is-decorative" :checked="element.config.isDecorative.value" :label="$tc('sw-cms.elements.image.config.label.isDecorative')" @change="onChangeIsDecorative"/>

        {% block sw_cms_element_image_config_media_modal %}
            <sw-media-modal-v2
                    v-if="mediaModalIsOpen"
                    variant="full"
                    :caption="$tc('sw-cms.elements.general.config.caption.mediaUpload')"
                    :entity-context="cmsPageState.entityName"
                    :allow-multi-select="false"
                    :initial-folder-id="cmsPageState.defaultMediaFolderId"
                    @media-upload-remove-image="onImageRemove"
                    @media-modal-selection-change="onSelectionChanges"
                    @modal-close="onCloseModal"/>
        {% endblock %}
    </div>
{% endblock %}
scs

.sw-cms-el-config-image {
    .sw-media-upload-v2 {
        margin-bottom: 22px;

        .sw-media-upload-v2__header {
            display: none;
        }
    }

    .sw-cms-el-config-image__mapping-preview {
        display: grid;
        width: 100%;
        height: 200px;
        justify-items: center;
        align-items: start;

        img {
            width: 100%;
            height: 100%;
            object-fit: contain;
            overflow: hidden;
        }

        .sw-cms-el-config-image__preview-info {
            width: 100%;
        }
    }
}

preview
index.js

import template from "./sw-cms-el-preview-custom-image.html.twig";
import "./sw-cms-el-preview-custom-image.scss";

const { Component } = Shopware;

Component.register("sw-cms-el-preview-custom-image", {
    template,

    computed: {
        assetFilter() {
            return Shopware.Filter.getByName("asset");
        },
    },
});
twig

{% block sw_cms_element_image_preview %}
    <div class="sw-cms-el-preview-image">
        <img :src="assetFilter('/administration/static/img/cms/preview_mountain_small.jpg')" alt="">
    </div>
{% endblock %}
scss

.sw-cms-el-preview-image {
    width: 100%;
    height: 100%;
    overflow: hidden;

    img {
        margin: 0;
        display: block;
        width: 110%;
    }
}

index.js

import './component';
import './config';
import './preview';

Shopware.Service('cmsService').registerCmsElement({
    name: 'custom-image',
    label: 'Custom Image',
    component: 'sw-cms-el-custom-image',
    configComponent: 'sw-cms-el-config-custom-image',
    previewComponent: 'sw-cms-el-preview-custom-image',
    defaultConfig: {
        media: {
            source: 'static',
            value: null,
            required: true,
            entity: {
                name: 'media',
            },
        },
        url: {
            source: 'static',
            value: null,
        },
    },
});

   snippet
de

{
  "sw-cms": {
    "elements": {
      "general": {
        "config": {
          "caption": {
            "mediaUpload": "Upload an image"
          },
          "label": {
            "verticalAlign": "Vertical align",
            "alignment": "Alignment",
            "verticalAlignTop": "Top",
            "verticalAlignCenter": "Center",
            "verticalAlignBottom": "Bottom",
            "displayMode": "Display mode",
            "displayModeStandard": "Original",
            "displayModeCover": "Cropped",
            "displayModeStretch": "Scaled",
            "displayModeContain": "Fixed height",
            "autoSlide": "Automatic transition",
            "autoplayTimeout": "Delay",
            "speed": "Animation duration",
            "horizontalAlign": "Horizontal align",
            "horizontalAlignLeft": "Left",
            "horizontalAlignCenter": "Center",
            "horizontalAlignRight": "Right"
          },
          "helpText": {
            "autoSlide": "Temporarily disabled due to lack of accessibility.",
            "autoplayTimeout": "The millisecond duration for how long the slide should stay still.",
            "speed": "The millisecond duration of the sliding animation.",
            "displayMode": "<strong>Original:</strong> The image is displayed in its original resolution or shrunk down to current CMS page's width.<br><br><strong>Scaled:</strong> The image is scaled to the CMS page's width without any height restrictions.<br><br><strong>Cropped:</strong> The image is scaled to the current CMS page's width. Any vertical overflow is cropped to the configured maximum height."
          },
          "tab": {
            "content": "Content",
            "settings": "Settings",
            "options": "Options"
          },
          "infoText": {
            "listingElement": "This element's product data is automatically loaded by the category associated with this layout. The content just shows a sample preview."
          }
        },
        "switch": {
          "groups": {
            "all": "All elements",
            "favorites": "Favourite elements"
          }
        }
      },

      "buyButton": {
        "label": "Buy Button",
        "infoText": {
          "tooltipSettingDisabled": "This element's product data is automatically loaded by the product associated with this layout. The content just shows a sample preview."
        },
        "config": {
          "label": {
            "buttonText": "Button Text",
            "redirectLink": "Redirect Link"
          },
          "placeholder": {
            "buttonText": "Enter Button Text ...",
            "redirectLink": "/#"
          }
        },
        "component": {
          "label": {
            "actionBuy": "Add to cart",
            "taxInfo": "Prices incl. VAT plus shipping costs",
            "deliveryShippingFree": "Free shipping",
            "deliveryTime": "Available, delivery time {name}",
            "variants": "Variants",
            "productNumber": "Product number:"
          }
        }
      }
    },
    "blocks": {
      "textImage": {
        "centerText": {
          "label": "Three columns, boxed images & text"
        },
        "imageText": {
          "label": "Two columns, boxed image & text"
        },
        "imageTextBubble": {
          "label": "Three columns, captioned & rounded"
        },
        "imageTextCover": {
          "label": "Two columns, full-sized image & text"
        },
        "imageTextGallery": {
          "label": "Three columns, image/text cards"
        },
        "imageTextRow": {
          "label": "Three columns, captioned images"
        },
        "textOnImage": {
          "label": "Hero image"
        }
      }
    }
  }
}
en

{
  "sw-cms": {
    "elements": {
      "general": {
        "config": {
          "caption": {
            "mediaUpload": "Upload an image"
          },
          "label": {
            "verticalAlign": "Vertical align",
            "alignment": "Alignment",
            "verticalAlignTop": "Top",
            "verticalAlignCenter": "Center",
            "verticalAlignBottom": "Bottom",
            "displayMode": "Display mode",
            "displayModeStandard": "Original",
            "displayModeCover": "Cropped",
            "displayModeStretch": "Scaled",
            "displayModeContain": "Fixed height",
            "autoSlide": "Automatic transition",
            "autoplayTimeout": "Delay",
            "speed": "Animation duration",
            "horizontalAlign": "Horizontal align",
            "horizontalAlignLeft": "Left",
            "horizontalAlignCenter": "Center",
            "horizontalAlignRight": "Right"
          },
          "helpText": {
            "autoSlide": "Temporarily disabled due to lack of accessibility.",
            "autoplayTimeout": "The millisecond duration for how long the slide should stay still.",
            "speed": "The millisecond duration of the sliding animation.",
            "displayMode": "<strong>Original:</strong> The image is displayed in its original resolution or shrunk down to current CMS page's width.<br><br><strong>Scaled:</strong> The image is scaled to the CMS page's width without any height restrictions.<br><br><strong>Cropped:</strong> The image is scaled to the current CMS page's width. Any vertical overflow is cropped to the configured maximum height."
          },
          "tab": {
            "content": "Content",
            "settings": "Settings",
            "options": "Options"
          },
          "infoText": {
            "listingElement": "This element's product data is automatically loaded by the category associated with this layout. The content just shows a sample preview."
          }
        },
        "switch": {
          "groups": {
            "all": "All elements",
            "favorites": "Favourite elements"
          }
        }
      },

      "buyButton": {
        "label": "Buy Button",
        "infoText": {
          "tooltipSettingDisabled": "This element's product data is automatically loaded by the product associated with this layout. The content just shows a sample preview."
        },
        "config": {
          "label": {
            "buttonText": "Button Text",
            "redirectLink": "Redirect Link"
          },
          "placeholder": {
            "buttonText": "Enter Button Text ...",
            "redirectLink": "/#"
          }
        },
        "component": {
          "label": {
            "actionBuy": "Add to cart",
            "taxInfo": "Prices incl. VAT plus shipping costs",
            "deliveryShippingFree": "Free shipping",
            "deliveryTime": "Available, delivery time {name}",
            "variants": "Variants",
            "productNumber": "Product number:"
          }
        }
      }
    },
    "blocks": {
      "textImage": {
        "centerText": {
          "label": "Three columns, boxed images & text"
        },
        "imageText": {
          "label": "Two columns, boxed image & text"
        },
        "imageTextBubble": {
          "label": "Three columns, captioned & rounded"
        },
        "imageTextCover": {
          "label": "Two columns, full-sized image & text"
        },
        "imageTextGallery": {
          "label": "Three columns, image/text cards"
        },
        "imageTextRow": {
          "label": "Three columns, captioned images"
        },
        "textOnImage": {
          "label": "Hero image"
        }
      }
    }
  }
}

  main.js

import './module/sw-cms/blocks/text-image/text-image-text';
import './module/sw-cms/elements/custom-image';
import './module/sw-cms/blocks/text-image/custom-image-text-gallery';
import './module/sw-cms/elements/buy-button';

 storefront keep it as it is
config
services.xmml

<?xml version="1.0"?>

<container xmlns="http://symfony.com/schema/dic/services"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
    <services>
        <service id="CmsTheme2\DataResolver\CustomImageCmsElementResolver"
                 class="CmsTheme2\DataResolver\CustomImageCmsElementResolver">
            <tag name="shopware.cms.data_resolver" />
        </service>
        <service id="CmsTheme2\DataResolver\CustomImageTextGalleryCmsElementResolver"
                 class="CmsTheme2\DataResolver\CustomImageTextGalleryCmsElementResolver">
            <tag name="shopware.cms.data_resolver" />
        </service>
    </services>
</container>

public/admin keep it as it is

views/storefront
block
custom-block-text-image-text.html.twig

{% block block_text_image_text %}
{#    {{ dump('test') }}#}
    {% set columns = 3 %}

    {% block block_left %}
        {% set element = block.slots.getSlot('left') %}

        <div class="col-md-4" data-cms-element-id="{{ element.id }}">
            {% block block_left_inner %}
                {% sw_include '@Storefront/storefront/element/cms-element-' ~ element.type ~ '.html.twig' ignore missing %}
            {% endblock %}
        </div>
    {% endblock %}

    {% block block_center %}
        {% set element = block.slots.getSlot('center') %}

        <div class="col-md-4" data-cms-element-id="{{ element.id }}">
            {% block block_center_center_inner %}
                {% sw_include '@Storefront/storefront/element/cms-element-' ~ element.type ~ '.html.twig' ignore missing %}
            {% endblock %}
        </div>
    {% endblock %}

    {% block block_right %}
        {% set element = block.slots.getSlot('right') %}

        <div class="col-md-4" data-cms-element-id="{{ element.id }}">
            {% block block_right_inner %}
                {% sw_include '@Storefront/storefront/element/cms-element-' ~ element.type ~ '.html.twig' ignore missing %}
            {% endblock %}
        </div>
    {% endblock %}
{% endblock %}

element
cms-element-custorm-image.html.twig

{% block sw_cms_el_custom_image %}
{#    {{ dump(element.data.media.media.url) }}#}
    <div class="cms-element-custom-image">
        <a href="{{ element.config.url.value }}">
            <img src="{{ element.data.media.media.url }}"/>
        </a>
    </div>
{% endblock %}


Enter fullscreen mode Exit fullscreen mode

Top comments (0)