decidim-homepage_proposals 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE-AGPLv3.txt +661 -0
  3. data/README.md +32 -0
  4. data/Rakefile +30 -0
  5. data/app/cells/decidim/homepage_proposals/content_blocks/proposals_slider/show.erb +37 -0
  6. data/app/cells/decidim/homepage_proposals/content_blocks/proposals_slider_cell.rb +56 -0
  7. data/app/cells/decidim/homepage_proposals/content_blocks/proposals_slider_settings_form/show.erb +13 -0
  8. data/app/cells/decidim/homepage_proposals/content_blocks/proposals_slider_settings_form_cell.rb +36 -0
  9. data/app/controllers/decidim/proposals_slider_controller.rb +74 -0
  10. data/app/packs/entrypoints/decidim_homepage_proposals.js +5 -0
  11. data/app/packs/entrypoints/decidim_homepage_proposals_admin.js +1 -0
  12. data/app/packs/images/decidim/homepage_proposals/icon.svg +1 -0
  13. data/app/packs/images/decidim/homepage_proposals/slider_proposal_image.jpeg +0 -0
  14. data/app/packs/src/decidim/homepage_proposals/admin.js +16 -0
  15. data/app/packs/src/decidim/homepage_proposals/glideBuilder.js +49 -0
  16. data/app/packs/src/decidim/homepage_proposals/glidejs/Manager.js +130 -0
  17. data/app/packs/src/decidim/homepage_proposals/glidejs/glideItems/GlideItem.js +17 -0
  18. data/app/packs/src/decidim/homepage_proposals/glidejs/glideItems/NotFound.js +23 -0
  19. data/app/packs/src/decidim/homepage_proposals/glidejs/glideItems/Proposal.js +51 -0
  20. data/app/packs/src/decidim/homepage_proposals/main.js +19 -0
  21. data/app/packs/stylesheets/decidim/homepage_proposals/homepage_proposals.scss +45 -0
  22. data/app/views/decidim/shared/homepage_proposals/_filters.erb +47 -0
  23. data/app/views/decidim/shared/homepage_proposals/_filters_small_view.erb +18 -0
  24. data/config/assets.rb +10 -0
  25. data/config/i18n-tasks.yml +10 -0
  26. data/config/locales/en.yml +33 -0
  27. data/config/routes.rb +5 -0
  28. data/lib/decidim/homepage_proposals/admin.rb +10 -0
  29. data/lib/decidim/homepage_proposals/admin_engine.rb +24 -0
  30. data/lib/decidim/homepage_proposals/engine.rb +41 -0
  31. data/lib/decidim/homepage_proposals/test/factories.rb +17 -0
  32. data/lib/decidim/homepage_proposals/version.rb +13 -0
  33. data/lib/decidim/homepage_proposals.rb +12 -0
  34. data/lib/tasks/homepage_proposals.rake +25 -0
  35. data/lib/tasks/homepage_proposals_webpacker_tasks.rake +14 -0
  36. metadata +92 -0
