diff --git a/project_browser.module b/project_browser.module index 211b4d17c3c9e7e5013aed3bf79cb811b1d359bc..e168a2f1c4a1de911e370e5d1fe2440fc224a13d 100644 --- a/project_browser.module +++ b/project_browser.module @@ -38,7 +38,7 @@ function project_browser_help(string $route_name, RouteMatchInterface $route_mat function project_browser_theme(): array { return [ 'project_browser_main_app' => [ - 'variables' => [], + 'render element' => 'element', ], ]; } @@ -59,3 +59,13 @@ function project_browser_project_browser_source_info_alter(array &$definitions): ]; } } + +/** + * Preprocess function for the project_browser_main_app theme hook. + * + * @param array $variables + * The variables to pass to the template. + */ +function template_preprocess_project_browser_main_app(array &$variables): void { + $variables['id'] = $variables['element']['#id']; +} diff --git a/src/Element/ProjectBrowser.php b/src/Element/ProjectBrowser.php index 757a955cd7a2c6165f9b6d4193454c606cbb5901..7d9652524651c08010459c50966b176fdcfb8858 100644 --- a/src/Element/ProjectBrowser.php +++ b/src/Element/ProjectBrowser.php @@ -3,6 +3,7 @@ namespace Drupal\project_browser\Element; use Drupal\Component\Utility\DeprecationHelper; +use Drupal\Component\Uuid\UuidInterface; use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\DependencyInjection\DependencySerializationTrait; use Drupal\Core\Extension\ModuleHandlerInterface; @@ -31,6 +32,7 @@ final class ProjectBrowser implements ElementInterface, ContainerFactoryPluginIn private readonly ?InstallReadiness $installReadiness, private readonly ModuleHandlerInterface $moduleHandler, private readonly ConfigFactoryInterface $configFactory, + private readonly UuidInterface $uuid, ) {} /** @@ -60,6 +62,7 @@ final class ProjectBrowser implements ElementInterface, ContainerFactoryPluginIn $install_readiness, $container->get(ModuleHandlerInterface::class), $container->get(ConfigFactoryInterface::class), + $container->get(UuidInterface::class), ); } @@ -94,8 +97,10 @@ final class ProjectBrowser implements ElementInterface, ContainerFactoryPluginIn */ public function attachProjectBrowserSettings(array $element): array { assert($element['#source'] instanceof ProjectBrowserSourceInterface); + $element['#id'] ??= $this->uuid->generate(); $element['#attached']['drupalSettings']['project_browser'] = $this->getDrupalSettings( $element['#source'], + $element['#id'], $element['#max_selections'] ?? $this->configFactory->get('project_browser.admin_settings')->get('max_selections') ?? NULL, ); return $element; @@ -106,6 +111,8 @@ final class ProjectBrowser implements ElementInterface, ContainerFactoryPluginIn * * @param \Drupal\project_browser\Plugin\ProjectBrowserSourceInterface $source * The source plugin to query for projects. + * @param string $instance_id + * An identifier for the project browser application instance. * @param int|null $max_selections * (optional) The maximum number of project to install at once, or NULL for * no limit. Defaults to NULL. @@ -113,7 +120,7 @@ final class ProjectBrowser implements ElementInterface, ContainerFactoryPluginIn * @return array * An array of Drupal settings. */ - private function getDrupalSettings(ProjectBrowserSourceInterface $source, ?int $max_selections = NULL): array { + private function getDrupalSettings(ProjectBrowserSourceInterface $source, string $instance_id, ?int $max_selections = NULL): array { if (is_int($max_selections) && $max_selections <= 0) { throw new \InvalidArgumentException('$max_selections must be a positive integer or NULL.'); } @@ -139,6 +146,12 @@ final class ProjectBrowser implements ElementInterface, ContainerFactoryPluginIn 'package_manager' => $package_manager, 'filters' => (object) $source->getFilterDefinitions(), 'max_selections' => $max_selections, + 'instances' => [ + $instance_id => [ + 'source' => $source->getPluginId(), + 'filters' => (object) $source->getFilterDefinitions(), + ], + ], ]; } diff --git a/sveltejs/public/build/bundle.js b/sveltejs/public/build/bundle.js index c8deb70568ae814b78c9366f9c9da271ea6c5501..b292e3dea9414b38e5a2359c84124c5219936eef 100644 Binary files a/sveltejs/public/build/bundle.js and b/sveltejs/public/build/bundle.js differ diff --git a/sveltejs/public/build/bundle.js.map b/sveltejs/public/build/bundle.js.map index d12bb3ab15ac84b0d4e4db3039d51afce0a39353..e57323ad96d99f3dce56a724231d6f4aff882e35 100644 Binary files a/sveltejs/public/build/bundle.js.map and b/sveltejs/public/build/bundle.js.map differ diff --git a/sveltejs/src/App.svelte b/sveltejs/src/App.svelte index ab2aa018ebf5e11b4fe673a1ad3854df4419390b..6cf7ef8bc2c7ba41e6f4077a9676c9e281479869 100644 --- a/sveltejs/src/App.svelte +++ b/sveltejs/src/App.svelte @@ -1,6 +1,9 @@ <script> import ProjectBrowser from './ProjectBrowser.svelte'; + // eslint-disable-next-line import/prefer-default-export + export let id; + // Removes initial loader if it exists. const initialLoader = document.getElementById('initial-loader'); if (initialLoader) { @@ -8,4 +11,4 @@ } </script> -<ProjectBrowser /> +<ProjectBrowser {id} /> diff --git a/sveltejs/src/ProjectBrowser.svelte b/sveltejs/src/ProjectBrowser.svelte index 7687836300fa8c728838fac75909b89aaa6d7fd6..bff7a4b115a555ab858dcb8b24439bb5ee979e75 100644 --- a/sveltejs/src/ProjectBrowser.svelte +++ b/sveltejs/src/ProjectBrowser.svelte @@ -31,9 +31,12 @@ } from './constants'; // cspell:ignore tabwise - const { Drupal } = window; + const { Drupal, drupalSettings } = window; const { announce } = Drupal; + export let id; + const settings = drupalSettings.project_browser.instances[id]; + let data; let rows = []; let sources = []; @@ -286,6 +289,7 @@ on:selectCategory={onSelectCategory} {searchText} {refreshLiveRegion} + filterDefinitions={settings.filters} /> <div class="pb-layout__header"> diff --git a/sveltejs/src/Search/BooleanFilter.svelte b/sveltejs/src/Search/BooleanFilter.svelte index 906cf43cc1358acdbc05c8565762374959e929dc..8cd94aee449e740d1c22a8c825be33f4a71d1f1f 100644 --- a/sveltejs/src/Search/BooleanFilter.svelte +++ b/sveltejs/src/Search/BooleanFilter.svelte @@ -1,11 +1,12 @@ <script> import { filters } from '../stores'; - export let name; + export let definition; + + const { name, on_label: onLabel, off_label: offLabel } = definition; + export let changeHandler; export let type; - export let onLabel; - export let offLabel; </script> <div class="filter-group__filter-options form-item"> diff --git a/sveltejs/src/Search/Search.svelte b/sveltejs/src/Search/Search.svelte index 68ffd971427f47eb8277670f82213ea34c6658d6..fc2273a57f536eeed7853b9cdc947a358da01a66 100644 --- a/sveltejs/src/Search/Search.svelte +++ b/sveltejs/src/Search/Search.svelte @@ -13,7 +13,7 @@ searchString, sortCriteria, } from '../stores'; - import { FULL_MODULE_PATH, DARK_COLOR_SCHEME, FILTERS } from '../constants'; + import { FULL_MODULE_PATH, DARK_COLOR_SCHEME } from '../constants'; const { Drupal } = window; const dispatch = createEventDispatcher(); @@ -39,6 +39,8 @@ let sortText = sortMatch.text; let filterComponent; + export let filterDefinitions; + export async function onSearch(event) { const state = stateContext.getState(); const detail = { @@ -74,7 +76,7 @@ if (event) { const filterName = event.target.name; - if (FILTERS[filterName]._type === 'boolean') { + if (filterDefinitions[filterName]._type === 'boolean') { $filters[filterName] = event.target.value === 'true'; } else { $filters[filterName] = event.target.value; @@ -116,8 +118,6 @@ document.getElementById('pb-text').focus(); } - const filterDefinitions = Object.entries(FILTERS); - /** * Resets the filters to the initial values provided by the source. * @@ -127,7 +127,7 @@ */ const resetFilters = (clear) => { $filters = {}; - filterDefinitions.forEach(([name, definition]) => { + Object.entries(filterDefinitions).forEach(([name, definition]) => { let value; if (clear) { if (definition._type === 'boolean') { @@ -205,16 +205,14 @@ </button> </div> </div> - {#if filterDefinitions.length !== 0} + {#if Object.keys(filterDefinitions).length !== 0} <div class="search__form-filters-container"> <div class="search__form-filters"> - {#each filterDefinitions as [filterType, filter]} + {#each Object.entries(filterDefinitions) as [filterType, filter]} {#if filter._type === 'boolean'} <BooleanFilter - name={filter.name} + definition={filter} type={filterType} - onLabel={filter.on_label} - offLabel={filter.off_label} changeHandler={onAdvancedFilter} /> {:else if filter._type === 'multiple_choice'} diff --git a/sveltejs/src/main.js b/sveltejs/src/main.js index 74fc6d16eb8fdd6d9a0a060be14e50e3fe36576e..2e1178d0c50a826fc9da23ebfed29d50480c7fd8 100644 --- a/sveltejs/src/main.js +++ b/sveltejs/src/main.js @@ -1,9 +1,13 @@ import App from './App.svelte'; +const element = document.querySelector('[data-project-browser-instance-id]'); + const app = new App({ // The #project-browser markup is returned by the project_browser.browse Drupal route. - target: document.querySelector('#project-browser'), - props: {}, + target: element, + props: { + id: element.getAttribute('data-project-browser-instance-id'), + }, }); export default app; diff --git a/templates/project-browser-main-app.html.twig b/templates/project-browser-main-app.html.twig index f3768f4f0fbd97e76a6e43ee50adb6fb8d20d10c..3a87f6cef7fa65d2841b64546d8bfcbb681537fa 100644 --- a/templates/project-browser-main-app.html.twig +++ b/templates/project-browser-main-app.html.twig @@ -11,4 +11,4 @@ </div> </div> -<div id="project-browser"></div> +<div data-project-browser-instance-id="{{ id }}" id="project-browser"></div>