Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

backport: Addition of sortable scopes using drag and drop #632

Merged
merged 8 commits into from
Nov 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions app/commands/admin/reorder_scopes.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# frozen_string_literal: true

module Admin
class ReorderScopes < Decidim::Command
def initialize(organization, scope, ids)
@organization = organization
@scope = scope
@ids = ids
end

def call
return broadcast(:invalid) if @ids.blank?

reorder_scopes
broadcast(:ok)
end

def collection
@collection ||= Decidim::Scope.where(id: @ids, organization: @organization)
end

def reorder_scopes
transaction do
set_new_weights
end
end

def set_new_weights
@ids.each do |id|
current_scope = collection.find { |block| block.id == id.to_i }
next if current_scope.blank?

current_scope.update!(weight: @ids.index(id) + 1)
end
end
end
end
25 changes: 25 additions & 0 deletions app/forms/decidim/user_interest_scope_form.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# frozen_string_literal: true

module Decidim
# The form object that handles the data behind updating a user's
# interests in their profile page.
class UserInterestScopeForm < Form
mimic :scope

attribute :name, JsonbAttributes
attribute :checked, Boolean
attribute :children, Array[UserInterestScopeForm]

def map_model(model_hash)
scope = model_hash[:scope]
user = model_hash[:user]

self.id = scope.id
self.name = scope.name
self.checked = user.interested_scopes_ids.include?(scope.id)
self.children = scope.children.sort_by(&:weight).map do |children_scope|
UserInterestScopeForm.from_model(scope: children_scope, user: user)
end
end
end
end
23 changes: 23 additions & 0 deletions app/forms/decidim/user_interests_form.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# frozen_string_literal: true

module Decidim
# The form object that handles the data behind updating a user's
# interests in their profile page.
class UserInterestsForm < Form
mimic :user

attribute :scopes, Array[UserInterestScopeForm]

def newsletter_notifications_at
return unless newsletter_notifications

Time.current
end

def map_model(user)
self.scopes = user.organization.scopes.top_level.sort_by(&:weight).map do |scope|
UserInterestScopeForm.from_model(scope: scope, user: user)
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# frozen_string_literal: true

module Decidim
module SimpleProposal
module ScopesHelperOverride
extend ActiveSupport::Concern

included do
def scopes_picker_field(form, name, root: false, options: { checkboxes_on_top: true, sort_by_weight: true })
options.merge!(selected: selected_scope(form)) if selected_scope(form)
form.select(name, simple_scope_options(root: root, options: options), include_blank: t("decidim.scopes.prompt"))
end

private

def selected_scope(form)
form.try(:scope_id) ||
form.try(:settings).try(:scope_id) ||
form.try(:object).try(:scope_id) ||
form.try(:object).try(:decidim_scope_id)
end

def simple_scope_options(root: false, options: {})
scopes_array = []
roots = root ? root.children : ancestors

roots.sort_by { |ancestor| ancestor.weight || 0 }.each do |ancestor|
children_after_parent(ancestor, scopes_array, "")
end

selected = options.has_key?(:selected) ? options[:selected] : params.dig(:filter, :decidim_scope_id)
options_for_select(scopes_array, selected)
end

def ancestors
@ancestors ||= current_organization.scopes.where(parent_id: nil)
end

def children_after_parent(ancestor, array, prefix)
array << ["#{prefix} #{translated_attribute(ancestor.name)}", ancestor.id, ancestor.weight]
ancestor.children.sort_by { |child| child.weight || 0 }.each do |child|
children_after_parent(child, array, "#{prefix}-")
end
end
end
end
end
end
2 changes: 2 additions & 0 deletions app/packs/entrypoints/application.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,5 @@
// Activate Active Storage
// import * as ActiveStorage from "@rails/activestorage"
// ActiveStorage.start()

