diff --git a/app/assets/stylesheets/filters.css b/app/assets/stylesheets/filters.css index 47d3edb13..df9ea05cd 100644 --- a/app/assets/stylesheets/filters.css +++ b/app/assets/stylesheets/filters.css @@ -8,6 +8,16 @@ } } +.filter--active { + li { + display: none; + + &.selected { + display: flex; + } + } +} + .filter__button { --hover-size: 0; diff --git a/app/controllers/buckets/accesses_controller.rb b/app/controllers/buckets/accesses_controller.rb deleted file mode 100644 index 9421e5ba4..000000000 --- a/app/controllers/buckets/accesses_controller.rb +++ /dev/null @@ -1,17 +0,0 @@ -class Buckets::AccessesController < ApplicationController - include BucketScoped - - def edit - @users = @bucket.users - end - - def update - @bucket.update_access [ Current.user, *find_users ] - redirect_to edit_bucket_access_url(@bucket) - end - - private - def find_users - Current.account.users.active.where(id: params[:user_ids]) - end -end diff --git a/app/controllers/buckets_controller.rb b/app/controllers/buckets_controller.rb index bc6ffe8c7..275496794 100644 --- a/app/controllers/buckets_controller.rb +++ b/app/controllers/buckets_controller.rb @@ -15,10 +15,16 @@ class BucketsController < ApplicationController end def edit + selected_user_ids = @bucket.users.pluck :id + @selected_users, @unselected_users = User.active.alphabetically.partition { |user| selected_user_ids.include? user.id } end def update - @bucket.update!(bucket_params) + @bucket.transaction do + @bucket.update! bucket_params + @bucket.accesses.revise granted: grantees, revoked: revokees + end + redirect_to bucket_bubbles_url(@bucket) end @@ -29,10 +35,22 @@ class BucketsController < ApplicationController private def set_bucket - @bucket = Current.user.buckets.find(params[:id]) + @bucket = Current.user.buckets.find params[:id] end def bucket_params params.require(:bucket).permit(:name) end + + def grantees + Current.account.users.active.where id: grantee_ids + end + + def revokees + @bucket.users.where.not id: grantee_ids + end + + def grantee_ids + params.fetch :user_ids, [] + end end diff --git a/app/helpers/translations_helper.rb b/app/helpers/translations_helper.rb index 5898b3a88..094145099 100644 --- a/app/helpers/translations_helper.rb +++ b/app/helpers/translations_helper.rb @@ -2,7 +2,8 @@ module TranslationsHelper TRANSLATIONS = { user_name: { "🇺🇸": "Enter your name", "🇪🇸": "Introduce tu nombre", "🇫🇷": "Entrez votre nom", "🇮🇳": "अपना नाम दर्ज करें", "🇩🇪": "Geben Sie Ihren Namen ein", "🇧🇷": "Insira seu nome" }, email_address: { "🇺🇸": "Enter your email address", "🇪🇸": "Introduce tu correo electrónico", "🇫🇷": "Entrez votre adresse courriel", "🇮🇳": "अपना ईमेल पता दर्ज करें", "🇩🇪": "Geben Sie Ihre E-Mail-Adresse ein", "🇧🇷": "Insira seu endereço de email" }, - password: { "🇺🇸": "Enter your password", "🇪🇸": "Introduce tu contraseña", "🇫🇷": "Saisissez votre mot de passe", "🇮🇳": "अपना पासवर्ड दर्ज करें", "🇩🇪": "Geben Sie Ihr Passwort ein", "🇧🇷": "Insira sua senha" } + password: { "🇺🇸": "Enter your password", "🇪🇸": "Introduce tu contraseña", "🇫🇷": "Saisissez votre mot de passe", "🇮🇳": "अपना पासवर्ड दर्ज करें", "🇩🇪": "Geben Sie Ihr Passwort ein", "🇧🇷": "Insira sua senha" }, + bucket_name: { "🇺🇸": "Give it a name", "🇪🇸": "Dale un nombre", "🇫🇷": "Donnez lui un nom", "🇮🇳": "इसे एक नाम दें", "🇩🇪": "Gib ihm einen Namen", "🇧🇷": "Dê-lhe um nome" } } def translations_for(translation_key) diff --git a/app/javascript/controllers/filter_controller.js b/app/javascript/controllers/filter_controller.js new file mode 100644 index 000000000..815b05952 --- /dev/null +++ b/app/javascript/controllers/filter_controller.js @@ -0,0 +1,46 @@ +import { Controller } from "@hotwired/stimulus" +import { debounce } from "helpers/timing_helpers" + +export default class extends Controller { + static targets = [ "list" ] + static classes = [ "active", "selected" ] + + initialize() { + this.filter = debounce(this.filter.bind(this), 300) + } + + connect() { + this.element.focus() + } + + filter(event) { + this.#reset() + + if (event.target.value != "") { + this.#selectMatches(event.target.value) + this.#activate() + } + } + + #reset() { + this.#deactivate() + + this.listTarget.querySelectorAll(`.${this.selectedClass}`).forEach((element) => { + element.classList.remove(this.selectedClass) + }) + } + + #activate() { + this.listTarget.classList.add(this.activeClass) + } + + #deactivate() { + this.listTarget.classList.remove(this.activeClass) + } + + #selectMatches(value) { + this.listTarget.querySelectorAll(`[data-value*=${value.toLowerCase()}]`).forEach((element) => { + element.classList.add(this.selectedClass) + }) + } +} diff --git a/app/models/bucket/accessible.rb b/app/models/bucket/accessible.rb index 4b4e7c031..0f3c0d04a 100644 --- a/app/models/bucket/accessible.rb +++ b/app/models/bucket/accessible.rb @@ -2,22 +2,26 @@ module Bucket::Accessible extend ActiveSupport::Concern included do - has_many :accesses, dependent: :destroy + has_many :accesses, dependent: :delete_all do + def revise(granted: [], revoked: []) + transaction do + grant_to granted + revoke_from revoked + end + end + + private + def grant_to(users) + Access.insert_all Array(users).collect { |user| { bucket_id: proxy_association.owner.id, user_id: user.id } } + end + + def revoke_from(users) + destroy_by user: users + end + end + has_many :users, through: :accesses - after_create -> { grant_access(creator) } - end - - def update_access(users) - transaction do - grant_access(users) - accesses.where.not(user: Array(users)).delete_all - end - end - - def grant_access(users) - Array(users).each do |user| - accesses.create_or_find_by!(user: user) - end + after_create -> { accesses.grant_to(creator) } end end diff --git a/app/models/user.rb b/app/models/user.rb index 5ff9bd7a0..2d1c5ce6f 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -19,6 +19,7 @@ class User < ApplicationRecord normalizes :email_address, with: ->(value) { value.strip.downcase } scope :active, -> { where(active: true) } + scope :alphabetically, -> { order("LOWER(name)") } def initials name.to_s.scan(/\b\p{L}/).join.upcase diff --git a/app/views/bubbles/_filters.html.erb b/app/views/bubbles/_filters.html.erb index 20a2254b5..a98fe7771 100644 --- a/app/views/bubbles/_filters.html.erb +++ b/app/views/bubbles/_filters.html.erb @@ -34,6 +34,11 @@ <% end %> + + <%= link_to edit_bucket_url(bucket), class: "btn btn--plain", style: "font-size: 0.25em;" do %> + <%= image_tag "pencil.svg", aria: { hidden: true }, size: 24 %> + Edit + <% end %> diff --git a/app/views/buckets/_user_toggle.html.erb b/app/views/buckets/_user_toggle.html.erb new file mode 100644 index 000000000..f1e3a2f23 --- /dev/null +++ b/app/views/buckets/_user_toggle.html.erb @@ -0,0 +1,24 @@ +
  • +
    + <%= avatar_tag user, loading: :lazy %> +
    + +
    +
    + <%= user.name %> +
    +
    + + + + <% if user == Current.user %> + <%= hidden_field_tag "user_ids[]", user.id, id: nil %> + <%= image_tag "check.svg", size: 20, class: "colorize--black flex-item-no-shrink", aria: { hidden: "true" } %> + <% else %> + + <% end %> +
  • diff --git a/app/views/buckets/edit.html.erb b/app/views/buckets/edit.html.erb index 5105b5dab..7272b7cc1 100644 --- a/app/views/buckets/edit.html.erb +++ b/app/views/buckets/edit.html.erb @@ -2,7 +2,7 @@ <% content_for :header do %>