decidim-homepage_proposals 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
}
|