import "src/decidim/admin/reorder_scopes";
1 change: 1 addition & 0 deletions app/packs/entrypoints/decidim_custom_scopes.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@import "stylesheets/decidim/scopes/scopes-custom.scss";
19 changes: 19 additions & 0 deletions app/packs/src/decidim/admin/reorder_scopes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
$(document).ready(() => {
let activeBlocks = Array.prototype.slice.call(document.querySelectorAll(".js-list-scopes li"));
const defaultOrder = activeBlocks.map(block => block.dataset.scopeId);

document.addEventListener("dragend", () => {
activeBlocks = Array.prototype.slice.call(document.querySelectorAll(".js-list-scopes li"));
let activeBlocksManifestName = activeBlocks.map(block => block.dataset.scopeId);
let sortUrl = document.querySelector(".js-list-scopes").dataset.sortUrl;

if (JSON.stringify(activeBlocksManifestName) === JSON.stringify(defaultOrder)) { return; }

$.ajax({
method: "PUT",
url: sortUrl,
contentType: "application/json",
data: JSON.stringify({ manifests: activeBlocksManifestName })
});
})
});
18 changes: 18 additions & 0 deletions app/packs/stylesheets/decidim/scopes/_scopes-custom.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
.draggable-list .draggable-content {
cursor: move;
justify-content: space-between;
align-items: center;
font-weight: 600;
border: none !important;
background-color: transparent !important;
padding: 0.5rem 1rem;
}

.custom-text {
color: black;
}

.custom-list {
border: 1px solid lightgray !important;
margin: 0.4rem
}
65 changes: 65 additions & 0 deletions app/views/decidim/admin/scopes/index.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<% add_decidim_page_title(t("decidim.admin.titles.scopes")) %>
<div class="grid-container full">
<div class="grid-x grid-margin-x card-grid">
<div class="cell">
<div class="card">
<div class="card-divider">
<h2 class="card-title">
<% if parent_scope %>
<%= scope_breadcrumbs(parent_scope).join(" - ").html_safe %> <%= link_to t("actions.add", scope: "decidim.admin"), new_scope_scope_path(parent_scope), class: "button tiny button--title" if allowed_to? :create, :scope %><%= link_to t("actions.edit", scope: "decidim.admin"), edit_scope_path(parent_scope), class: "button tiny button--title" if allowed_to? :edit, :scope, scope: parent_scope %>
<% else %>
<%= t "decidim.admin.titles.scopes" %> <%= link_to t("actions.add", scope: "decidim.admin"), new_scope_path, class: "button tiny button--title" if allowed_to? :create, :scope %>
<% end %>
</h2>
</div>
<div class="card-section">
<% if @scopes.any? %>
<div class="table-scroll">
<table class="table-list">
<thead>
<tr>
<th><%= t("models.scope.fields.name", scope: "decidim.admin") %></th>
<th><%= t("models.scope.fields.scope_type", scope: "decidim.admin") %></th>
<th></th>
</tr>
</thead>
</table>
<table>
<tbody>
<ul class="draggable-list js-connect js-list-scopes" data-sort-url="<%= "/admin/scopes/refresh_scopes" %>">
<% @scopes.each do |scope| %>
<li draggable="true" data-scope-id="<%= scope.id %>" class="custom-list">
<div class="draggable-content">
<div>
<%= icon "move", class: "icon--small", role: "img", "aria-hidden": true %>
<%= link_to translated_attribute(scope.name), scope_scopes_path(scope), class:"custom-text" %>
</div>
<div>
<%= icon_link_to "zoom-in", scope_scopes_path(scope), t("actions.browse", scope: "decidim.admin"), class: "action-icon--browse", method: :get, data: {} %>

<% if allowed_to? :update, :scope, scope: scope %>
<%= icon_link_to "pencil", [:edit, scope], t("actions.edit", scope: "decidim.admin"), class: "action-icon--edit", method: :get, data: {} %>
<% end %>

<% if allowed_to? :destroy, :scope, scope: scope %>
<%= icon_link_to "circle-x", scope, t("actions.destroy", scope: "decidim.admin"), class: "action-icon--remove", method: :delete, data: { confirm: t("actions.confirm_destroy", scope: "decidim.admin") } %>
<% end %>
</div>
</div>
</li>
<% end %>
</ul>
</tbody>
</table>
</div>
<% else %>
<p><%= t("decidim.admin.scopes.no_scopes") %></p>
<% end %>
</div>
</div>
</div>
</div>
</div>

