Skip to content

Feature/cms 1411 thumb alignment setting for card view designers #17193

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

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
3 changes: 3 additions & 0 deletions CHANGELOG-WIP.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
- Element condition builders now show condition rules for custom fields with duplicate names. ([#17361](https://github.com/craftcms/cms/pull/17361))

### Administration
- It’s now possible to customize the thumbnail alignment within element cards. ([#17193](https://github.com/craftcms/cms/pull/17193))
- Assets and Categories fields no longer have “Show the site menu” settings. ([#17156](https://github.com/craftcms/cms/issues/17156))
- Entry type edit pages now have a “Save as a new entry type” action. ([#15977](https://github.com/craftcms/cms/discussions/15977))
- The `accessibilityDefaults` config setting can now contain `notificationPosition` and `slideoutPosition` keys. ([#17169](https://github.com/craftcms/cms/pull/17169))
Expand All @@ -39,7 +40,9 @@
- Added `craft\fields\data\OptionData::$icon`.
- Added `craft\helpers\Cp::buttonGroupFieldHtml()`.
- Added `craft\helpers\Cp::buttonGroupHtml()`.
- Added `craft\models\FieldLayout::getCardThumbAlignment()`.
- Added `craft\models\FieldLayout::resetUids()`.
- Added `craft\models\FieldLayout::setCardThumbAlignment()`.
- Added `craft\web\Request::getValidatedQueryParam()`.
- `craft\elements\Asset::getMimeType()` now returns the file’s actual MIME type (rather than the MIME type associated with the file’s extension), for locally-stored assets. ([#17254](https://github.com/craftcms/cms/pull/17254))
- `craft\fields\data\ColorData` now extends `craft\base\Model` and includes `blue`, `green`, `hex`, `luma`, `red`, and `rgb` attributes in its array keys. ([#17265](https://github.com/craftcms/cms/issues/17265))
Expand Down
3 changes: 3 additions & 0 deletions src/controllers/FieldsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -611,6 +611,7 @@ public function actionRenderCardPreview()
$fieldLayoutConfig = $this->request->getRequiredBodyParam('fieldLayoutConfig');
$cardElements = $this->request->getRequiredBodyParam('cardElements');
$showThumb = $this->request->getBodyParam('showThumb', false);
$thumbAlignment = $this->request->getBodyParam('thumbAlignment', false);

if (!isset($fieldLayoutConfig['id'])) {
$fieldLayout = Craft::createObject([
Expand All @@ -630,6 +631,8 @@ public function actionRenderCardPreview()
array_column($cardElements, 'value')
); // this fully takes care of attributes, but not fields

$fieldLayout->setCardThumbAlignment($thumbAlignment);

return $this->asJson([
'previewHtml' => Cp::cardPreviewHtml($fieldLayout, $cardElements, $showThumb),
]);
Expand Down
150 changes: 135 additions & 15 deletions src/helpers/Cp.php
Original file line number Diff line number Diff line change
Expand Up @@ -661,8 +661,13 @@ public static function elementCardHtml(ElementInterface $element, array $config
}

$color = $element instanceof Colorable ? $element->getColor() : null;
$thumbAlignment = $element->getFieldLayout()?->getCardThumbAlignment() ?? 'end';

$classes = [
'card',
"thumb-$thumbAlignment",
];

$classes = ['card'];
if ($element->hasErrors()) {
$classes[] = 'error';
}
Expand Down Expand Up @@ -739,7 +744,6 @@ public static function elementCardHtml(ElementInterface $element, array $config
Html::endTag('div');
}

$thumb = $element->getThumbHtml(120);
$icon = $element instanceof Iconic ? $element->getIcon() : null;
$title = $element->getCardTitle();

Expand Down Expand Up @@ -793,12 +797,23 @@ public static function elementCardHtml(ElementInterface $element, array $config
Html::endTag('div') . // .card-actions
Html::endTag('div') . // .card-actions-container
Html::endTag('div') . // .card-titlebar
Html::beginTag('div', ['class' => 'card-main']) .
Html::beginTag('div', ['class' => 'card-main']);

$contentHtml =
Html::beginTag('div', ['class' => 'card-content']) .
($headingContent !== '' ? Html::tag('div', $headingContent, ['class' => 'card-heading']) : '') .
($bodyContent !== '' ? Html::tag('div', $bodyContent, ['class' => 'card-body']) : '') .
Html::endTag('div') . // .card-content
($thumb ?? '') .
Html::endTag('div'); // .card-content

$thumbHtml = $element->getThumbHtml(120);

if ($thumbAlignment === 'start') {
$html .= $thumbHtml . $contentHtml;
} else {
$html .= $contentHtml . $thumbHtml;
}

$html .=
Html::endTag('div'); // .card-main

if ($config['context'] === 'field' && $config['inputName'] !== null) {
Expand Down Expand Up @@ -2035,7 +2050,7 @@ public static function rangeHtml(array $config): string
}

/**
* Renders a lightswitch field’s HTML.
* Renders a range field’s HTML.
*
* @param array $config
* @return string
Expand Down Expand Up @@ -2779,9 +2794,96 @@ public static function cardViewDesignerHtml(FieldLayout $fieldLayout, array $con
Html::endTag('div') . // .cvd-preview-container
Html::endTag('div') . // .cvd-preview
Html::endTag('div') . // .cvd-container
self::_getThumbManagementHtml($fieldLayout, $config) .
Html::endTag('div'); // .card-view-designer
}

/**
* Return HTML for managing thumbnail provider and position.
*
* @param FieldLayout $fieldLayout
* @param array $config
* @return string
* @throws TemplateLoaderException
*/
private static function _getThumbManagementHtml(FieldLayout $fieldLayout, array $config): string
{
$readOnly = isset($config['config']['disabled']) && $config['config']['disabled'];
if ((new ($fieldLayout['type']))->hasThumbs()) {
$options = [
['label' => Craft::t('app', 'Default'), 'value' => '__default__'],
];
} else {
$options = [
['label' => Craft::t('app', 'None'), 'value' => '__none__'],
];
}
$elementThumbnail = $fieldLayout->getThumbField()?->uid;
$thumbnailAlignment = $fieldLayout->getCardThumbAlignment();

$thumbableElements = array_filter(
$fieldLayout->getAllElements(),
fn($element) => $element instanceof BaseField && $element->thumbable()
);

foreach ($thumbableElements as $thumbableElement) {
$options[] = ['label' => $thumbableElement->label(), 'value' => $thumbableElement->uid];
}

$thumbHtml = Html::beginTag('div', ['class' => 'thumb-management']) .
Html::tag('h2', Craft::t('app', 'Manage element thumbnails'), ['class' => 'visually-hidden']) .
Html::beginTag('div', ['class' => ['flex', 'flex-nowrap', 'items-start']]);

// dropdown field that contains all thumbable fields + 'None' option
$thumbHtml .= self::selectFieldHtml([
'label' => Craft::t('app', 'Thumbnail Source'),
'id' => 'thumb-source',
'name' => 'thumbSource',
'options' => $options,
'value' => $elementThumbnail,
'disabled' => $readOnly,
]);

// radio button switch that lets you choose whether the thumb alignment should be start or end
$orientation = Craft::$app->getLocale()->getOrientation();
$thumbHtml .= self::buttonGroupFieldHtml([
'label' => Craft::t('app', 'Thumbnail Alignment'),
'id' => 'thumb-alignment',
'fieldClass' => $elementThumbnail === null ? 'hidden' : false,
'name' => 'thumbAlignment',
'options' => [
[
'icon' => $orientation == 'ltr' ? 'slideout-left' : 'slideout-right',
'value' => 'start',
'attributes' => [
'title' => $orientation == 'ltr' ? Craft::t('app', 'Left') : Craft::t('app', 'Right'),
'aria' => [
'label' => $orientation == 'ltr' ? Craft::t('app', 'Left') : Craft::t('app', 'Right'),
],
],
],
[
'icon' => $orientation == 'ltr' ? 'slideout-right' : 'slideout-left',
'value' => 'end',
'attributes' => [
'title' => $orientation == 'ltr' ? Craft::t('app', 'Right') : Craft::t('app', 'Left'),
'aria' => [
'label' => $orientation == 'ltr' ? Craft::t('app', 'Right') : Craft::t('app', 'Left'),
],
],
],
],
'value' => $thumbnailAlignment,
'disabled' => $readOnly,
]);


$thumbHtml .= Html::endTag('div') . // .flex
Html::endTag('div'); // .thumb-management

return $thumbHtml;
}

/**
* Returns HTML for the card preview based on selected fields and attributes.
*
Expand All @@ -2792,6 +2894,9 @@ public static function cardViewDesignerHtml(FieldLayout $fieldLayout, array $con
*/
public static function cardPreviewHtml(FieldLayout $fieldLayout, array $cardElements = [], $showThumb = false): string
{
$hasThumb = $showThumb ?? $fieldLayout->getThumbField() !== null ? true : (new ($fieldLayout['type']))->hasThumbs();
$thumbAlignment = $fieldLayout->getCardThumbAlignment();

// get heading
$heading = Html::tag('craft-element-label',
Html::tag('a', Html::tag('span', Craft::t('app', 'Title')), [
Expand All @@ -2811,12 +2916,18 @@ public static function cardPreviewHtml(FieldLayout $fieldLayout, array $cardElem

$previewHtml =
Html::beginTag('div', [
'class' => ['element', 'card'],
'class' => array_filter([
'element',
'card',
$hasThumb ? "thumb-$thumbAlignment" : null,
]),
]);

$previewHtml .=
Html::tag('div', options: ['class' => 'card-titlebar']) .
Html::beginTag('div', ['class' => 'card-main']) .
Html::beginTag('div', ['class' => 'card-main']);

$contentHtml =
Html::beginTag('div', ['class' => 'card-content']) .
Html::tag('div', $heading, ['class' => 'card-heading']) .
Html::beginTag('div', ['class' => 'card-body']);
Expand All @@ -2826,37 +2937,45 @@ public static function cardPreviewHtml(FieldLayout $fieldLayout, array $cardElem

foreach ($cardElements as $cardElement) {
if ($cardElement instanceof CustomField) {
$previewHtml .= Html::tag('div', $cardElement->getField()->previewPlaceholderHtml(null, null));
$contentHtml .= Html::tag('div', $cardElement->getField()->previewPlaceholderHtml(null, null));
} elseif ($cardElement instanceof BaseField) {
$previewHtml .= Html::tag('div', $cardElement->previewPlaceholderHtml(null, null));
$contentHtml .= Html::tag('div', $cardElement->previewPlaceholderHtml(null, null));
} else {
$html = $elementType::attributePreviewHtml($cardElement);
if (is_callable($html)) {
$html = $html();
}
$previewHtml .= Html::tag('div', $html);
$contentHtml .= Html::tag('div', $html);
}
}

if (!empty(array_filter($labels))) {
$previewHtml .= Html::ul($labels, [
$contentHtml .= Html::ul($labels, [
'class' => ['flex', 'gap-xs'],
'encode' => false,
]);
}

$previewHtml .=
$contentHtml .=
Html::endTag('div') . // .card-body
Html::endTag('div'); // .card-content

// get thumb placeholder
if ($showThumb ?? $fieldLayout->getThumbField() !== null) {
if ($hasThumb) {
$previewThumb = Html::tag('div',
Html::tag('div', Cp::iconSvg('image'), ['class' => 'cp-icon']),
['class' => 'cvd-thumbnail']
);

$previewHtml .= Html::tag('div', $previewThumb, ['class' => ['thumb']]);
$thumbHtml = Html::tag('div', $previewThumb, ['class' => ['thumb']]);
} else {
$thumbHtml = '';
}

if ($thumbAlignment === 'start') {
$previewHtml .= $thumbHtml . $contentHtml;
} else {
$previewHtml .= $contentHtml . $thumbHtml;
}

$previewHtml .=
Expand Down Expand Up @@ -2926,6 +3045,7 @@ public static function fieldLayoutDesignerHtml(FieldLayout $fieldLayout, array $
'customizableTabs' => $config['customizableTabs'],
'customizableUi' => $config['customizableUi'],
'withCardViewDesigner' => $config['withCardViewDesigner'] ?? false,
'alwaysShowThumbAlignmentBtns' => (new ($fieldLayout['type']))->hasThumbs(),
'readOnly' => $readOnly,
]);
$namespacedId = $view->namespaceInputId($config['id']);
Expand Down
46 changes: 46 additions & 0 deletions src/models/FieldLayout.php
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,13 @@ public static function createFromConfig(array $config): self
*/
private array $_cardView;

/**
* @var string
* @see getCardThumbAlignment()
* @see setCardThumbAlignment()
*/
private string $_cardThumbAlignment;

/**
* @inheritdoc
*/
Expand All @@ -276,6 +283,10 @@ public function init(): void
$this->setCardView([]);
}
}

if (!isset($this->_cardThumbAlignment)) {
$this->setCardThumbAlignment();
}
}

/**
Expand Down Expand Up @@ -422,6 +433,38 @@ public function setCardView(?array $items): void
$this->reset();
}

/**
* Returns the thumbnail alignment that should be used in element cards.
*
* @return string `start` or `end`
* @since 5.8.0
*/
public function getCardThumbAlignment(): string
{
if (!isset($this->_cardThumbAlignment)) {
$this->setCardThumbAlignment();
}

return $this->_cardThumbAlignment;
}

/**
* Sets the thumbnail alignment that should be used in element cards.
*
* @param string|null $alignment `start` or `end`
* @since 5.8.0
*/
public function setCardThumbAlignment(?string $alignment = null): void
{
$validOptions = ['start', 'end'];

if (!in_array($alignment, $validOptions)) {
$alignment = null;
}

$this->_cardThumbAlignment = $alignment ?? 'end';
}

/**
* Returns the available fields, grouped by field group name.
*
Expand Down Expand Up @@ -603,14 +646,17 @@ public function getConfig(): ?array
));

$cardViewConfig = $this->getCardView();
$cardThumbAlignment = $this->getCardThumbAlignment();

if (empty($tabConfigs) && empty($cardViewConfig)) {
// no point bothering with the thumb alignment if we don't have the card view
return null;
}

return [
'tabs' => $tabConfigs,
'cardView' => $cardViewConfig,
'cardThumbAlignment' => $cardThumbAlignment,
];
}

Expand Down
1 change: 1 addition & 0 deletions src/services/Fields.php
Original file line number Diff line number Diff line change
Expand Up @@ -1126,6 +1126,7 @@ public function assembleLayoutFromPost(?string $namespace = null): FieldLayout
$config = JsonHelper::decode(Craft::$app->getRequest()->getBodyParam($paramPrefix . 'fieldLayout'));
$cardView = Craft::$app->getRequest()->getBodyParam($paramPrefix . 'cardView');
$config['cardView'] = empty($cardView) ? null : $cardView;
$config['cardThumbAlignment'] = Craft::$app->getRequest()->getBodyParam($paramPrefix . 'thumbAlignment');
$layout = $this->createLayout($config);

// Make sure all the elements have a dateAdded value set
Expand Down
5 changes: 4 additions & 1 deletion src/templates/_includes/forms/button.twig
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
{% set label = label ?? null %}
{% set labelHtml = labelHtml ?? null %}

{# the "disabled" class takes care of disabling the button,
but the "read-only" class is needed so that it is allowed to have custom tabindex attribute #}
{% set hasIcon = icon or icon is same as('0') %}
{% set hasLabel = label or label is same as('0') %}
{% set hasLabelHtml = labelHtml or labelHtml is same as('0') %}
Expand All @@ -19,7 +21,8 @@
class: (class ?? [])|explodeClass|merge([
'btn',
not (hasIcon or hasLabel or hasLabelHtml) ? 'btn-empty' : null,
(disabled ?? false) ? 'disabled',
(disabled ?? readOnly ?? false) ? 'disabled',
(readOnly ?? false) ? 'read-only',
]|filter),
data: {
'busy-message': busyMessage,
Expand Down
Loading