Loading css/options-filter.css +32 −3 Original line number Diff line number Diff line Loading @@ -54,16 +54,19 @@ .radio-details--name { grid-area: 2 / 1 / span 1 / span 3; font-weight: bold; margin-bottom: 1rem; font-size: 0.9rem; } .remote_status--deprecated .radio-details--name { text-decoration: line-through; } .radio-details--wrapper .radio-details--description { grid-area: 3 / 1 / span 1 / span 3; font-size: 0.7rem; color: #666; line-height: 1rem; margin-bottom: 1rem; margin-bottom: 1.5rem; padding-right: 10px; } Loading @@ -74,7 +77,6 @@ overflow: hidden; border-radius: 5px; border: 1px solid #bbb; margin-bottom: 15px; background-image: url("../assets/default-thumbnail.svg"); background-color: #cae6ef; background-size: 100%; Loading @@ -95,6 +97,22 @@ background: #eea0c0; } .radio-details--wrapper .radio-details_remote-status { display: none } .radio-details--wrapper .radio-details_remote-status--deprecated { display: block; background: #ff6666a0; position: absolute; bottom: 0; right: 0; left: 0; text-align: center; font-size: 0.8rem; border-radius: 0 0 3px 3px; } .radio-details--wrapper .radio-details--source { padding: 0 5px; font-size: 0.75rem; Loading Loading @@ -166,3 +184,14 @@ width: 100%; height: 600px; } .reset-search-box { color: #555; height: 30px; text-align: center; width: 30px; background: none; border-width: 0; cursor: pointer; margin-left: -35px; } js/options-filter.js +257 −98 Original line number Diff line number Diff line (function(once) { function toggleRadioButtons(inputQuery, fieldset) { if (inputQuery.length === 0) { for (var radio of fieldset.querySelectorAll('input[type="radio"]')) { radio.parentElement.parentElement.parentElement.hidden = false; } return; const deprecatedMessage = 'The selected widget is deprecated and will be removed. Evaluate migrating to a stable widget.'; window.optionsFilter = { states: [], initIndex: function(index = 0) { optionsFilter.states[index] = {}; }, setContainer: function(container, index = 0) { optionsFilter.states[index].container = container; optionsFilter.states[index].widgets = Array.from(container.querySelectorAll('.js-form-type-radio')); var deprecationCheckbox = container.querySelector('input.deprecation-checkbox'); optionsFilter.states[index].deprecatedFilterValue = true; if (deprecationCheckbox !== null) { optionsFilter.states[index].deprecatedFilterValue = container.querySelector( 'input.deprecation-checkbox').checked; } var matching = Array.from(fieldset.querySelectorAll('.radio-details--search')) .filter(e => e.innerText.search(inputQuery) !== -1); // Hide all inputs, then show the matching and selected. for (var radio of fieldset.querySelectorAll('input[type="radio"]')) { // Always show the checked option. radio.parentElement.parentElement.parentElement.hidden = !radio.checked; optionsFilter.states[index].infoLayer = container.querySelector('.currently-selected'); optionsFilter.states[index].infoLayer.hidden = true; optionsFilter.states[index].warningLayer = container.querySelector('.warning'); optionsFilter.states[index].warningLayer.hidden = true; optionsFilter.setDefaultWidgetInDOM(index); }, getContainer: function(index = 0) { return optionsFilter.states[index].container; }, getInfoLayer: function(index = 0) { return optionsFilter.states[index].infoLayer; }, setSearchboxValue: function(value, index = 0) { optionsFilter.states[index].searchboxValue = value; }, getSearchboxValue: function(index = 0) { return optionsFilter.states[index].searchboxValue; }, setDeprecatedFilterValue: function(value, index = 0) { optionsFilter.states[index].deprecatedFilterValue = value; }, getDeprecatedFilterValue: function(index = 0) { return optionsFilter.states[index].deprecatedFilterValue; }, setSelectedWidget: function(selectedWidget, index = 0) { optionsFilter.states[index].selectedWidget = selectedWidget; }, getSelectedWidget: function(index = 0) { return optionsFilter.states[index].selectedWidget; }, setDefaultWidgetInDOM: function(index = 0) { const container = optionsFilter.getContainer(index); const selectedWidget = container.querySelector('input[type="radio"]:checked'); if (selectedWidget === null) { return null; } for (var matchingElement of matching) { optionsFilter.selectWidget(selectedWidget, index); }, setSearchboxValueInDOM: function(value, index = 0) { const container = optionsFilter.getContainer(index); const searchBox = container.querySelector('input.search-box'); searchBox.value = value; optionsFilter.setSearchboxValue(value); }, selectWidget: function(selectedWidget, index = 0) { var widgetWrapper = selectedWidget.closest('.js-form-type-radio'); optionsFilter.setSelectedWidget(widgetWrapper, index); optionsFilter.states[index].widgets.map(function(element) { element.classList.remove('form-type--radio__selected'); }); widgetWrapper.classList.add('form-type--radio__selected'); optionsFilter.showWarningMessageIfNeeded(index); optionsFilter.setSearchboxValueInDOM(widgetWrapper.querySelector('.radio-details--machine-name').innerText); }, showAllWidgets: function(index = 0) { optionsFilter.states[index].widgets.map(function(element) { element.hidden = false; }); }, showSelectedWidget: function(index = 0) { optionsFilter.getSelectedWidget(index).hidden = false; }, hideAllWidgets: function(index = 0) { optionsFilter.states[index].widgets.map(function(element) { element.hidden = true; }); }, hideAllWidgetsBut: function(widgetsToShow, index = 0) { optionsFilter.hideAllWidgets(index); for (const matchingElement of widgetsToShow) { matchingElement.parentElement.parentElement.hidden = false; } }, filterWidgetsSearched: function(index = 0) { const container = optionsFilter.getContainer(index); const searchboxValue = optionsFilter.getSearchboxValue(index); const matchingWidgets = Array.from( container.querySelectorAll('.radio-details--search')). filter(e => e.innerText.search(searchboxValue) !== -1); optionsFilter.hideAllWidgetsBut(matchingWidgets, index); }, isWidgetDeprecated: function(widget) { return widget.innerText.search('deprecated') >= 0; }, getDeprecatedWidgets: function(index = 0) { return optionsFilter.states[index].widgets.filter(function(widget) { return optionsFilter.isWidgetDeprecated(widget); }); }, filterWidgetsDeprecated: function(index = 0) { var showDeprecated = optionsFilter.getDeprecatedFilterValue(index); optionsFilter.getDeprecatedWidgets(index).map(function(widget) { if (!showDeprecated) { widget.hidden = true; } }); }, function subscribeToChanges(fieldset) { var search = fieldset.querySelector('input[type="search"]'); var changeSearchText = (event) => toggleRadioButtons(event.target.value, fieldset); search.addEventListener('input', changeSearchText); showWarningMessageIfNeeded: function(index = 0) { var selectedWidget = optionsFilter.getSelectedWidget(index); optionsFilter.states[index].warningLayer.hidden = true; optionsFilter.states[index].warningLayer.innerText = ''; if (optionsFilter.isWidgetDeprecated(selectedWidget)) { optionsFilter.states[index].warningLayer.hidden = false; optionsFilter.states[index].warningLayer.innerText = deprecatedMessage; } }, refreshWidgets: function(index = 0) { optionsFilter.showAllWidgets(index); optionsFilter.filterWidgetsSearched(index); optionsFilter.filterWidgetsDeprecated(index); optionsFilter.showSelectedWidget(index); }, var renderCurrentlySelected = (info, selectedContainer) => { var id = selectedContainer.querySelector('input[type="radio"]').value; var name = selectedContainer.querySelector('.radio-details--human-name').innerText; var description = selectedContainer.querySelector('.radio-details--description').innerText; var status = selectedContainer.querySelector('.radio-details--status').innerText; var languages = selectedContainer.querySelector('.radio-details--languages').innerHTML; var source = selectedContainer.querySelector('.radio-details--source').innerText; var version = selectedContainer.querySelector('.radio-details--version').innerText; var createdDate = selectedContainer.querySelector('.radio-details--created').innerText; var updatedDate = selectedContainer.querySelector('.radio-details--updated').innerText; const imgElement = selectedContainer.querySelector('.radio-details--image'); renderSelectedWidget: function(index = 0) { var info = optionsFilter.getInfoLayer(index); var selectedWidget = optionsFilter.getSelectedWidget(index); var id = selectedWidget.querySelector('input[type="radio"]').value; var name = selectedWidget.querySelector('.radio-details--human-name').innerText; var description = selectedWidget.querySelector('.radio-details--description').innerText; var status = selectedWidget.querySelector('.radio-details_remote-status').innerText; var languages = selectedWidget.querySelector('.radio-details--languages').innerHTML; var source = selectedWidget.querySelector('.radio-details--source').innerText; var version = selectedWidget.querySelector('.radio-details--version').innerText; var createdDate = selectedWidget.querySelector('.radio-details--created').innerText; var updatedDate = selectedWidget.querySelector('.radio-details--updated').innerText; const imgElement = selectedWidget.querySelector('.radio-details--image'); var img = imgElement ? imgElement.outerHTML : ''; var previewUrl = selectedContainer.querySelector('.radio-details--preview-url').innerText; info.innerHTML = Drupal.theme('currentlySelectedClComponent', id, name, description, status, languages, version, source, createdDate, updatedDate, img, previewUrl); var previewUrl = selectedWidget.querySelector('.radio-details--preview-url').innerText; info.innerHTML = Drupal.theme('currentlySelectedWidget', id, name, description, status, languages, version, source, createdDate, updatedDate, img, previewUrl); if (previewUrl) { info.querySelector('a.button').addEventListener('click', (event) => { var button = event.target; Loading @@ -49,28 +181,88 @@ }); } info.hidden = false; }, }; const subscribeSearchboxToChanges = function(index = 0) { const container = optionsFilter.getContainer(index); const searchBox = container.querySelector('input.search-box'); if (searchBox === null) { return; } searchBox.addEventListener('input', function(event) { optionsFilter.setSearchboxValue(event.target.value, index); optionsFilter.refreshWidgets(index); }); }; const includeResetButton = function(index) { const container = optionsFilter.getContainer(index); const searchBox = container.querySelector('input.search-box'); if (searchBox === null) { return; } var resetButton = document.createElement('button'); resetButton.classList.add('reset-search-box'); resetButton.innerText = '⨯'; resetButton.title = 'Reset filter'; resetButton.addEventListener('click', function(event) { event.preventDefault(); container.querySelector('input.search-box').value = ''; optionsFilter.setSearchboxValue('', index); optionsFilter.refreshWidgets(index); event.target.blur(); }); searchBox.parentElement.insertBefore(resetButton, searchBox.nextSibling); }; const subscribeDeprecatedSearchboxToChanges = function(index = 0) { const container = optionsFilter.getContainer(index); const deprecationCheckbox = container.querySelector('input.deprecation-checkbox'); if (deprecationCheckbox === null) { return; } deprecationCheckbox.addEventListener('change', function(event) { optionsFilter.setDeprecatedFilterValue(event.target.checked, index); optionsFilter.refreshWidgets(index); }); }; const subscribeRadiosToChanges = function(index = 0) { const container = optionsFilter.getContainer(index); var radios = once('radio-change-subscribed', 'input[type="radio"]', container); radios.map(function(radio) { radio.addEventListener('change', function(event) { optionsFilter.selectWidget(event.target, index); optionsFilter.refreshWidgets(index); optionsFilter.renderSelectedWidget(index); }); }); }; /** * Set up options filter */ Drupal.behaviors.optionsFilter = { attach: (context, settings) => { var fieldsets = once('options-filter', '.widget-type--selector', context); for (var fieldset of fieldsets) { var search = fieldset.querySelector('input[type="search"]'); if (!search.value) { var selected = fieldset.querySelector('input[type="radio"][checked]'); search.value = selected ? selected.parentElement.parentElement.querySelector('.radio-details--machine-name').innerText : ''; } toggleRadioButtons(search.value, fieldset); subscribeToChanges(fieldset); once('options-filter', '.widget-type--selector', context).map(function(container, index) { optionsFilter.initIndex(index); optionsFilter.setContainer(container, index); subscribeSearchboxToChanges(index); subscribeDeprecatedSearchboxToChanges(index); subscribeRadiosToChanges(index); includeResetButton(index); optionsFilter.refreshWidgets(index); if (optionsFilter.getSelectedWidget(index) !== undefined) { optionsFilter.renderSelectedWidget(index); } }); }, }; Drupal.theme.currentlySelectedClComponent = (id, name, description, status, languages, version, source, createdDate, updatedDate, img, previewUrl) => ` <summary>${Drupal.t('ℹ️ More information about <em>@name</em>', { '@name': name })}</summary> Drupal.theme.currentlySelectedWidget = ( id, name, description, status, languages, version, source, createdDate, updatedDate, img, previewUrl) => ` <summary>${Drupal.t('ℹ️ More information about <em>@name</em>', {'@name': name})}</summary> <p>${description}</p> <div class='image-table--wrapper'> <table> Loading @@ -81,49 +273,16 @@ <tr><th>${Drupal.t('Status')}</th><td>${status}</td></tr> <tr><th>${Drupal.t('Available Languages')}</th><td>${languages}</td></tr> </table> <div class='currently-selected--image--wrapper${img ? '' : ' currently-selected--image--wrapper__empty'}'> <div class='currently-selected--image--wrapper${img ? '' : ' currently-selected--image--wrapper__empty'}'> ${img ? img : ''} </div> </div> ${previewUrl ? `<div style='display: none' id='preview-url'>${previewUrl}</div> <div class="try-now--wrapper"><a href="#preview-url" class='try-now button button--primary'>${Drupal.t('Try now')}</a></div>` <div class="try-now--wrapper"><a href="#preview-url" class='try-now button button--primary'>${Drupal.t( 'Try now')}</a></div>` : '' }`; /** * Render more info about the currently selected component. */ Drupal.behaviors.currentlySelected = { attach: (context, settings) => { var fieldsets = once('currently-selected', '.widget-type--selector', context); for (var fieldset of fieldsets) { var info = fieldset.querySelector('.currently-selected'); info.hidden = true; var selected = fieldset.querySelector('input[type="radio"][checked]'); if (selected) { selected.parentElement.parentElement.parentElement.classList.add('form-type--radio__selected'); renderCurrentlySelected(info, selected.parentElement.parentElement); } var radios = once('radio-change-subscribed', 'input[type="radio"]', fieldset); for (var radio of radios) { radio.addEventListener('change', (event) => { if (event.target.checked) { var all = event.target.parentElement.parentElement.parentElement.parentElement.querySelectorAll('input[type="radio"]'); for (var item of all) { item.parentElement.parentElement.parentElement.classList.remove('form-type--radio__selected'); } event.target.parentElement.parentElement.parentElement.classList.add('form-type--radio__selected'); renderCurrentlySelected(info, event.target.parentElement.parentElement); var searchElement = fieldset.querySelector('input[type="search"]'); var machineName = event.target.parentElement.parentElement.querySelector('.radio-details--machine-name').innerText; searchElement.value = machineName; toggleRadioButtons(machineName, fieldset); } }); } } }, }; }(once)); src/Element/WidgetSelectorElement.php +41 −18 Original line number Diff line number Diff line Loading @@ -106,17 +106,39 @@ class WidgetSelectorElement extends FormElement implements ContainerFactoryPlugi $default_widget_type = ($default_id ? $widget_types[$default_id] : NULL) ?? NULL; $element += [ '#attached' => ['library' => ['widget_type/selector']], 'search' => [ ]; // Widgets types can't be change once created, so hide search and deprecated searchbox if (!$default_widget_type) { $element['search'] = [ '#title' => $this->t('Search'), '#title_display' => 'hidden', '#type' => 'search', '#default_value' => $default_widget_type instanceof WidgetTypeInterface ? $default_widget_type->getRemoteId() : NULL, '#default_value' => $default_widget_type instanceof WidgetTypeInterface ? $default_widget_type->getRemoteId( ) : NULL, '#placeholder' => $this->t('Search for a widget type'), '#size' => 50, '#description' => $this->t('Start typing to search for a widget type.'), '#input' => FALSE, '#attributes' => [ 'class' => [ 'search-box', ], ], ]; $element['show_deprecated'] = [ '#type' => 'checkbox', '#title' => $this->t('Show deprecated widgets'), '#default_value' => FALSE, '#attributes' => [ 'class' => [ 'deprecation-checkbox', ], 'target_id' => [ ], ]; } $element['target_id'] = [ '#type' => 'radios', '#options' => $options, '#title' => $this->t('Widgets'), Loading @@ -126,12 +148,12 @@ class WidgetSelectorElement extends FormElement implements ContainerFactoryPlugi [Radios::class, 'processRadios'], [$this, 'processRadios'], ], '#weight' => 1, '#attributes' => [ 'class' => ['widget-type-selector--radios'], ], '#ajax' => $element['#ajax'] ?? FALSE, '#input' => FALSE, ], ]; $classes = $element['#attributes']['class'] ?? []; $classes[] = 'widget-type--selector'; Loading Loading @@ -172,6 +194,7 @@ class WidgetSelectorElement extends FormElement implements ContainerFactoryPlugi $element[$key]['#human_name'] = $widget_type->getName(); $element[$key]['#machine_name'] = $widget_type->getRemoteId(); $element[$key]['#remote_description'] = $widget_type->getDescription(); $element[$key]['#remote_status'] = $widget_type->getRemoteStatus(); $field_image = $widget_type->getPreviewImage(); $thumbnail = ['#markup' => '']; $image = ['#markup' => '']; Loading @@ -194,7 +217,7 @@ class WidgetSelectorElement extends FormElement implements ContainerFactoryPlugi '#uri' => $uri, '#attributes' => [ 'loading' => 'lazy', 'class' => ['radio-details--image'] 'class' => ['radio-details--image'], ], ]; } Loading Loading @@ -230,11 +253,11 @@ class WidgetSelectorElement extends FormElement implements ContainerFactoryPlugi return [ '#type' => 'html_tag', '#tag' => 'code', '#value' => $langcode '#value' => $langcode, ]; }, $widget_type->getRemoteLanguages() ) ), ]; } return $element; Loading src/Entity/WidgetType.php +28 −4 Original line number Diff line number Diff line Loading @@ -233,8 +233,7 @@ final class WidgetType extends ContentEntityBase implements WidgetTypeInterface return []; } return $first->getValue(); } catch (MissingDataException $exception) { } catch (MissingDataException $exception) { return []; } } Loading @@ -249,8 +248,7 @@ final class WidgetType extends ContentEntityBase implements WidgetTypeInterface return []; } return $first->getValue(); } catch (MissingDataException $exception) { } catch (MissingDataException $exception) { return []; } } Loading Loading @@ -331,6 +329,21 @@ final class WidgetType extends ContentEntityBase implements WidgetTypeInterface return $this; } /** * {@inheritdoc} */ public function getRemoteStatus(): string { return $this->get('remote_widget_status')->value; } /** * {@inheritdoc} */ public function setRemoteStatus($remote_status): WidgetTypeInterface { $this->set('remote_widget_status', $remote_status); return $this; } /** * {@inheritdoc} */ Loading Loading @@ -452,6 +465,17 @@ final class WidgetType extends ContentEntityBase implements WidgetTypeInterface ->setDisplayConfigurable('view', TRUE) ->setReadOnly(TRUE); $fields['remote_widget_status'] = BaseFieldDefinition::create('string') ->setLabel(t('Remote Widget status')) ->setDescription(t('The list of files of the widget.')) ->setDisplayOptions('view', [ 'label' => 'above', 'type' => 'string', 'weight' => -2, ]) ->setDisplayConfigurable('view', FALSE) ->setReadOnly(TRUE); $fields['remote_widget_settings'] = BaseFieldDefinition::create('map') ->setLabel(t('Remote Widget Settings')) ->setDescription(t('The key/value settings from the widget server.')) Loading src/WidgetTypeInterface.php +15 −1 Original line number Diff line number Diff line Loading @@ -5,7 +5,6 @@ namespace Drupal\widget_type; use Drupal\Core\Entity\ContentEntityInterface; use Drupal\Core\Entity\EntityChangedInterface; use Drupal\file\FileInterface; use Drupal\widget_type\Entity\WidgetType; /** * Provides an interface defining a widget type entity type. Loading Loading @@ -110,6 +109,21 @@ interface WidgetTypeInterface extends ContentEntityInterface, EntityChangedInter */ public function setRemoteLanguages(array $lang_codes): self; /** * @param $remote_status * * @return $this */ public function setRemoteStatus($remote_status): self; /** * Get the remote widget type status. * * @return string * The widget type description. */ public function getRemoteStatus(): string; /** * Get the widget type settings. * Loading Loading
css/options-filter.css +32 −3 Original line number Diff line number Diff line Loading @@ -54,16 +54,19 @@ .radio-details--name { grid-area: 2 / 1 / span 1 / span 3; font-weight: bold; margin-bottom: 1rem; font-size: 0.9rem; } .remote_status--deprecated .radio-details--name { text-decoration: line-through; } .radio-details--wrapper .radio-details--description { grid-area: 3 / 1 / span 1 / span 3; font-size: 0.7rem; color: #666; line-height: 1rem; margin-bottom: 1rem; margin-bottom: 1.5rem; padding-right: 10px; } Loading @@ -74,7 +77,6 @@ overflow: hidden; border-radius: 5px; border: 1px solid #bbb; margin-bottom: 15px; background-image: url("../assets/default-thumbnail.svg"); background-color: #cae6ef; background-size: 100%; Loading @@ -95,6 +97,22 @@ background: #eea0c0; } .radio-details--wrapper .radio-details_remote-status { display: none } .radio-details--wrapper .radio-details_remote-status--deprecated { display: block; background: #ff6666a0; position: absolute; bottom: 0; right: 0; left: 0; text-align: center; font-size: 0.8rem; border-radius: 0 0 3px 3px; } .radio-details--wrapper .radio-details--source { padding: 0 5px; font-size: 0.75rem; Loading Loading @@ -166,3 +184,14 @@ width: 100%; height: 600px; } .reset-search-box { color: #555; height: 30px; text-align: center; width: 30px; background: none; border-width: 0; cursor: pointer; margin-left: -35px; }
js/options-filter.js +257 −98 Original line number Diff line number Diff line (function(once) { function toggleRadioButtons(inputQuery, fieldset) { if (inputQuery.length === 0) { for (var radio of fieldset.querySelectorAll('input[type="radio"]')) { radio.parentElement.parentElement.parentElement.hidden = false; } return; const deprecatedMessage = 'The selected widget is deprecated and will be removed. Evaluate migrating to a stable widget.'; window.optionsFilter = { states: [], initIndex: function(index = 0) { optionsFilter.states[index] = {}; }, setContainer: function(container, index = 0) { optionsFilter.states[index].container = container; optionsFilter.states[index].widgets = Array.from(container.querySelectorAll('.js-form-type-radio')); var deprecationCheckbox = container.querySelector('input.deprecation-checkbox'); optionsFilter.states[index].deprecatedFilterValue = true; if (deprecationCheckbox !== null) { optionsFilter.states[index].deprecatedFilterValue = container.querySelector( 'input.deprecation-checkbox').checked; } var matching = Array.from(fieldset.querySelectorAll('.radio-details--search')) .filter(e => e.innerText.search(inputQuery) !== -1); // Hide all inputs, then show the matching and selected. for (var radio of fieldset.querySelectorAll('input[type="radio"]')) { // Always show the checked option. radio.parentElement.parentElement.parentElement.hidden = !radio.checked; optionsFilter.states[index].infoLayer = container.querySelector('.currently-selected'); optionsFilter.states[index].infoLayer.hidden = true; optionsFilter.states[index].warningLayer = container.querySelector('.warning'); optionsFilter.states[index].warningLayer.hidden = true; optionsFilter.setDefaultWidgetInDOM(index); }, getContainer: function(index = 0) { return optionsFilter.states[index].container; }, getInfoLayer: function(index = 0) { return optionsFilter.states[index].infoLayer; }, setSearchboxValue: function(value, index = 0) { optionsFilter.states[index].searchboxValue = value; }, getSearchboxValue: function(index = 0) { return optionsFilter.states[index].searchboxValue; }, setDeprecatedFilterValue: function(value, index = 0) { optionsFilter.states[index].deprecatedFilterValue = value; }, getDeprecatedFilterValue: function(index = 0) { return optionsFilter.states[index].deprecatedFilterValue; }, setSelectedWidget: function(selectedWidget, index = 0) { optionsFilter.states[index].selectedWidget = selectedWidget; }, getSelectedWidget: function(index = 0) { return optionsFilter.states[index].selectedWidget; }, setDefaultWidgetInDOM: function(index = 0) { const container = optionsFilter.getContainer(index); const selectedWidget = container.querySelector('input[type="radio"]:checked'); if (selectedWidget === null) { return null; } for (var matchingElement of matching) { optionsFilter.selectWidget(selectedWidget, index); }, setSearchboxValueInDOM: function(value, index = 0) { const container = optionsFilter.getContainer(index); const searchBox = container.querySelector('input.search-box'); searchBox.value = value; optionsFilter.setSearchboxValue(value); }, selectWidget: function(selectedWidget, index = 0) { var widgetWrapper = selectedWidget.closest('.js-form-type-radio'); optionsFilter.setSelectedWidget(widgetWrapper, index); optionsFilter.states[index].widgets.map(function(element) { element.classList.remove('form-type--radio__selected'); }); widgetWrapper.classList.add('form-type--radio__selected'); optionsFilter.showWarningMessageIfNeeded(index); optionsFilter.setSearchboxValueInDOM(widgetWrapper.querySelector('.radio-details--machine-name').innerText); }, showAllWidgets: function(index = 0) { optionsFilter.states[index].widgets.map(function(element) { element.hidden = false; }); }, showSelectedWidget: function(index = 0) { optionsFilter.getSelectedWidget(index).hidden = false; }, hideAllWidgets: function(index = 0) { optionsFilter.states[index].widgets.map(function(element) { element.hidden = true; }); }, hideAllWidgetsBut: function(widgetsToShow, index = 0) { optionsFilter.hideAllWidgets(index); for (const matchingElement of widgetsToShow) { matchingElement.parentElement.parentElement.hidden = false; } }, filterWidgetsSearched: function(index = 0) { const container = optionsFilter.getContainer(index); const searchboxValue = optionsFilter.getSearchboxValue(index); const matchingWidgets = Array.from( container.querySelectorAll('.radio-details--search')). filter(e => e.innerText.search(searchboxValue) !== -1); optionsFilter.hideAllWidgetsBut(matchingWidgets, index); }, isWidgetDeprecated: function(widget) { return widget.innerText.search('deprecated') >= 0; }, getDeprecatedWidgets: function(index = 0) { return optionsFilter.states[index].widgets.filter(function(widget) { return optionsFilter.isWidgetDeprecated(widget); }); }, filterWidgetsDeprecated: function(index = 0) { var showDeprecated = optionsFilter.getDeprecatedFilterValue(index); optionsFilter.getDeprecatedWidgets(index).map(function(widget) { if (!showDeprecated) { widget.hidden = true; } }); }, function subscribeToChanges(fieldset) { var search = fieldset.querySelector('input[type="search"]'); var changeSearchText = (event) => toggleRadioButtons(event.target.value, fieldset); search.addEventListener('input', changeSearchText); showWarningMessageIfNeeded: function(index = 0) { var selectedWidget = optionsFilter.getSelectedWidget(index); optionsFilter.states[index].warningLayer.hidden = true; optionsFilter.states[index].warningLayer.innerText = ''; if (optionsFilter.isWidgetDeprecated(selectedWidget)) { optionsFilter.states[index].warningLayer.hidden = false; optionsFilter.states[index].warningLayer.innerText = deprecatedMessage; } }, refreshWidgets: function(index = 0) { optionsFilter.showAllWidgets(index); optionsFilter.filterWidgetsSearched(index); optionsFilter.filterWidgetsDeprecated(index); optionsFilter.showSelectedWidget(index); }, var renderCurrentlySelected = (info, selectedContainer) => { var id = selectedContainer.querySelector('input[type="radio"]').value; var name = selectedContainer.querySelector('.radio-details--human-name').innerText; var description = selectedContainer.querySelector('.radio-details--description').innerText; var status = selectedContainer.querySelector('.radio-details--status').innerText; var languages = selectedContainer.querySelector('.radio-details--languages').innerHTML; var source = selectedContainer.querySelector('.radio-details--source').innerText; var version = selectedContainer.querySelector('.radio-details--version').innerText; var createdDate = selectedContainer.querySelector('.radio-details--created').innerText; var updatedDate = selectedContainer.querySelector('.radio-details--updated').innerText; const imgElement = selectedContainer.querySelector('.radio-details--image'); renderSelectedWidget: function(index = 0) { var info = optionsFilter.getInfoLayer(index); var selectedWidget = optionsFilter.getSelectedWidget(index); var id = selectedWidget.querySelector('input[type="radio"]').value; var name = selectedWidget.querySelector('.radio-details--human-name').innerText; var description = selectedWidget.querySelector('.radio-details--description').innerText; var status = selectedWidget.querySelector('.radio-details_remote-status').innerText; var languages = selectedWidget.querySelector('.radio-details--languages').innerHTML; var source = selectedWidget.querySelector('.radio-details--source').innerText; var version = selectedWidget.querySelector('.radio-details--version').innerText; var createdDate = selectedWidget.querySelector('.radio-details--created').innerText; var updatedDate = selectedWidget.querySelector('.radio-details--updated').innerText; const imgElement = selectedWidget.querySelector('.radio-details--image'); var img = imgElement ? imgElement.outerHTML : ''; var previewUrl = selectedContainer.querySelector('.radio-details--preview-url').innerText; info.innerHTML = Drupal.theme('currentlySelectedClComponent', id, name, description, status, languages, version, source, createdDate, updatedDate, img, previewUrl); var previewUrl = selectedWidget.querySelector('.radio-details--preview-url').innerText; info.innerHTML = Drupal.theme('currentlySelectedWidget', id, name, description, status, languages, version, source, createdDate, updatedDate, img, previewUrl); if (previewUrl) { info.querySelector('a.button').addEventListener('click', (event) => { var button = event.target; Loading @@ -49,28 +181,88 @@ }); } info.hidden = false; }, }; const subscribeSearchboxToChanges = function(index = 0) { const container = optionsFilter.getContainer(index); const searchBox = container.querySelector('input.search-box'); if (searchBox === null) { return; } searchBox.addEventListener('input', function(event) { optionsFilter.setSearchboxValue(event.target.value, index); optionsFilter.refreshWidgets(index); }); }; const includeResetButton = function(index) { const container = optionsFilter.getContainer(index); const searchBox = container.querySelector('input.search-box'); if (searchBox === null) { return; } var resetButton = document.createElement('button'); resetButton.classList.add('reset-search-box'); resetButton.innerText = '⨯'; resetButton.title = 'Reset filter'; resetButton.addEventListener('click', function(event) { event.preventDefault(); container.querySelector('input.search-box').value = ''; optionsFilter.setSearchboxValue('', index); optionsFilter.refreshWidgets(index); event.target.blur(); }); searchBox.parentElement.insertBefore(resetButton, searchBox.nextSibling); }; const subscribeDeprecatedSearchboxToChanges = function(index = 0) { const container = optionsFilter.getContainer(index); const deprecationCheckbox = container.querySelector('input.deprecation-checkbox'); if (deprecationCheckbox === null) { return; } deprecationCheckbox.addEventListener('change', function(event) { optionsFilter.setDeprecatedFilterValue(event.target.checked, index); optionsFilter.refreshWidgets(index); }); }; const subscribeRadiosToChanges = function(index = 0) { const container = optionsFilter.getContainer(index); var radios = once('radio-change-subscribed', 'input[type="radio"]', container); radios.map(function(radio) { radio.addEventListener('change', function(event) { optionsFilter.selectWidget(event.target, index); optionsFilter.refreshWidgets(index); optionsFilter.renderSelectedWidget(index); }); }); }; /** * Set up options filter */ Drupal.behaviors.optionsFilter = { attach: (context, settings) => { var fieldsets = once('options-filter', '.widget-type--selector', context); for (var fieldset of fieldsets) { var search = fieldset.querySelector('input[type="search"]'); if (!search.value) { var selected = fieldset.querySelector('input[type="radio"][checked]'); search.value = selected ? selected.parentElement.parentElement.querySelector('.radio-details--machine-name').innerText : ''; } toggleRadioButtons(search.value, fieldset); subscribeToChanges(fieldset); once('options-filter', '.widget-type--selector', context).map(function(container, index) { optionsFilter.initIndex(index); optionsFilter.setContainer(container, index); subscribeSearchboxToChanges(index); subscribeDeprecatedSearchboxToChanges(index); subscribeRadiosToChanges(index); includeResetButton(index); optionsFilter.refreshWidgets(index); if (optionsFilter.getSelectedWidget(index) !== undefined) { optionsFilter.renderSelectedWidget(index); } }); }, }; Drupal.theme.currentlySelectedClComponent = (id, name, description, status, languages, version, source, createdDate, updatedDate, img, previewUrl) => ` <summary>${Drupal.t('ℹ️ More information about <em>@name</em>', { '@name': name })}</summary> Drupal.theme.currentlySelectedWidget = ( id, name, description, status, languages, version, source, createdDate, updatedDate, img, previewUrl) => ` <summary>${Drupal.t('ℹ️ More information about <em>@name</em>', {'@name': name})}</summary> <p>${description}</p> <div class='image-table--wrapper'> <table> Loading @@ -81,49 +273,16 @@ <tr><th>${Drupal.t('Status')}</th><td>${status}</td></tr> <tr><th>${Drupal.t('Available Languages')}</th><td>${languages}</td></tr> </table> <div class='currently-selected--image--wrapper${img ? '' : ' currently-selected--image--wrapper__empty'}'> <div class='currently-selected--image--wrapper${img ? '' : ' currently-selected--image--wrapper__empty'}'> ${img ? img : ''} </div> </div> ${previewUrl ? `<div style='display: none' id='preview-url'>${previewUrl}</div> <div class="try-now--wrapper"><a href="#preview-url" class='try-now button button--primary'>${Drupal.t('Try now')}</a></div>` <div class="try-now--wrapper"><a href="#preview-url" class='try-now button button--primary'>${Drupal.t( 'Try now')}</a></div>` : '' }`; /** * Render more info about the currently selected component. */ Drupal.behaviors.currentlySelected = { attach: (context, settings) => { var fieldsets = once('currently-selected', '.widget-type--selector', context); for (var fieldset of fieldsets) { var info = fieldset.querySelector('.currently-selected'); info.hidden = true; var selected = fieldset.querySelector('input[type="radio"][checked]'); if (selected) { selected.parentElement.parentElement.parentElement.classList.add('form-type--radio__selected'); renderCurrentlySelected(info, selected.parentElement.parentElement); } var radios = once('radio-change-subscribed', 'input[type="radio"]', fieldset); for (var radio of radios) { radio.addEventListener('change', (event) => { if (event.target.checked) { var all = event.target.parentElement.parentElement.parentElement.parentElement.querySelectorAll('input[type="radio"]'); for (var item of all) { item.parentElement.parentElement.parentElement.classList.remove('form-type--radio__selected'); } event.target.parentElement.parentElement.parentElement.classList.add('form-type--radio__selected'); renderCurrentlySelected(info, event.target.parentElement.parentElement); var searchElement = fieldset.querySelector('input[type="search"]'); var machineName = event.target.parentElement.parentElement.querySelector('.radio-details--machine-name').innerText; searchElement.value = machineName; toggleRadioButtons(machineName, fieldset); } }); } } }, }; }(once));
src/Element/WidgetSelectorElement.php +41 −18 Original line number Diff line number Diff line Loading @@ -106,17 +106,39 @@ class WidgetSelectorElement extends FormElement implements ContainerFactoryPlugi $default_widget_type = ($default_id ? $widget_types[$default_id] : NULL) ?? NULL; $element += [ '#attached' => ['library' => ['widget_type/selector']], 'search' => [ ]; // Widgets types can't be change once created, so hide search and deprecated searchbox if (!$default_widget_type) { $element['search'] = [ '#title' => $this->t('Search'), '#title_display' => 'hidden', '#type' => 'search', '#default_value' => $default_widget_type instanceof WidgetTypeInterface ? $default_widget_type->getRemoteId() : NULL, '#default_value' => $default_widget_type instanceof WidgetTypeInterface ? $default_widget_type->getRemoteId( ) : NULL, '#placeholder' => $this->t('Search for a widget type'), '#size' => 50, '#description' => $this->t('Start typing to search for a widget type.'), '#input' => FALSE, '#attributes' => [ 'class' => [ 'search-box', ], ], ]; $element['show_deprecated'] = [ '#type' => 'checkbox', '#title' => $this->t('Show deprecated widgets'), '#default_value' => FALSE, '#attributes' => [ 'class' => [ 'deprecation-checkbox', ], 'target_id' => [ ], ]; } $element['target_id'] = [ '#type' => 'radios', '#options' => $options, '#title' => $this->t('Widgets'), Loading @@ -126,12 +148,12 @@ class WidgetSelectorElement extends FormElement implements ContainerFactoryPlugi [Radios::class, 'processRadios'], [$this, 'processRadios'], ], '#weight' => 1, '#attributes' => [ 'class' => ['widget-type-selector--radios'], ], '#ajax' => $element['#ajax'] ?? FALSE, '#input' => FALSE, ], ]; $classes = $element['#attributes']['class'] ?? []; $classes[] = 'widget-type--selector'; Loading Loading @@ -172,6 +194,7 @@ class WidgetSelectorElement extends FormElement implements ContainerFactoryPlugi $element[$key]['#human_name'] = $widget_type->getName(); $element[$key]['#machine_name'] = $widget_type->getRemoteId(); $element[$key]['#remote_description'] = $widget_type->getDescription(); $element[$key]['#remote_status'] = $widget_type->getRemoteStatus(); $field_image = $widget_type->getPreviewImage(); $thumbnail = ['#markup' => '']; $image = ['#markup' => '']; Loading @@ -194,7 +217,7 @@ class WidgetSelectorElement extends FormElement implements ContainerFactoryPlugi '#uri' => $uri, '#attributes' => [ 'loading' => 'lazy', 'class' => ['radio-details--image'] 'class' => ['radio-details--image'], ], ]; } Loading Loading @@ -230,11 +253,11 @@ class WidgetSelectorElement extends FormElement implements ContainerFactoryPlugi return [ '#type' => 'html_tag', '#tag' => 'code', '#value' => $langcode '#value' => $langcode, ]; }, $widget_type->getRemoteLanguages() ) ), ]; } return $element; Loading
src/Entity/WidgetType.php +28 −4 Original line number Diff line number Diff line Loading @@ -233,8 +233,7 @@ final class WidgetType extends ContentEntityBase implements WidgetTypeInterface return []; } return $first->getValue(); } catch (MissingDataException $exception) { } catch (MissingDataException $exception) { return []; } } Loading @@ -249,8 +248,7 @@ final class WidgetType extends ContentEntityBase implements WidgetTypeInterface return []; } return $first->getValue(); } catch (MissingDataException $exception) { } catch (MissingDataException $exception) { return []; } } Loading Loading @@ -331,6 +329,21 @@ final class WidgetType extends ContentEntityBase implements WidgetTypeInterface return $this; } /** * {@inheritdoc} */ public function getRemoteStatus(): string { return $this->get('remote_widget_status')->value; } /** * {@inheritdoc} */ public function setRemoteStatus($remote_status): WidgetTypeInterface { $this->set('remote_widget_status', $remote_status); return $this; } /** * {@inheritdoc} */ Loading Loading @@ -452,6 +465,17 @@ final class WidgetType extends ContentEntityBase implements WidgetTypeInterface ->setDisplayConfigurable('view', TRUE) ->setReadOnly(TRUE); $fields['remote_widget_status'] = BaseFieldDefinition::create('string') ->setLabel(t('Remote Widget status')) ->setDescription(t('The list of files of the widget.')) ->setDisplayOptions('view', [ 'label' => 'above', 'type' => 'string', 'weight' => -2, ]) ->setDisplayConfigurable('view', FALSE) ->setReadOnly(TRUE); $fields['remote_widget_settings'] = BaseFieldDefinition::create('map') ->setLabel(t('Remote Widget Settings')) ->setDescription(t('The key/value settings from the widget server.')) Loading
src/WidgetTypeInterface.php +15 −1 Original line number Diff line number Diff line Loading @@ -5,7 +5,6 @@ namespace Drupal\widget_type; use Drupal\Core\Entity\ContentEntityInterface; use Drupal\Core\Entity\EntityChangedInterface; use Drupal\file\FileInterface; use Drupal\widget_type\Entity\WidgetType; /** * Provides an interface defining a widget type entity type. Loading Loading @@ -110,6 +109,21 @@ interface WidgetTypeInterface extends ContentEntityInterface, EntityChangedInter */ public function setRemoteLanguages(array $lang_codes): self; /** * @param $remote_status * * @return $this */ public function setRemoteStatus($remote_status): self; /** * Get the remote widget type status. * * @return string * The widget type description. */ public function getRemoteStatus(): string; /** * Get the widget type settings. * Loading