<%= stylesheet_pack_tag "decidim_custom_scopes", media: "all" %>
<%= javascript_pack_tag 'application' %>
3 changes: 3 additions & 0 deletions config/application.rb
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,10 @@ class Application < Rails::Application
config.after_initialize do
require "extends/controllers/decidim/devise/sessions_controller_extends"
require "extends/controllers/decidim/editor_images_controller_extends"
require "extends/controllers/decidim/admin/scopes_controller_extends"
require "extends/controllers/decidim/scopes_controller_extends"
require "extends/services/decidim/iframe_disabler_extends"
require "extends/helpers/decidim/check_boxes_tree_helper_extends"
require "extends/helpers/decidim/icon_helper_extends"
require "extends/commands/decidim/initiatives/admin/update_initiative_answer_extends"
require "extends/controllers/decidim/initiatives/committee_requests_controller_extends"
Expand Down
19 changes: 19 additions & 0 deletions config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,31 @@ en:
file: importing file
decidim:
admin:
actions:
add: Add
browse: Browse
confirm_destroy: Confirm destroy
destroy: Destroy
edit: Edit
exports:
export_as: "%{name} as %{export_format}"
notice: Your export is currently in progress. You'll receive an email when it's complete.
models:
scope:
fields:
name: Name
scope_type: Scope type
participatory_space_private_users:
create:
error: Error
success: Success
scopes:
no_scopes: No scopes at this level.
update:
error: There was a problem updating this scope.
success: Scope updated successfully
titles:
scopes: Scopes
amendments:
emendation:
announcement:
Expand Down Expand Up @@ -175,6 +193,7 @@ en:
change: Change
choose: Choose
currently_selected: Currently selected
prompt: Select a scope
shared:
login_modal:
close_modal: Close modal
Expand Down
19 changes: 19 additions & 0 deletions config/locales/fr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,33 @@ fr:
file: importer un fichier d'utilisateurs
decidim:
admin:
actions:
add: Ajouter
browse: Naviguer
confirm_destroy: Confirmer la suppression
destroy: Supprimer
edit: Modifier
exports:
export_as: "%{name} au format %{export_format}"
notice: Votre exportation est en cours. Vous recevrez un e-mail quand elle sera terminée.
menu:
admin_accountability: Admin accountability
models:
scope:
fields:
name: Nom
scope_type: Type de secteur
participatory_space_private_users:
create:
error: Erreur
success: Succès
scopes:
no_scopes: Aucun secteur à ce niveau.
update:
error: Il y a eu une erreur lors de la mise à jour du secteur.
success: Secteur mis à jour avec succès.
titles:
scopes: Secteurs
amendments:
emendation:
announcement:
Expand Down Expand Up @@ -177,6 +195,7 @@ fr:
change: Modifier
choose: Sélectionner
currently_selected: Sélectionné
prompt: Sélectionnez un périmètre d'application
shared:
login_modal:
close_modal: Fermer
Expand Down
5 changes: 5 additions & 0 deletions db/migrate/20240412112810_add_weight_to_scopes.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class AddWeightToScopes < ActiveRecord::Migration[6.1]
def change
add_column :decidim_scopes, :weight, :integer, default: 0
end
end
1 change: 1 addition & 0 deletions db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1764,6 +1764,7 @@
t.string "code", null: false
t.integer "part_of", default: [], null: false, array: true
t.jsonb "geojson"
t.integer "weight", default: 0
t.index ["decidim_organization_id", "code"], name: "index_decidim_scopes_on_decidim_organization_id_and_code", unique: true
t.index ["decidim_organization_id"], name: "index_decidim_scopes_on_decidim_organization_id"
t.index ["parent_id"], name: "index_decidim_scopes_on_parent_id"
Expand Down
Loading
Loading