data/README.md ADDED
@@ -0,0 +1,32 @@
1
+ # Decidim::HomepageProposals
2
+
3
+ Homepage slider for proposals.
4
+
5
+ ## Usage
6
+
7
+ HomepageProposals will be available as a Component for a Participatory
8
+ Space.
9
+
10
+ ## Installation
11
+
12
+ Add this line to your application's Gemfile:
13
+
14
+ ```ruby
15
+ gem "decidim-homepage_proposals"
16
+ ```
17
+
18
+ And then execute:
19
+
20
+ ```bash
21
+ bundle
22
+ bundle exec rake decidim_module_homepage_proposals:webpacker:install
23
+ bundle exec rake assets:precompile
24
+ ```
25
+
26
+ ## Contributing
27
+
28
+ See [Decidim](https://github.com/decidim/decidim).
29
+
30
+ ## License
31
+
32
+ This engine is distributed under the GNU AFFERO GENERAL PUBLIC LICENSE.
data/Rakefile ADDED
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "decidim/dev/common_rake"
4
+
5
+ desc "Generates a dummy app for testing"
6
+ task test_app: "decidim:generate_external_test_app"
7
+
8
+ desc "Generates a development app."
9
+ task :development_app do
10
+ Bundler.with_original_env do
11
+ generate_decidim_app(
12
+ "development_app",
13
+ "--app_name",
14
+ "#{base_app_name}_development_app",
15
+ "--path",
16
+ "..",
17
+ "--recreate_db",
18
+ "--seed_db",
19
+ "--demo",
20
+ "--profiling"
21
+ )
22
+ end
23
+ seed_slider("development_app")
24
+ end
25
+
26
+ def seed_slider(path)
27
+ Dir.chdir(path) do
28
+ system("bundle exec rake decidim:homepage_proposals:seed")
29
+ end
30
+ end
@@ -0,0 +1,37 @@
1
+ <section class="wrapper-home home-section" xmlns="http://www.w3.org/1999/xhtml">
2
+ <div class="row collapse">
3
+ <h3 class="section-heading"><%= t("decidim.homepage_proposals.proposal_at_a_glance.title") %></h3>
4
+
5
+
6
+ <div class="filters_container" <%= 'style="display: none"' unless content_block_settings.activate_filters %>>
7
+ <%= render partial: "decidim/shared/homepage_proposals/filters_small_view" %>
8
+ <div class="show-for-mediumlarge">
9
+ <%= render partial: "decidim/shared/homepage_proposals/filters" %>
10
+ </div>
11
+ </div>
12
+
13
+
14
+ <div id="proposals_slider">
15
+ <div class="glide margin-top-3">
16
+ <div class="column small-12">
17
+ <p class="loading text-center"><%= t(".loading") %></p>
18
+ </div>
19
+ <div class="glide__track row small-12 column" data-glide-el="track">
20
+ <ul class="glide__slides" id="proposals_glide_items">
21
+ <%# Content loaded by 'app/packs/src/decidim/homepage_proposals/main.js' %>
22
+ </ul>
23
+ </div>
24
+ <div class="glide__bullets" data-glide-el="controls[nav]">
25
+ <div class="glide__bullet" data-glide-dir="<">
26
+ <%= icon "chevron-left", class: "icon--large" %>
27
+ </div>
28
+ <div class="glide__bullet" data-glide-dir=">">
29
+ <%= icon "chevron-right", class: "icon--large" %>
30
+ </div>
31
+ </div>
32
+ </div>
33
+ </div>
34
+ </div>
35
+ </section>
36
+
37
+ <%= javascript_pack_tag 'decidim_homepage_proposals' %>
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module HomepageProposals
5
+ module ContentBlocks
6
+ class ProposalsSliderCell < Decidim::ViewModel
7
+ attr_accessor :glanced_proposals
8
+
9
+ include Cell::ViewModel::Partial
10
+ include Core::Engine.routes.url_helpers
11
+ include Decidim::IconHelper
12
+ include ActionView::Helpers::FormOptionsHelper
13
+ include Decidim::FiltersHelper
14
+ include Decidim::FilterResource
15
+
16
+ private
17
+
18
+ def content_block_settings
19
+ @content_block_settings ||= Decidim::ContentBlock.find_by(
20
+ manifest_name: "proposals_slider",
21
+ organization: current_organization
22
+ ).settings
23
+ end
24
+
25
+ def options_for_default_component
26
+ components = Decidim::Component.where(id: content_block_settings.linked_components_id.compact)
27
+ options = components.map do |component|
28
+ ["#{translated_attribute(component.name)} (#{translated_attribute(component.participatory_space.title)})", component.id]
29
+ end
30
+
31
+ options_for_select(options, selected: selected_component_id)
32
+ end
33
+
34
+ def linked_components
35
+ @linked_components ||= Decidim::Component.where(id: content_block_settings.linked_components_id.compact)
36
+ end
37
+
38
+ def default_filter_params
39
+ {
40
+ scope_id: nil,
41
+ category_id: nil,
42
+ component_id: nil
43
+ }
44
+ end
45
+
46
+ def categories_filter
47
+ @categories_filter ||= Decidim::Category.where(id: linked_components.map(&:categories).flatten)
48
+ end
49
+
50
+ def selected_component_id
51
+ @selected_component_id ||= params.dig(:filter, :component_id) || content_block_settings.default_linked_component
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,13 @@
1
+ <% form.fields_for :settings, form.object.settings do |settings_fields| %>
2
+ <%= settings_fields.check_box :activate_filters, label: t(".activate_filters") %>
3
+ <%= settings_fields.select :linked_components_id,
4
+ options_for_proposals_components,
5
+ { include_blank: true, label: t(".linked_components_id") },
6
+ { multiple: true, class: "chosen-select" } %>
7
+ <%= settings_fields.select :default_linked_component,
8
+ options_for_default_component,
9
+ { include_blank: false, label: t(".default_linked_component") },
10
+ class: "chosen-select" %>
11
+ <% end %>
12
+
13
+ <%= javascript_pack_tag "decidim_homepage_proposals_admin" %>
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module HomepageProposals
5
+ module ContentBlocks
6
+ class ProposalsSliderSettingsFormCell < Decidim::ViewModel
7
+ include ActionView::Helpers::FormOptionsHelper
8
+
9
+ alias form model
10
+
11
+ def content_block
12
+ options[:content_block]
13
+ end
14
+
15
+ def options_for_proposals_components
16
+ options = proposals_components.map do |proposal_component|
17
+ ["#{translated_attribute(proposal_component.name)} (#{translated_attribute(proposal_component.participatory_space.title)})", proposal_component.id]
18
+ end
19
+ options_for_select(options, selected: content_block.settings.linked_components_id)
20
+ end
21
+
22
+ def options_for_default_component
23
+ components = Decidim::Component.where(id: content_block.settings.linked_components_id.compact)
24
+ options = components.map do |component|
25
+ ["#{translated_attribute(component.name)} (#{translated_attribute(component.participatory_space.title)})", component.id]
26
+ end
27
+ options_for_select(options, selected: content_block.settings.default_linked_component)
28
+ end
29
+
30
+ def proposals_components
31
+ @proposals_components ||= Decidim::Component.where(manifest_name: "proposals").published
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ class ProposalsSliderController < Decidim::ApplicationController
5
+ include Decidim::FilterResource
6
+ include Decidim::TranslatableAttributes
7
+ include Decidim::Core::Engine.routes.url_helpers
8
+ include Decidim::ComponentPathHelper
9
+
10
+ def refresh_proposals
11
+ render json: build_proposals_api
12
+ end
13
+
14
+ private
15
+
16
+ def build_proposals_api
17
+ return component_url unless glanced_proposals.any?
18
+
19
+ glanced_proposals.flat_map do |proposal|
20
+ {
21
+ id: proposal.id,
22
+ title: translated_attribute(proposal.title).truncate(40),
23
+ body: translated_attribute(proposal.body).truncate(150),
24
+ url: proposal_path(proposal),
25
+ image: image_for(proposal),
26
+ state: proposal.state
27
+ }
28
+ end
29
+ end
30
+
31
+ def glanced_proposals
32
+ if params[:filter].present?
33
+ category = Decidim::Category.find(params.dig(:filter, :category_id)) if params.dig(:filter, :category_id).present?
34
+ scopes = Decidim::Scope.find(params.dig(:filter, :scope_id)) if params.dig(:filter, :scope_id).present?
35
+ end
36
+
37
+ @glanced_proposals ||= Decidim::Proposals::Proposal.published
38
+ .where(component: params.dig(:filter, :component_id))
39
+ .where(filter_by_scopes(scopes))
40
+ .select do |proposal|
41
+ if category.present?
42
+ proposal.category == category
43
+ else
44
+ true
45
+ end
46
+ end
47
+ .sample(12)
48
+ end
49
+
50
+ def filter_by_scopes(scopes)
51
+ { scope: scopes } if scopes.present?
52
+ end
53
+
54
+ def proposal_path(proposal)
55
+ Decidim::ResourceLocatorPresenter.new(proposal).path
56
+ end
57
+
58
+ def image_for(proposal)
59
+ return view_context.image_pack_url("media/images/slider_proposal_image.jpeg") unless proposal.attachments.select(&:image?).any?
60
+
61
+ proposal.attachments.select(&:image?).first&.url
62
+ end
63
+
64
+ def component_url
65
+ return { url: "/" } if params.dig(:filter, :component_id).blank?
66
+
67
+ begin
68
+ { url: main_component_path(Decidim::Component.find(params.dig(:filter, :component_id))) }
69
+ rescue ActiveRecord::RecordNotFound
70
+ { url: "/" }
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,5 @@
1
+ // Images
2
+ require.context("../images", true)
3
+
4
+ import "src/decidim/homepage_proposals/main"
5
+ import "src/decidim/form_filter.js"
@@ -0,0 +1 @@
1
+ import "src/decidim/homepage_proposals/admin"
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 35 35"><path d="M17.5 35A17.5 17.5 0 1 1 35 17.5 17.52 17.52 0 0 1 17.5 35zm0-33.06A15.56 15.56 0 1 0 33.06 17.5 15.57 15.57 0 0 0 17.5 1.94zm9.5 13.7H8a1 1 0 0 1 0-1.94h19a1 1 0 0 1 0 1.94zm0 3.68H8a1 1 0 0 1 0-1.94h19a1 1 0 0 1 0 1.94zM22.26 23H8a1 1 0 0 1 0-1.94h14.26a1 1 0 0 1 0 1.94z"/></svg>
@@ -0,0 +1,16 @@
1
+ $(document).ready(function() {
2
+ $('#content_block_settings_linked_components_id').on('change', function() {
3
+ var selectedOptions = $(this).val(); // Get the selected options from the multiselect field
4
+ var $selectedOptionsField = $('#content_block_settings_default_linked_component'); // Get the select field for the selected options
5
+
6
+ $selectedOptionsField.empty(); // Clear the select field
7
+
8
+ // Add the selected options to the select field
9
+ if (selectedOptions && selectedOptions.length > 0) {
10
+ selectedOptions.forEach(function(option) {
11
+ var optionText = $('#content_block_settings_linked_components_id option[value="' + option + '"]').text();
12
+ $selectedOptionsField.append($('<option>', { value: option, text: optionText }));
13
+ });
14
+ }
15
+ });
16
+ });
@@ -0,0 +1,49 @@
1
+ import Glide from "@glidejs/glide";
2
+
3
+ export default class GlideBuilder {
4
+ constructor(selector = '.glide', type = 'carousel') {
5
+ this.type = type
6
+ this.setOpts()
7
+ this.glide = new Glide(selector, this.options)
8
+
9
+ this.bindings()
10
+ }
11
+
12
+ static pervView() {
13
+ return 4;
14
+ }
15
+
16
+ destroy() {
17
+ this.glide.destroy();
18
+ }
19
+
20
+ disable() {
21
+ this.glide.disable();
22
+ }
23
+
24
+ mount() {
25
+ this.glide.mount()
26
+ }
27
+
28
+ bindings() {
29
+ this.glide.on("run", () => {
30
+ let bulletNumber = this.glide.index;
31
+ $($(".glide__bullets").children()).css("color", "lightgrey");
32
+ $($(".glide__bullets").children().get(bulletNumber + 1)).css("color", "grey");
33
+ });
34
+ }
35
+
36
+ setOpts() {
37
+ this.options = {
38
+ type: this.type,
39
+ startAt: 0,
40
+ autoplay: 0,
41
+ perView: GlideBuilder.pervView(),
42
+ hoverpause: true,
43
+ breakpoints: {
44
+ 1024: { perView: 3 }, 768: { perView: 2 }, 480: { perView: 1 }
45
+ },
46
+ perTouch: 1
47
+ }
48
+ }
49
+ }
@@ -0,0 +1,130 @@
1
+ import FormFilterComponents from "src/decidim/form_filter.js";
2
+ import GlideBuilder from "../glideBuilder";
3
+ import GlideItem from "./glideItems/GlideItem";
4
+ import Proposal from "./glideItems/Proposal";
5
+ import NotFound from "./glideItems/NotFound";
6
+
7
+
8
+ // Manager communicates with API and build the HTML for the Glide.js carousel
9
+ // Example :
10
+ // let manager = new Manager($proposalsSlider, $proposalsGlideItems, $glideBullets, $formFilter)
11
+ // manager.start().then(() => manager.glide.mount())
12
+ //
13
+ // @return - Instance of Manager
14
+ export default class Manager {
15
+ constructor($proposalsSlider, $proposalsGlideItems, $glideBullets, $formFilter) {
16
+ this.$proposalSlider = $proposalsSlider;
17
+ this.$proposalsGlideItems = $proposalsGlideItems;
18
+ this.formFilterComponent = new FormFilterComponents($formFilter);
19
+ this.$loading = this.$proposalSlider.find(".loading");
20
+ }
21
+
22
+ // @return String - API URL with filter params
23
+ APIUrl() {
24
+ return '/proposals_slider/refresh_proposals' + this.filterURIParams();
25
+ }
26
+
27
+ // @return String - Filter params query string
28
+ filterURIParams() {
29
+ return this.formFilterComponent._currentStateAndPath()[0];
30
+ }
31
+
32
+ // Clears Glide carousel and display loader
33
+ // @return void
34
+ startLoading() {
35
+ let height = $(".glide__slides").css("height");
36
+ this.clearGlideItems();
37
+ this.$loading.css("height", height);
38
+ this.$loading.show();
39
+ }
40
+
41
+ // Hide loader
42
+ // @return void
43
+ endLoading() {
44
+ this.$loading.hide();
45
+ }
46
+
47
+ // Clears glide items and glide bullets
48
+ // @return void
49
+ clearGlideItems() {
50
+ this.$proposalsGlideItems.empty();
51
+ $(".glide__bullet.glide__bullet_idx").remove()
52
+ }
53
+
54
+ // We must disable existing glide carousel before refresh
55
+ // @return void
56
+ disableGlide() {
57
+ if (this.glide !== undefined) {
58
+ this.glide.disable()
59
+ }
60
+ }
61
+
62
+ // Send request to API and create items received in Glide.js carousel
63
+ // @return this.glide
64
+ start() {
65
+ this.startLoading();
66
+ this.disableGlide()
67
+
68
+ $.get(this.APIUrl())
69
+ .done((res) => {
70
+ this.generateGlides(res)
71
+ })
72
+ .fail(() => {
73
+ this.generateGlides([])
74
+ })
75
+ .always((res) => {
76
+ this.glide = new GlideBuilder('.glide', 'carousel');
77
+
78
+ if (res.length <= 1 || res.status === 500) {
79
+ this.glide.disable()
80
+ }
81
+ this.endLoading();
82
+
83
+ this.glide.mount()
84
+ })
85
+ }
86
+
87
+ // Creates Glide Items
88
+ // @return void
89
+ generateGlides(res) {
90
+ if (res.url) {
91
+ this.createProposalsNotFound(res.url);
92
+ return;
93
+ }
94
+
95
+ this.createProposals(res)
96
+ }
97
+
98
+ // Create Glide Items with:
99
+ // - A Not Found GlideItem
100
+ // - 3 placeholders
101
+ // @return void
102
+ createProposalsNotFound(url) {
103
+ const notFoundGlide = new NotFound(url)
104
+ this.$proposalsGlideItems.append(notFoundGlide.render())
105
+ $(".glide__bullets > .glide__bullet:last").before(notFoundGlide.bullet(0));
106
+
107
+ for (let i = 0; i < GlideBuilder.pervView() - 1; i++) {
108
+ this.$proposalsGlideItems.append(notFoundGlide.placeholder());
109
+ }
110
+ }
111
+
112
+ // Create Glide Items proposals, if proposals length is smaller than GlideBuilder.pervView() (default: 4)
113
+ // Then it creates placeholders until reaching 4 Glide Items
114
+ // @return void
115
+ createProposals(proposals) {
116
+ for (let i = 0; i < proposals.length; i++) {
117
+ let proposalGlide = new Proposal(proposals[i])
118
+ this.$proposalsGlideItems.append(proposalGlide.render());
119
+ $(".glide__bullets > .glide__bullet:last").before(proposalGlide.bullet(i));
120
+ }
121
+
122
+ if (proposals.length < GlideBuilder.pervView()) {
123
+ let missingCount = GlideBuilder.pervView() - proposals.length
124
+
125
+ for (let i = 0; i < missingCount; i++) {
126
+ this.$proposalsGlideItems.append(new GlideItem(null).placeholder());
127
+ }
128
+ }
129
+ }
130
+ }
@@ -0,0 +1,17 @@
1
+ export default class GlideItem {
2
+ constructor(url) {
3
+ this.url = url;
4
+ }
5
+
6
+ placeholder() {
7
+ return `<div class="column glide__slide"></div>`
8
+ }
9
+
10
+ bullet(idx) {
11
+ return `<div class="glide__bullet glide__bullet_idx" data-glide-dir="=${idx}">
12
+ <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" fill="currentColor" class="bi bi-circle-fill" viewBox="0 0 16 16">
13
+ <circle cx="7" cy="7" r="7"/>
14
+ </svg>
15
+ </div>`
16
+ }
17
+ }
@@ -0,0 +1,23 @@
1
+ import GlideItem from "./GlideItem";
2
+
3
+ export default class NotFound extends GlideItem {
4
+ render() {
5
+ return `<div class="column glide__slide">
6
+ <div class="card card--proposal card--stack">
7
+ <div class="card--header"></div>
8
+ <div class="card--content text-center margin-top-1">
9
+ <h3 class="card__title">No match</h3>
10
+ <div class="card__text--paragraph padding-top-1">
11
+ <p>No proposals found for this request</p>
12
+ </div>
13
+ <a href="${this.url}" class="button--clear-filters">
14
+ <div class="card__button align-bottom">
15
+ <span class="button button--secondary">Visit proposals</span>
16
+ </div>
17
+ </a>
18
+ </div>
19
+ </div>
20
+ </div>`
21
+ }
22
+
23
+ }
@@ -0,0 +1,51 @@
1
+ import GlideItem from "./GlideItem";
2
+
3
+ export default class Proposal extends GlideItem {
4
+ constructor(obj) {
5
+ super();
6
+ this.title = obj.title;
7
+ this.body = obj.body;
8
+ this.image = obj.image;
9
+ this.url = obj.url;
10
+ this.state = obj.state || 'not answered' ;
11
+ this.color = this.colorFromState(this.state);
12
+ }
13
+
14
+ colorFromState(state) {
15
+ switch(state){
16
+ case 'accepted':
17
+ return 'success';
18
+ case 'rejected':
19
+ return 'alert';
20
+ case 'evaluating':
21
+ return 'warning';
22
+ default:
23
+ return 'muted';
24
+ }
25
+ }
26
+
27
+ render() {
28
+ return `<div class="column glide__slide">
29
+ <div class="card card--proposal card--stack">
30
+ <a href="${this.url}">
31
+ <div class="proposal-glance card--header">
32
+ <img src="${this.image}" class="proposal-glance__img" alt="slider_img">
33
+ </div>
34
+ </a>
35
+ <div class="card--process__small text-center padding-1">
36
+ <span class="${this.color} card__text--status status_slider"> ${this.state.charAt(0).toUpperCase() + this.state.slice(1)} </span>
37
+ <a href="${this.url}"><h3 class="proposal-glance card__title">${this.title}</h3></a>
38
+ <div class="card__text--paragraph padding-top-1">
39
+ <p>${this.body}</p>
40
+ </div>
41
+ <a href="${this.url}">
42
+ <div class="card__button align-bottom padding-top-1">
43
+ <span class="button small button--secondary">Visit</span>
44
+ </div>
45
+ </a>
46
+ </div>
47
+ </div>
48
+ </div>`
49
+ }
50
+
51
+ }
@@ -0,0 +1,19 @@
1
+ import Manager from "./glidejs/Manager"
2
+
3
+ $(() => {
4
+ const $proposalsSlider = $("#proposals_slider");
5
+ const $proposalsGlideItems = $("#proposals_glide_items");
6
+ const $filterForm = $("#filters-form");
7
+ const $smallFilterForm = $("#filter-box form");
8
+ const slider = new Manager($proposalsSlider, $proposalsGlideItems, $(".glide__bullet.glide__bullet_idx"), $filterForm);
9
+ const smallSlider = new Manager($proposalsSlider, $proposalsGlideItems, $(".glide__bullet.glide__bullet_idx"), $smallFilterForm);
10
+ slider.start()
11
+
12
+ $filterForm.on("change", () => {
13
+ slider.start()
14
+ });
15
+
16
+ $smallFilterForm.on("change", () => {
17
+ smallSlider.start()
18
+ });
19
+ });
@@ -0,0 +1,45 @@
1
+ @import "@glidejs/glide/src/assets/sass/glide.core";
2
+
3
+ .card__text--paragraph {
4
+ height: 150px;
5
+ }
6
+
7
+ // glide's relative position prevents filters from being clicked
8
+ .glide {
9
+ position: inherit !important;
10
+ margin-top: 0 !important;
11
+ }
12
+
13
+ .glide__bullets {
14
+ display: flex;
15
+ justify-content: center;
16
+ align-items: center;
17
+ }
18
+
19
+ .glide__bullet {
20
+ margin-right: 0.5vw;
21
+ margin-left: 0.5vw;
22
+ color: lightgrey;
23
+ }
24
+
25
+ .glide__bullet:nth-of-type(2) {
26
+ color: grey;
27
+ }
28
+
29
+ .glide__bullet:hover {
30
+ color: grey;
31
+ }
32
+
33
+ .proposal-glance.card--header {
34
+ height: 120px;
35
+ }
36
+
37
+ .proposal-glance__img {
38
+ width:100%;
39
+ height:100%;
40
+ object-fit:cover;
41
+ }
42
+
43
+ .proposal-glance.card__title {
44
+ max-height: 30px;
45
+ }