decidim-homepage_proposals 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE-AGPLv3.txt +661 -0
- data/README.md +32 -0
- data/Rakefile +30 -0
- data/app/cells/decidim/homepage_proposals/content_blocks/proposals_slider/show.erb +37 -0
- data/app/cells/decidim/homepage_proposals/content_blocks/proposals_slider_cell.rb +56 -0
- data/app/cells/decidim/homepage_proposals/content_blocks/proposals_slider_settings_form/show.erb +13 -0
- data/app/cells/decidim/homepage_proposals/content_blocks/proposals_slider_settings_form_cell.rb +36 -0
- data/app/controllers/decidim/proposals_slider_controller.rb +74 -0
- data/app/packs/entrypoints/decidim_homepage_proposals.js +5 -0
- data/app/packs/entrypoints/decidim_homepage_proposals_admin.js +1 -0
- data/app/packs/images/decidim/homepage_proposals/icon.svg +1 -0
- data/app/packs/images/decidim/homepage_proposals/slider_proposal_image.jpeg +0 -0
- data/app/packs/src/decidim/homepage_proposals/admin.js +16 -0
- data/app/packs/src/decidim/homepage_proposals/glideBuilder.js +49 -0
- data/app/packs/src/decidim/homepage_proposals/glidejs/Manager.js +130 -0
- data/app/packs/src/decidim/homepage_proposals/glidejs/glideItems/GlideItem.js +17 -0
- data/app/packs/src/decidim/homepage_proposals/glidejs/glideItems/NotFound.js +23 -0
- data/app/packs/src/decidim/homepage_proposals/glidejs/glideItems/Proposal.js +51 -0
- data/app/packs/src/decidim/homepage_proposals/main.js +19 -0
- data/app/packs/stylesheets/decidim/homepage_proposals/homepage_proposals.scss +45 -0
- data/app/views/decidim/shared/homepage_proposals/_filters.erb +47 -0
- data/app/views/decidim/shared/homepage_proposals/_filters_small_view.erb +18 -0
- data/config/assets.rb +10 -0
- data/config/i18n-tasks.yml +10 -0
- data/config/locales/en.yml +33 -0
- data/config/routes.rb +5 -0
- data/lib/decidim/homepage_proposals/admin.rb +10 -0
- data/lib/decidim/homepage_proposals/admin_engine.rb +24 -0
- data/lib/decidim/homepage_proposals/engine.rb +41 -0
- data/lib/decidim/homepage_proposals/test/factories.rb +17 -0
- data/lib/decidim/homepage_proposals/version.rb +13 -0
- data/lib/decidim/homepage_proposals.rb +12 -0
- data/lib/tasks/homepage_proposals.rake +25 -0
- data/lib/tasks/homepage_proposals_webpacker_tasks.rake +14 -0
- 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
|
data/app/cells/decidim/homepage_proposals/content_blocks/proposals_slider_settings_form/show.erb
ADDED
@@ -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" %>
|
data/app/cells/decidim/homepage_proposals/content_blocks/proposals_slider_settings_form_cell.rb
ADDED
@@ -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 @@
|
|
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
|
+
}
|