Unnest bubble filters
This commit is contained in:
@@ -1,12 +1,14 @@
|
||||
class BubblesController < ApplicationController
|
||||
include BucketScoped
|
||||
|
||||
skip_before_action :set_bucket, only: :index
|
||||
|
||||
before_action :set_filter, only: :index
|
||||
before_action :set_bubble, only: %i[ show edit update ]
|
||||
before_action :clear_assignees, only: :index
|
||||
before_action :set_view, :set_filter, only: :index
|
||||
|
||||
def index
|
||||
@bubbles = @filter.bubbles
|
||||
@bubbles = @bubbles.mentioning(params[:term]) if params[:term].present?
|
||||
end
|
||||
|
||||
def new
|
||||
@@ -31,6 +33,10 @@ class BubblesController < ApplicationController
|
||||
end
|
||||
|
||||
private
|
||||
def set_filter
|
||||
@filter = Current.user.filters.build params: params.permit(*Filter::KNOWN_PARAMS)
|
||||
end
|
||||
|
||||
def set_bubble
|
||||
@bubble = @bucket.bubbles.find params[:id]
|
||||
end
|
||||
@@ -38,18 +44,4 @@ class BubblesController < ApplicationController
|
||||
def bubble_params
|
||||
params.expect(bubble: [ :title, :color, :due_on, :image, tag_ids: [] ])
|
||||
end
|
||||
|
||||
def clear_assignees
|
||||
params[:assignee_ids] = nil if helpers.unassigned_filter_activated?
|
||||
end
|
||||
|
||||
def set_view
|
||||
@view = @bucket.views.find_by(creator: Current.user, id: params[:view_id]) if params[:view_id]
|
||||
@view ||= @bucket.views.find_by(creator: Current.user, filters: helpers.bubble_filter_params.to_h)
|
||||
params[:view_id] = @view&.id
|
||||
end
|
||||
|
||||
def set_filter
|
||||
@filter = @bucket.bubble_filter_from helpers.view_filter_params
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
class Buckets::ViewsController < ApplicationController
|
||||
include BucketScoped
|
||||
|
||||
before_action :set_view, only: %i[ update destroy ]
|
||||
|
||||
def create
|
||||
@view = @bucket.views.create! filters: filter_params
|
||||
redirect_to bucket_bubbles_path(@bucket, **filter_params.merge(view_id: @view.id)), notice: "✓"
|
||||
end
|
||||
|
||||
def update
|
||||
@view.update! filters: filter_params
|
||||
redirect_to bucket_bubbles_path(@bucket, **filter_params.merge(view_id: @view.id)), notice: "✓"
|
||||
end
|
||||
|
||||
def destroy
|
||||
@view.destroy
|
||||
redirect_to buckets_path
|
||||
end
|
||||
|
||||
private
|
||||
def set_view
|
||||
@view = @bucket.views.find_by creator: Current.user, id: params[:id]
|
||||
end
|
||||
|
||||
def filter_params
|
||||
helpers.bubble_filter_params.to_h
|
||||
end
|
||||
end
|
||||
@@ -2,7 +2,8 @@ class BucketsController < ApplicationController
|
||||
before_action :set_bucket, except: %i[ index new create ]
|
||||
|
||||
def index
|
||||
@buckets = (Current.user.buckets.all + Current.user.bucket_views.all).sort_by(&:updated_at).reverse!
|
||||
@filters = Current.user.filters.all
|
||||
@buckets = Current.user.buckets.all
|
||||
end
|
||||
|
||||
def new
|
||||
@@ -11,7 +12,7 @@ class BucketsController < ApplicationController
|
||||
|
||||
def create
|
||||
@bucket = Current.account.buckets.create! bucket_params
|
||||
redirect_to bucket_bubbles_path(@bucket)
|
||||
redirect_to bubbles_path(bucket_ids: @bucket)
|
||||
end
|
||||
|
||||
def edit
|
||||
@@ -23,7 +24,7 @@ class BucketsController < ApplicationController
|
||||
@bucket.update! bucket_params
|
||||
@bucket.accesses.revise granted: grantees, revoked: revokees
|
||||
|
||||
redirect_to bucket_bubbles_path(@bucket)
|
||||
redirect_to bubbles_path(bucket_ids: @bucket)
|
||||
end
|
||||
|
||||
def destroy
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
class FiltersController < ApplicationController
|
||||
before_action :set_filter, only: :destroy
|
||||
|
||||
def create
|
||||
@filter = Current.user.filters.create_or_find_by!(params: filter_params).tap(&:touch)
|
||||
redirect_to bubbles_path(@filter.to_params)
|
||||
end
|
||||
|
||||
def destroy
|
||||
@filter.destroy!
|
||||
redirect_after_destroy
|
||||
end
|
||||
|
||||
private
|
||||
def set_filter
|
||||
@filter = Current.user.filters.find params[:id]
|
||||
end
|
||||
|
||||
def filter_params
|
||||
params.permit(*Filter::KNOWN_PARAMS).compact_blank
|
||||
end
|
||||
|
||||
def redirect_after_destroy
|
||||
if request.referer == root_url
|
||||
redirect_to root_path
|
||||
else
|
||||
redirect_to bubbles_path(@filter.to_params)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,61 +1,32 @@
|
||||
module FiltersHelper
|
||||
def main_filter_text
|
||||
(Bucket::View::ORDERS[params[:order_by]] || Bucket::View::STATUSES[params[:status]] || Bubble.default_order_by).humanize
|
||||
def bubble_filters_heading(filter, &)
|
||||
tag.h1 class: "txt-large flex align-center gap-half",
|
||||
style: token_list("margin-inline-end: calc(var(--btn-size) / -2);": filter.savable?), &
|
||||
end
|
||||
|
||||
def tag_filter_text(filter)
|
||||
if filter.tags
|
||||
filter.tags.map(&:hashtag).to_choice_sentence
|
||||
def buckets_filter_text(filter)
|
||||
if filter.buckets.any?
|
||||
filter.buckets.pluck(:name).to_choice_sentence
|
||||
else
|
||||
"any tag"
|
||||
"all projects"
|
||||
end
|
||||
end
|
||||
|
||||
def assignee_filter_text(filter)
|
||||
if filter.assignees
|
||||
def assignments_filter_text(filter)
|
||||
if filter.assignees.present?
|
||||
"assigned to #{filter.assignees.pluck(:name).to_choice_sentence}"
|
||||
elsif filter.assignments.unassigned?
|
||||
"assigned to no one"
|
||||
else
|
||||
"assigned to anyone"
|
||||
end
|
||||
end
|
||||
|
||||
# `#bubble_filter_params` is memoized to avoid spam in logs about unpermitted params
|
||||
def bubble_filter_params
|
||||
@bubble_filter_params ||= params.permit :order_by, :status, assignee_ids: [], tag_ids: []
|
||||
end
|
||||
|
||||
# `#view_filter_params` is memoized to avoid spam in logs about unpermitted params
|
||||
def view_filter_params
|
||||
@view_filter_params ||= bubble_filter_params.merge params.permit(:term, :view_id)
|
||||
end
|
||||
|
||||
def unassigned_filter_activated?
|
||||
params[:status] == "unassigned"
|
||||
end
|
||||
|
||||
def default_filters?
|
||||
bubble_filter_params.values.all?(&:blank?) || bubble_filter_params.to_h == Bucket::View.default_filters
|
||||
end
|
||||
|
||||
def bubble_filter_form_tag(path, method:, id: nil)
|
||||
form_tag path, method: method, id: id do
|
||||
yield if block_given?
|
||||
|
||||
if params[:order_by].present?
|
||||
concat hidden_field_tag(:order_by, params[:order_by])
|
||||
end
|
||||
|
||||
if params[:status].present?
|
||||
concat hidden_field_tag(:status, params[:status])
|
||||
end
|
||||
|
||||
Array(params[:assignee_ids]).each do |assignee_id|
|
||||
concat hidden_field_tag("assignee_ids[]", assignee_id, id: nil)
|
||||
end
|
||||
|
||||
Array(params[:tag_ids]).each do |tag_id|
|
||||
concat hidden_field_tag("tag_ids[]", tag_id, id: nil)
|
||||
end
|
||||
def tags_filter_text(filter)
|
||||
if filter.tags.present?
|
||||
filter.tags.map(&:hashtag).to_choice_sentence
|
||||
else
|
||||
"any tag"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import { Controller } from "@hotwired/stimulus"
|
||||
|
||||
export default class extends Controller {
|
||||
static values = { fallbackDestination: String }
|
||||
|
||||
connect() {
|
||||
if (history.state.turbo?.restorationIndex > 0) {
|
||||
this.element.href = "javascript:history.back()"
|
||||
} else {
|
||||
this.element.href = this.fallbackDestinationValue
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { Controller } from "@hotwired/stimulus"
|
||||
|
||||
export default class extends Controller {
|
||||
merge({ params: { key, value } }) {
|
||||
const url = new URL(window.location.href)
|
||||
url.searchParams.set(key, value)
|
||||
Turbo.visit(url)
|
||||
}
|
||||
}
|
||||
+5
-19
@@ -1,7 +1,7 @@
|
||||
class Bubble < ApplicationRecord
|
||||
include Assignable, Boostable, Colored, Eventable, Messages, Poppable, Searchable, Staged, Taggable
|
||||
|
||||
belongs_to :bucket
|
||||
belongs_to :bucket, touch: true
|
||||
belongs_to :creator, class_name: "User", default: -> { Current.user }
|
||||
|
||||
has_one_attached :image, dependent: :purge_later
|
||||
@@ -10,6 +10,7 @@ class Bubble < ApplicationRecord
|
||||
|
||||
scope :reverse_chronologically, -> { order created_at: :desc, id: :desc }
|
||||
scope :chronologically, -> { order created_at: :asc, id: :asc }
|
||||
scope :in_bucket, ->(bucket) { where bucket: bucket }
|
||||
|
||||
# FIXME: Compute activity and comment count at write time so it's easier to query for.
|
||||
scope :left_joins_comments, -> do
|
||||
@@ -22,29 +23,14 @@ class Bubble < ApplicationRecord
|
||||
left_joins_comments.select("bubbles.*, COUNT(comments.id) AS comment_count").group(:id).order("comment_count DESC")
|
||||
end
|
||||
|
||||
# FIXME: `status` implies an enum. Consider a name change.
|
||||
scope :with_status, ->(status) do
|
||||
status = status.presence_in %w[ popped active unassigned ]
|
||||
public_send(status) if status
|
||||
end
|
||||
|
||||
scope :ordered_by, ->(order) do
|
||||
case order
|
||||
scope :indexed_by, ->(index) do
|
||||
case index
|
||||
when "most_active" then ordered_by_activity
|
||||
when "most_discussed" then ordered_by_comments
|
||||
when "most_boosted" then ordered_by_boosts
|
||||
when "newest" then reverse_chronologically
|
||||
when "oldest" then chronologically
|
||||
end
|
||||
end
|
||||
|
||||
class << self
|
||||
def default_order_by
|
||||
"most_active"
|
||||
end
|
||||
|
||||
def default_status
|
||||
"active"
|
||||
when "popped" then popped
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -7,11 +7,9 @@ module Bubble::Searchable
|
||||
searchable_by :title, using: :bubbles_search_index
|
||||
|
||||
scope :mentioning, ->(query) do
|
||||
if query = query.presence
|
||||
bubbles = search(query).select(:id).to_sql
|
||||
comments = Comment.search(query).select(:id).to_sql
|
||||
left_joins(:messages).where("bubbles.id in (#{bubbles}) or messages.messageable_id in (#{comments})").distinct
|
||||
end
|
||||
bubbles = Bubble.search(query).select(:id).to_sql
|
||||
comments = Comment.search(query).select(:id).to_sql
|
||||
left_joins(:messages).where("bubbles.id in (#{bubbles}) or messages.messageable_id in (#{comments})").distinct
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
class Bucket < ApplicationRecord
|
||||
include Accessible, Filterable, Views
|
||||
include Accessible
|
||||
|
||||
belongs_to :account
|
||||
belongs_to :creator, class_name: "User", default: -> { Current.user }
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
class Bucket::BubbleFilter
|
||||
def initialize(bucket, params = {})
|
||||
@bucket = bucket
|
||||
@status = params["status"]
|
||||
@order_by = params["order_by"]
|
||||
@term = params["term"]
|
||||
@tag_ids = params["tag_ids"]
|
||||
@assignee_ids = params["assignee_ids"]
|
||||
end
|
||||
|
||||
def bubbles
|
||||
@bubbles ||= begin
|
||||
result = bucket.bubbles
|
||||
result = result.ordered_by(order_by || Bubble.default_order_by)
|
||||
result = result.with_status(status || Bubble.default_status)
|
||||
result = result.tagged_with(tags) if tags
|
||||
result = result.assigned_to(assignees) if assignees
|
||||
result = result.mentioning(term) if term
|
||||
result
|
||||
end
|
||||
end
|
||||
|
||||
def tags
|
||||
@tags ||= account.tags.where(id: tag_ids) if tag_ids
|
||||
end
|
||||
|
||||
def assignees
|
||||
@assignees ||= account.users.where(id: assignee_ids) if assignee_ids
|
||||
end
|
||||
|
||||
private
|
||||
attr_reader :bucket, :status, :order_by, :term, :tag_ids, :assignee_ids
|
||||
delegate :account, to: :bucket, private: true
|
||||
end
|
||||
@@ -1,5 +0,0 @@
|
||||
module Bucket::Filterable
|
||||
def bubble_filter_from(params = {})
|
||||
Bucket::BubbleFilter.new self, params
|
||||
end
|
||||
end
|
||||
@@ -1,27 +0,0 @@
|
||||
class Bucket::View < ApplicationRecord
|
||||
include Assignees, OrderBy, Status, Summarized, Tags
|
||||
|
||||
belongs_to :creator, class_name: "User", default: -> { Current.user }
|
||||
belongs_to :bucket
|
||||
|
||||
has_one :account, through: :creator
|
||||
|
||||
validate :must_not_be_the_default_view
|
||||
|
||||
class << self
|
||||
def default_filters
|
||||
{ "order_by" => "most_active" }
|
||||
end
|
||||
end
|
||||
|
||||
def bubbles
|
||||
@bubbles ||= bucket.bubble_filter_from(filters).bubbles
|
||||
end
|
||||
|
||||
private
|
||||
def must_not_be_the_default_view
|
||||
if filters.values.all?(&:blank?) || filters == self.class.default_filters
|
||||
errors.add :base, "must be different than the default view"
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,12 +0,0 @@
|
||||
module Bucket::View::Assignees
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
store_accessor :filters, :assignee_ids
|
||||
end
|
||||
|
||||
private
|
||||
def assignees
|
||||
@assignees ||= account.users.where id: assignee_ids
|
||||
end
|
||||
end
|
||||
@@ -1,15 +0,0 @@
|
||||
module Bucket::View::OrderBy
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
store_accessor :filters, :order_by
|
||||
end
|
||||
|
||||
private
|
||||
ORDERS = {
|
||||
"most_active" => "most active",
|
||||
"most_discussed" => "most discussed",
|
||||
"most_boosted" => "most boosted",
|
||||
"newest" => "newest",
|
||||
"oldest" => "oldest" }
|
||||
end
|
||||
@@ -1,10 +0,0 @@
|
||||
module Bucket::View::Status
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
store_accessor :filters, :status
|
||||
end
|
||||
|
||||
private
|
||||
STATUSES = %w[ unassigned popped ].index_by(&:itself)
|
||||
end
|
||||
@@ -1,24 +0,0 @@
|
||||
module Bucket::View::Summarized
|
||||
def summary
|
||||
[ order_by_summary, status_summary, tag_summary, assignee_summary ].compact.to_sentence.upcase_first
|
||||
end
|
||||
|
||||
private
|
||||
delegate :to_sentence, to: "ApplicationController.helpers", private: true
|
||||
|
||||
def order_by_summary
|
||||
Bucket::View::ORDERS.fetch order_by, nil
|
||||
end
|
||||
|
||||
def status_summary
|
||||
Bucket::View::STATUSES.fetch status, nil
|
||||
end
|
||||
|
||||
def assignee_summary
|
||||
"assigned to <mark>#{assignees.pluck(:name).to_choice_sentence}</mark>" if assignees.any?
|
||||
end
|
||||
|
||||
def tag_summary
|
||||
"tagged <mark>#{tags.map(&:hashtag).to_choice_sentence}</mark>" if tags.any?
|
||||
end
|
||||
end
|
||||
@@ -1,12 +0,0 @@
|
||||
module Bucket::View::Tags
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
store_accessor :filters, :tag_ids
|
||||
end
|
||||
|
||||
private
|
||||
def tags
|
||||
@tags ||= account.tags.where id: tag_ids
|
||||
end
|
||||
end
|
||||
@@ -1,7 +0,0 @@
|
||||
module Bucket::Views
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
has_many :views, dependent: :delete_all
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,53 @@
|
||||
class Filter < ApplicationRecord
|
||||
include Assignments, Buckets, Indexes, Summarized, Tags
|
||||
|
||||
KNOWN_PARAMS = %i[ indexed_by bucket_ids assignments tag_ids ]
|
||||
|
||||
belongs_to :creator, class_name: "User", default: -> { Current.user }
|
||||
has_one :account, through: :creator
|
||||
|
||||
class << self
|
||||
def default_params
|
||||
{ "indexed_by" => "most_active" }
|
||||
end
|
||||
end
|
||||
|
||||
def bubbles
|
||||
@bubbles ||= begin
|
||||
result = creator.accessible_bubbles.indexed_by(indexed_by)
|
||||
result = result.active unless indexed_by.popped?
|
||||
result = result.in_bucket(buckets) if buckets.present?
|
||||
result = result.tagged_with(tags) if tags.present?
|
||||
result = result.unassigned if assignments.unassigned?
|
||||
result = result.assigned_to(assignees) if assignees.present?
|
||||
result
|
||||
end
|
||||
end
|
||||
|
||||
def to_params
|
||||
ActionController::Parameters.new(params).permit(*KNOWN_PARAMS).tap do |params|
|
||||
params[:filter_id] = id if persisted?
|
||||
end
|
||||
end
|
||||
|
||||
def savable?
|
||||
!bucket_default?
|
||||
end
|
||||
|
||||
def cacheable?
|
||||
buckets.exists?
|
||||
end
|
||||
|
||||
def cache_key
|
||||
ActiveSupport::Cache.expand_cache_key buckets.cache_key_with_version, super
|
||||
end
|
||||
|
||||
private
|
||||
def bucket_default?
|
||||
non_default_params.keys == %w[ bucket_ids ] && buckets.one?
|
||||
end
|
||||
|
||||
def non_default_params
|
||||
params.reject { |k, v| self.class.default_params[k] == v }
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,9 @@
|
||||
module Filter::Assignments
|
||||
def assignments
|
||||
params["assignments"].to_s.inquiry
|
||||
end
|
||||
|
||||
def assignees
|
||||
@assignees ||= account.users.where id: assignments.split(",")
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,11 @@
|
||||
module Filter::Buckets
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
store_accessor :params, :bucket_ids
|
||||
end
|
||||
|
||||
def buckets
|
||||
@buckets ||= account.buckets.where id: bucket_ids.to_s.split(",")
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,7 @@
|
||||
module Filter::Indexes
|
||||
INDEXES = %w[ most_active most_discussed most_boosted newest oldest popped ]
|
||||
|
||||
def indexed_by
|
||||
(params["indexed_by"] || self.class.default_params["indexed_by"]).inquiry
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,36 @@
|
||||
module Filter::Summarized
|
||||
def summary
|
||||
[ index_summary, tag_summary, assignee_summary ].compact.to_sentence + " #{bucket_summary}"
|
||||
end
|
||||
|
||||
def plain_summary
|
||||
summary.remove(/<\/?mark>/)
|
||||
end
|
||||
|
||||
private
|
||||
def index_summary
|
||||
"<mark>#{indexed_by.humanize}</mark>"
|
||||
end
|
||||
|
||||
def tag_summary
|
||||
if tags.exists?
|
||||
"tagged <mark>#{tags.map(&:hashtag).to_choice_sentence}</mark>"
|
||||
end
|
||||
end
|
||||
|
||||
def assignee_summary
|
||||
if assignees.exists?
|
||||
"assigned to <mark>#{assignees.pluck(:name).to_choice_sentence}</mark>"
|
||||
elsif assignments.unassigned?
|
||||
"assigned to no one"
|
||||
end
|
||||
end
|
||||
|
||||
def bucket_summary
|
||||
if buckets.exists?
|
||||
"in <mark>#{buckets.pluck(:name).to_choice_sentence}</mark>"
|
||||
else
|
||||
"in <mark>all projects</mark>"
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,11 @@
|
||||
module Filter::Tags
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
store_accessor :params, :tag_ids
|
||||
end
|
||||
|
||||
def tags
|
||||
@tags ||= account.tags.where id: tag_ids.to_s.split(",")
|
||||
end
|
||||
end
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
class Pop < ApplicationRecord
|
||||
belongs_to :bubble
|
||||
belongs_to :bubble, touch: true
|
||||
belongs_to :user, optional: true
|
||||
end
|
||||
|
||||
+4
-3
@@ -1,6 +1,4 @@
|
||||
class User < ApplicationRecord
|
||||
include Viewer
|
||||
|
||||
belongs_to :account
|
||||
|
||||
has_many :sessions, dependent: :destroy
|
||||
@@ -8,7 +6,10 @@ class User < ApplicationRecord
|
||||
|
||||
has_many :accesses, dependent: :destroy
|
||||
has_many :buckets, through: :accesses
|
||||
has_many :bubbles, through: :buckets
|
||||
has_many :accessible_bubbles, through: :buckets, source: :bubbles
|
||||
|
||||
has_many :filters, foreign_key: :creator_id, inverse_of: :creator, dependent: :destroy
|
||||
|
||||
has_many :pops, dependent: :nullify
|
||||
|
||||
has_many :assignments, foreign_key: :assignee_id, dependent: :destroy
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
module User::Viewer
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
has_many :bucket_views, class_name: "Bucket::View", foreign_key: :creator_id, dependent: :destroy
|
||||
end
|
||||
end
|
||||
@@ -1,28 +1,16 @@
|
||||
<div class="flex flex-column gap-half align-center">
|
||||
<h1 class="txt-large flex align-center gap-half" style="margin-inline-end: calc(var(--btn-size) / -2);">
|
||||
<%= render "bubbles/filters/main", bucket: bucket %>
|
||||
<%= bubble_filters_heading filter do %>
|
||||
<%= render "bubbles/filters/index", filter: filter %>
|
||||
in
|
||||
<%= render "bubbles/filters/bucket", bucket: bucket %>
|
||||
<%= render "bubbles/filters/buckets", filter: filter %>
|
||||
|
||||
<% if false %>
|
||||
<%= button_tag type: :submit, class: "btn txt-small borderless" do %>
|
||||
<%= image_tag "bookmark.svg", aria: { hidden: true }, size: 24 %>
|
||||
<span class="for-screen-reader">Save this filter</span>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<%= button_tag type: :submit, class: "btn txt-small borderless" do %>
|
||||
<%= image_tag "bookmark-outline.svg", aria: { hidden: true }, size: 24 %>
|
||||
<span class="for-screen-reader">Save this filter</span>
|
||||
<% end %>
|
||||
<%= render "bubbles/filters/bookmark", filter: filter if filter.savable? %>
|
||||
<% end %>
|
||||
</h1>
|
||||
|
||||
<h2 class="txt-medium flex align-center justify-center gap-half">
|
||||
<%= filter.tags ? "Tagged" : "with" %>
|
||||
<%= render "bubbles/filters/tags", bucket: bucket, filter: filter %>
|
||||
<%= filter.tags.present? ? "Tagged" : "with" %>
|
||||
<%= render "bubbles/filters/tags", filter: filter %>
|
||||
|
||||
<% unless unassigned_filter_activated? %>
|
||||
and <%= render "bubbles/filters/assignees", bucket: bucket, filter: filter %>
|
||||
<% end %>
|
||||
and <%= render "bubbles/filters/assignments", filter: filter %>
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<% bubble.tags.each do |tag| %>
|
||||
<%= link_to "##{tag.title}", bucket_bubbles_path(bubble.bucket, tag_id: tag), class: "bubble__bubble bubble__meta bubble__tag" %>
|
||||
<%= link_to tag.hashtag, bubbles_path(bucket_id: bubble.bucket, tag_ids: tag), class: "bubble__bubble bubble__meta bubble__tag" %>
|
||||
<% end %>
|
||||
|
||||
<% if bubble.tags.size < 3 %>
|
||||
|
||||
+8
-6
@@ -1,18 +1,20 @@
|
||||
<div class="filter align-center gap-half" data-controller="dialog" data-action="keydown.esc->dialog#close click@document->dialog#closeOnClickOutside">
|
||||
<button class="btn btn--plain filter__button" data-action="click->dialog#toggle" data-dialog-modal-value="true">
|
||||
<%= assignee_filter_text filter %>
|
||||
<%= assignments_filter_text filter %>
|
||||
</button>
|
||||
|
||||
<dialog class="filter__popup panel fill-white shadow" data-dialog-target="dialog">
|
||||
<dialog class="filter__popup panel fill-white shadow" data-dialog-target="dialog" data-turbo-temporary>
|
||||
<menu class="filter__menu unpad margin-none">
|
||||
<% bucket.users.active.order(:name).each do |user| %>
|
||||
<li><%= link_to user.name, bucket_bubbles_path(bucket, view_filter_params.merge(assignee_ids: [ user.id ])) %></li>
|
||||
<li><%= link_to "No one", bubbles_path(filter.to_params.merge(assignments: :unassigned)), class: "filter__button" %></li>
|
||||
|
||||
<% Current.account.users.active.order(:name).each do |user| %>
|
||||
<li><%= link_to user.name, bubbles_path(filter.to_params.merge(assignments: user.id)) %></li>
|
||||
<% end %>
|
||||
</menu>
|
||||
</dialog>
|
||||
|
||||
<% if filter.assignees %>
|
||||
<%= link_to bucket_bubbles_path(bucket, view_filter_params.without(:assignee_ids)), class: "btn", style: "font-size: 0.4em;" do %>
|
||||
<% if filter.assignments.present? %>
|
||||
<%= link_to bubbles_path(filter.to_params.without(:assignments)), class: "btn", style: "font-size: 0.4em;" do %>
|
||||
<%= image_tag "remove.svg", aria: { hidden: true }, size: 24 %>
|
||||
<span class="for-screen-reader">Clear</span>
|
||||
<% end %>
|
||||
@@ -0,0 +1,16 @@
|
||||
<% if params[:filter_id] %>
|
||||
<%= button_to filter_path(params[:filter_id]), method: :delete, class: "btn txt-small borderless" do %>
|
||||
<%= image_tag "bookmark.svg", aria: { hidden: true }, size: 24 %>
|
||||
<span class="for-screen-reader">Delete this filter</span>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<%= form_with url: filters_path, method: :post do %>
|
||||
<% Filter::KNOWN_PARAMS.each do |param| %>
|
||||
<%= hidden_field_tag param, params[param] if params[param].present? %>
|
||||
<% end %>
|
||||
<%= button_tag type: :submit, class: "btn txt-small borderless" do %>
|
||||
<%= image_tag "bookmark-outline.svg", aria: { hidden: true }, size: 24 %>
|
||||
<span class="for-screen-reader">Save this filter</span>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
@@ -1,19 +0,0 @@
|
||||
<% view ||= Bucket::View.new %>
|
||||
|
||||
<%= bubble_filter_form_tag bucket_views_path(bucket), method: :post, id: dom_id(view, :new_form) %>
|
||||
|
||||
<% if view.persisted? %>
|
||||
<%= bubble_filter_form_tag bucket_view_path(bucket, view), method: :put, id: dom_id(view, :edit_form) %>
|
||||
<% end %>
|
||||
|
||||
<%= button_tag type: :submit, form: dom_id(view, :new_form), class: "btn", style: "font-size: 0.4em;" do %>
|
||||
<%= image_tag "add.svg", aria: { hidden: true }, size: 24 %>
|
||||
<span class="txt-small">New</span>
|
||||
<% end %>
|
||||
|
||||
<% if view.persisted? %>
|
||||
<%= button_tag type: :submit, form: dom_id(view, :edit_form), class: "btn", style: "font-size: 0.4em;" do %>
|
||||
<%= image_tag "pencil.svg", aria: { hidden: true }, size: 24 %>
|
||||
<span class="txt-small">Update</span>
|
||||
<% end %>
|
||||
<% end %>
|
||||
@@ -0,0 +1,15 @@
|
||||
<div class="filter align-center gap-half" data-controller="dialog" data-action="keydown.esc->dialog#close click@document->dialog#closeOnClickOutside">
|
||||
<button class="btn btn--plain filter__button" data-action="click->dialog#toggle" data-dialog-modal-value="true">
|
||||
<%= buckets_filter_text filter %>
|
||||
</button>
|
||||
|
||||
<dialog class="filter__popup panel fill-white shadow" data-dialog-target="dialog" data-turbo-temporary>
|
||||
<menu class="filter__menu unpad margin-none">
|
||||
<li><%= link_to "all projects", bubbles_path(filter.to_params.merge(bucket_ids: nil)), class: "filter__button" %></li>
|
||||
|
||||
<% Current.user.buckets.each do |bucket| %>
|
||||
<li><%= link_to bucket.name, bubbles_path(filter.to_params.merge(bucket_ids: bucket.id)), class: "filter__button" %></li>
|
||||
<% end %>
|
||||
</menu>
|
||||
</dialog>
|
||||
</div>
|
||||
+4
-4
@@ -1,12 +1,12 @@
|
||||
<div class="filter align-center gap-half" data-controller="dialog" data-action="keydown.esc->dialog#close click@document->dialog#closeOnClickOutside">
|
||||
<button class="btn btn--plain filter__button" data-action="click->dialog#toggle" data-dialog-modal-value="true">
|
||||
<%= bucket.name %>
|
||||
<%= filter.indexed_by.humanize %>
|
||||
</button>
|
||||
|
||||
<dialog class="filter__popup panel fill-white shadow" data-dialog-target="dialog">
|
||||
<dialog class="filter__popup panel fill-white shadow" data-dialog-target="dialog" data-turbo-temporary>
|
||||
<menu class="filter__menu unpad margin-none">
|
||||
<% Current.user.buckets.order(:name).each do |bucket| %>
|
||||
<li><%= link_to bucket.name, bucket_bubbles_path(bucket, view_filter_params), class: "filter__button" %></li>
|
||||
<% Filter::INDEXES.each do |index| %>
|
||||
<li><%= link_to index.humanize, bubbles_path(filter.to_params.merge(indexed_by: index)), class: "filter__button" %></li>
|
||||
<% end %>
|
||||
</menu>
|
||||
</dialog>
|
||||
@@ -1,17 +0,0 @@
|
||||
<div class="filter align-center gap-half" data-controller="dialog" data-action="keydown.esc->dialog#close click@document->dialog#closeOnClickOutside">
|
||||
<button class="btn btn--plain filter__button" data-action="click->dialog#toggle" data-dialog-modal-value="true">
|
||||
<%= main_filter_text %>
|
||||
</button>
|
||||
|
||||
<dialog class="filter__popup panel fill-white shadow" data-dialog-target="dialog">
|
||||
<menu class="filter__menu unpad margin-none">
|
||||
<% Bucket::View::ORDERS.each do |key, value| %>
|
||||
<li><%= link_to value.humanize, bucket_bubbles_path(bucket, view_filter_params.merge(order_by: key, status: nil)), class: "filter__button" %></li>
|
||||
<% end %>
|
||||
|
||||
<% Bucket::View::STATUSES.each do |key, value| %>
|
||||
<li><%= link_to value.humanize, bucket_bubbles_path(bucket, view_filter_params.merge(order_by: nil, status: key)), class: "filter__button" %></li>
|
||||
<% end %>
|
||||
</menu>
|
||||
</dialog>
|
||||
</div>
|
||||
@@ -1,18 +1,18 @@
|
||||
<div class="filter align-center gap-half" data-controller="dialog" data-action="keydown.esc->dialog#close click@document->dialog#closeOnClickOutside">
|
||||
<button class="btn btn--plain filter__button" data-action="click->dialog#toggle" data-dialog-modal-value="true">
|
||||
<%= tag_filter_text filter %>
|
||||
<%= tags_filter_text filter %>
|
||||
</button>
|
||||
|
||||
<dialog class="filter__popup panel fill-white shadow" data-dialog-target="dialog">
|
||||
<dialog class="filter__popup panel fill-white shadow" data-dialog-target="dialog" data-turbo-temporary>
|
||||
<menu class="filter__menu unpad margin-none">
|
||||
<% bucket.tags.order(:title).each do |tag| %>
|
||||
<li><%= link_to tag.title, bucket_bubbles_path(bucket, view_filter_params.merge(tag_ids: [ tag.id ])) %></li>
|
||||
<% Current.account.tags.order(:title).each do |tag| %>
|
||||
<li><%= link_to tag.title, bubbles_path(filter.to_params.merge(tag_ids: tag.id)) %></li>
|
||||
<% end %>
|
||||
</menu>
|
||||
</dialog>
|
||||
|
||||
<% if filter.tags %>
|
||||
<%= link_to bucket_bubbles_path(bucket, view_filter_params.without(:tag_ids)), class: "btn", style: "font-size: 0.4em;" do %>
|
||||
<% if filter.tags.present? %>
|
||||
<%= link_to bubbles_path(filter.to_params.without(:tag_ids)), class: "btn", style: "font-size: 0.4em;" do %>
|
||||
<%= image_tag "remove.svg", aria: { hidden: true }, size: 24 %>
|
||||
<span class="for-screen-reader">Clear</span>
|
||||
<% end %>
|
||||
|
||||
@@ -1,24 +1,30 @@
|
||||
<% @page_title = @bucket.name %>
|
||||
<% @page_title = @filter.plain_summary %>
|
||||
|
||||
<% content_for :header do %>
|
||||
<nav>
|
||||
<%= link_to buckets_path, class: "btn btn--plain flex-item-justify-start" do %>
|
||||
<%= link_to root_path, class: "btn btn--plain flex-item-justify-start" do %>
|
||||
<%= image_tag "bubbles.svg", aria: { hidden: true }, size: 24 %>
|
||||
<span class="for-screen-reader">All Buckets</span>
|
||||
<span class="for-screen-reader">All Projects</span>
|
||||
<% end %>
|
||||
|
||||
<header class="txt-align-center">
|
||||
<%= render "bubbles/filters", bucket: @bucket, view: @view, filter: @filter %>
|
||||
<%= render "bubbles/filters", filter: @filter %>
|
||||
</header>
|
||||
|
||||
<%= button_to bucket_bubbles_path(@bucket), method: :post, class: "btn btn--plain", form_class: "flex-item-justify-end" do %>
|
||||
<%= image_tag "bubble-add.svg", aria: { hidden: true }, size: 24 %>
|
||||
<span class="for-screen-reader">Create a new bubble</span>
|
||||
<% if @filter.buckets.any? %>
|
||||
<%= button_to bucket_bubbles_path(@filter.buckets.first), method: :post, class: "btn btn--plain", form_class: "flex-item-justify-end" do %>
|
||||
<%= image_tag "bubble-add.svg", aria: { hidden: true }, size: 24 %>
|
||||
<span class="for-screen-reader">Create a new bubble</span>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<span class="flex-item-justify-end" style="height: var(--btn-size); width: var(--btn-size);">
|
||||
<%# FIXME: This is a placeholder to keep the same layout without a "new bubble" button %>
|
||||
</span>
|
||||
<% end %>
|
||||
</nav>
|
||||
<% end %>
|
||||
|
||||
<section class="windshield flex-inline flex-wrap gap justify-center align-end" style="view-transition-name: windshield_<%= @bucket.id %>">
|
||||
<section class="windshield flex-inline flex-wrap gap justify-center align-end" style="view-transition-name: windshield_<%= params[:filter_id] %>">
|
||||
<% if @bubbles.any? %>
|
||||
<%= render partial: "bubbles/bubble", collection: @bubbles.limit(10), cached: true %>
|
||||
<% else %>
|
||||
@@ -28,12 +34,12 @@
|
||||
|
||||
<section class="bubbles-list unpad-inline center margin-block-start">
|
||||
<header class="flex-inline align-center gap txt-small margin-block-end-half center">
|
||||
<%= bubble_filter_form_tag bucket_bubbles_path(@bucket), method: :get do %>
|
||||
<%= form_tag bubbles_path(@filter.to_params), method: :get do %>
|
||||
<%= search_field_tag :term, params[:term], class: "input center flex-inline", placeholder: "Type to filter…" %>
|
||||
<% end %>
|
||||
</header>
|
||||
|
||||
<ul class="unpad margin-none flex flex-column txt-align-start center">
|
||||
<%= render partial: "bubbles/list/bubble", collection: @bubbles %>
|
||||
<%= render partial: "bubbles/list/bubble", collection: @bubbles, cached: true %>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
@@ -20,8 +20,10 @@
|
||||
|
||||
<small class="flex align-center gap flex-item-no-shrink">
|
||||
<% bubble.tags.each do |tag| %>
|
||||
<%# FIXME: This is not going to work, this partial must be cacheable, so it can't mutate a changeable view filter live. Need a JS solution. %>
|
||||
<%= link_to "##{tag.title}", bucket_bubbles_path(bubble.bucket, view_filter_params.merge(tag_ids: tag.id)), style: "color: #{bubble.color}" %>
|
||||
<%= button_tag tag.hashtag, type: :button,
|
||||
class: "btn btn--plain", style: "color: #{bubble.color}", data: {
|
||||
controller: "query-merger", action: "query-merger#merge",
|
||||
query_merger_key_param: "tag_ids", query_merger_value_param: tag.id } %>
|
||||
<% end %>
|
||||
|
||||
<%= tag.time bubble.created_at, class: "txt-nowrap flex-item-justify-end" do %>
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
<% content_for :header do %>
|
||||
<nav>
|
||||
<%= link_to bucket_bubbles_path(@bubble.bucket), class: "btn flex-item-justify-start" do %>
|
||||
<%= link_to "#", class: "btn flex-item-justify-start", data: { controller: "back-navigation", back_navigation_fallback_destination_value: bubbles_path(bucket_id: @bubble.bucket) } do %>
|
||||
<%= image_tag "arrow-left.svg", aria: { hidden: true }, size: 24 %>
|
||||
<span class="for-screen-reader">All Bubbles</span>
|
||||
<span class="for-screen-reader">Back</span>
|
||||
<% end %>
|
||||
|
||||
<section class="flex flex-column align-end gap">
|
||||
|
||||
@@ -1,23 +1,22 @@
|
||||
<li class="bucket flex flex-column txt-align-center max-width position-relative">
|
||||
<%= link_to bucket_bubbles_path(bucket), class: "windshield__container flex justify-center align-center position-relative" do %>
|
||||
<div class="windshield bucket__windshield flex flex-wrap gap justify-center align-end" style="view-transition-name: windshield_<%= bucket.id %>">
|
||||
<% bucket.bubbles.active.ordered_by_activity.limit(10).each do |bubble| %>
|
||||
<div class="bubble" style="--bubble-color: <%= bubble.color %>; <%= bubble_rotation(bubble) %> <%= bubble_size(bubble) %>">
|
||||
<svg class="bubble__svg" style="fill: <%= bubble.color %>; stroke: <%= bubble.color %>;" viewBox="0 0 990 990" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="m0 0h990v990h-990z" fill="none" stroke="none" />
|
||||
<path d="m391.65 879.47c-110.52-15.95-212.21-91.86-255.92-191.23-66.78-143.65-41.62-347.61 48.08-481.17 368.33-516.3 1252.97 520.2 451.03 660.78-44.07 8.84-88.98 13.49-133.01 15.68-36.69 2-73.37 1.91-109.99-4.03z"/>
|
||||
</svg>
|
||||
</div>
|
||||
<% cache bucket do %>
|
||||
<li class="bucket flex flex-column txt-align-center max-width position-relative">
|
||||
<%= link_to bubbles_path(bucket_ids: bucket), class: "windshield__container flex justify-center align-center position-relative" do %>
|
||||
<div class="windshield bucket__windshield flex flex-wrap gap justify-center align-end" style="view-transition-name: windshield_<%= bucket.id %>">
|
||||
<% bucket.bubbles.ordered_by_activity.limit(10).each do |bubble| %>
|
||||
<div class="bubble" style="--bubble-color: <%= bubble.color %>; <%= bubble_rotation(bubble) %> <%= bubble_size(bubble) %>">
|
||||
<svg class="bubble__svg" style="fill: <%= bubble.color %>; stroke: <%= bubble.color %>;" viewBox="0 0 990 990" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="m0 0h990v990h-990z" fill="none" stroke="none" />
|
||||
<path d="m391.65 879.47c-110.52-15.95-212.21-91.86-255.92-191.23-66.78-143.65-41.62-347.61 48.08-481.17 368.33-516.3 1252.97 520.2 451.03 660.78-44.07 8.84-88.98 13.49-133.01 15.68-36.69 2-73.37 1.91-109.99-4.03z"/>
|
||||
</svg>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<div class="flex align-center justify-center flex-column flex-wrap center gap-half">
|
||||
<%= link_to bubbles_path(bucket_ids: bucket), class: "txt-ink flex flex-column" do %>
|
||||
<strong class="txt-x-large">In <%= bucket.name %></strong>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<div class="flex align-center justify-center flex-column gap-half center">
|
||||
<strong><%= link_to bucket.name, bucket_bubbles_path(bucket), class: "txt-x-large txt-ink" %></strong>
|
||||
|
||||
<%= link_to edit_bucket_url(bucket), class: "btn" do %>
|
||||
<%= image_tag "settings.svg", aria: { hidden: true }, size: 24 %>
|
||||
<span class="for-screen-reader">Bucket settings</span>
|
||||
<% end %>
|
||||
</div>
|
||||
</li>
|
||||
</li>
|
||||
<% end %>
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
<% path = bucket_bubbles_path(view.bucket, **view.filters, view_id: view.id) %>
|
||||
|
||||
<li class="bucket flex flex-column txt-align-center max-width position-relative">
|
||||
<%= link_to path, class: "windshield__container flex justify-center align-center position-relative" do %>
|
||||
<div class="windshield bucket__windshield flex flex-wrap gap justify-center align-end" style="view-transition-name: windshield_<%= view.bucket.id %>">
|
||||
<% view.bubbles.ordered_by_activity.limit(10).each do |bubble| %>
|
||||
<div class="bubble" style="--bubble-color: <%= bubble.color %>; <%= bubble_rotation(bubble) %> <%= bubble_size(bubble) %>">
|
||||
<svg class="bubble__svg" style="fill: <%= bubble.color %>; stroke: <%= bubble.color %>;" viewBox="0 0 990 990" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="m0 0h990v990h-990z" fill="none" stroke="none" />
|
||||
<path d="m391.65 879.47c-110.52-15.95-212.21-91.86-255.92-191.23-66.78-143.65-41.62-347.61 48.08-481.17 368.33-516.3 1252.97 520.2 451.03 660.78-44.07 8.84-88.98 13.49-133.01 15.68-36.69 2-73.37 1.91-109.99-4.03z"/>
|
||||
</svg>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<div class="flex align-center justify-center flex-column flex-wrap center gap-half">
|
||||
<%= link_to path, class: "txt-ink flex flex-column" do %>
|
||||
<strong class="txt-x-large"><%= view.summary.html_safe %> in <mark><%= view.bucket.name %></mark></strong>
|
||||
<% end %>
|
||||
|
||||
<%= button_tag type: :submit, form: dom_id(view, :delete_form), class: "btn btn--negative", data: { turbo_confirm: "Are you sure you want to delete this filter?" } do %>
|
||||
<%= image_tag "minus.svg", aria: { hidden: true }, size: 24 %>
|
||||
<span class="for-screen-reader">Delete</span>
|
||||
<% end %>
|
||||
|
||||
<%= bubble_filter_form_tag bucket_view_path(view.bucket, view), method: :delete, id: dom_id(view, :delete_form) %>
|
||||
</div>
|
||||
</li>
|
||||
@@ -37,13 +37,13 @@
|
||||
<% end %>
|
||||
|
||||
<div data-filter-target="list">
|
||||
<%= render partial: "buckets/user_toggle", collection: @selected_users, as: :user, locals: { bucket: @bucket, selected: true } %>
|
||||
<%= render partial: "buckets/user_toggle", collection: @selected_users, as: :user, locals: { selected: true } %>
|
||||
|
||||
<% if @selected_users.any? && @unselected_users.any? %>
|
||||
<hr class="separator full-width" style="--border-style: solid">
|
||||
<% end %>
|
||||
|
||||
<%= render partial: "buckets/user_toggle", collection: @unselected_users, as: :user, locals: { bucket: @bucket, selected: false } %>
|
||||
<%= render partial: "buckets/user_toggle", collection: @unselected_users, as: :user, locals: { selected: false } %>
|
||||
</div>
|
||||
</menu>
|
||||
</section>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<% @page_title = "Buckets" %>
|
||||
<% @page_title = "Projects" %>
|
||||
|
||||
<% content_for :header do %>
|
||||
<nav>
|
||||
@@ -9,17 +9,12 @@
|
||||
|
||||
<%= link_to new_bucket_path, class: "btn btn--plain flex-item-justify-end", style: "view-transition-name: new-bucket" do %>
|
||||
<%= image_tag "bubbles-add.svg", aria: { hidden: true }, size: 24 %>
|
||||
<span class="for-screen-reader">Add a new bucket</span>
|
||||
<span class="for-screen-reader">Add a new project</span>
|
||||
<% end %>
|
||||
</nav>
|
||||
<% end %>
|
||||
|
||||
<menu class="buckets margin-none unpad align-center justify-center flex flex-wrap gap">
|
||||
<% @buckets.each do |bucket| %>
|
||||
<% if bucket.is_a? Bucket %>
|
||||
<%= render partial: "buckets/bucket", locals: { bucket: bucket } %>
|
||||
<% elsif bucket.is_a? Bucket::View %>
|
||||
<%= render partial: "buckets/view", locals: { view: bucket } %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<%= render @filters %>
|
||||
<%= render @buckets, cached: true %>
|
||||
</menu>
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
<% cache_if filter.cacheable?, filter do %>
|
||||
<li class="bucket flex flex-column txt-align-center max-width position-relative">
|
||||
<%= link_to bubbles_path(**filter.to_params), class: "windshield__container flex justify-center align-center position-relative" do %>
|
||||
<div class="windshield bucket__windshield flex flex-wrap gap justify-center align-end" style="view-transition-name: windshield_<%= filter.id %>">
|
||||
<% filter.bubbles.ordered_by_activity.limit(10).each do |bubble| %>
|
||||
<div class="bubble" style="--bubble-color: <%= bubble.color %>; <%= bubble_rotation(bubble) %> <%= bubble_size(bubble) %>">
|
||||
<svg class="bubble__svg" style="fill: <%= bubble.color %>; stroke: <%= bubble.color %>;" viewBox="0 0 990 990" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="m0 0h990v990h-990z" fill="none" stroke="none" />
|
||||
<path d="m391.65 879.47c-110.52-15.95-212.21-91.86-255.92-191.23-66.78-143.65-41.62-347.61 48.08-481.17 368.33-516.3 1252.97 520.2 451.03 660.78-44.07 8.84-88.98 13.49-133.01 15.68-36.69 2-73.37 1.91-109.99-4.03z"/>
|
||||
</svg>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<div class="flex align-center justify-center flex-column flex-wrap center gap-half">
|
||||
<%= link_to bubbles_path(**filter.to_params), class: "txt-ink flex flex-column" do %>
|
||||
<strong class="txt-x-large"><%= filter.summary.html_safe %></strong>
|
||||
<% end %>
|
||||
<%= button_to filter_path(filter), method: :delete, class: "btn btn--negative", data: { turbo_confirm: "Are you sure you want to delete this filter?" } do %>
|
||||
<%= image_tag "minus.svg", aria: { hidden: true }, size: 24 %>
|
||||
<span class="for-screen-reader">Delete</span>
|
||||
<% end %>
|
||||
</div>
|
||||
</li>
|
||||
<% end %>
|
||||
+3
-4
@@ -12,6 +12,8 @@ Rails.application.routes.draw do
|
||||
route_for :bucket_bubble, bubble.bucket, bubble, options
|
||||
end
|
||||
|
||||
resources :bubbles
|
||||
|
||||
resources :buckets do
|
||||
resources :bubbles do
|
||||
resources :assignments
|
||||
@@ -28,12 +30,9 @@ Rails.application.routes.draw do
|
||||
end
|
||||
|
||||
resources :tags, only: :index
|
||||
|
||||
scope module: :buckets do
|
||||
resources :views
|
||||
end
|
||||
end
|
||||
|
||||
resources :filters
|
||||
resource :first_run
|
||||
resource :session
|
||||
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
class RenameBucketViewsToFilters < ActiveRecord::Migration[8.0]
|
||||
def change
|
||||
rename_table :bucket_views, :filters
|
||||
|
||||
remove_index :filters, %i[ bucket_id creator_id filters ], unique: true
|
||||
remove_index :filters, :creator_id
|
||||
|
||||
remove_column :filters, :bucket_id
|
||||
|
||||
rename_column :filters, :filters, :params
|
||||
|
||||
add_index :filters, %i[ creator_id params ], unique: true
|
||||
end
|
||||
end
|
||||
Generated
+9
-11
@@ -10,7 +10,7 @@
|
||||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema[8.0].define(version: 2024_10_28_183212) do
|
||||
ActiveRecord::Schema[8.0].define(version: 2024_11_05_181312) do
|
||||
create_table "accesses", force: :cascade do |t|
|
||||
t.integer "bucket_id", null: false
|
||||
t.integer "user_id", null: false
|
||||
@@ -82,16 +82,6 @@ ActiveRecord::Schema[8.0].define(version: 2024_10_28_183212) do
|
||||
t.index ["stage_id"], name: "index_bubbles_on_stage_id"
|
||||
end
|
||||
|
||||
create_table "bucket_views", force: :cascade do |t|
|
||||
t.integer "creator_id", null: false
|
||||
t.integer "bucket_id", null: false
|
||||
t.json "filters", default: {}, null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["bucket_id", "creator_id", "filters"], name: "index_bucket_views_on_bucket_id_and_creator_id_and_filters", unique: true
|
||||
t.index ["creator_id"], name: "index_bucket_views_on_creator_id"
|
||||
end
|
||||
|
||||
create_table "buckets", force: :cascade do |t|
|
||||
t.integer "account_id", null: false
|
||||
t.integer "creator_id", null: false
|
||||
@@ -125,6 +115,14 @@ ActiveRecord::Schema[8.0].define(version: 2024_10_28_183212) do
|
||||
t.index ["summary_id", "action"], name: "index_events_on_summary_id_and_action"
|
||||
end
|
||||
|
||||
create_table "filters", force: :cascade do |t|
|
||||
t.integer "creator_id", null: false
|
||||
t.json "params", default: {}, null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["creator_id", "params"], name: "index_filters_on_creator_id_and_params", unique: true
|
||||
end
|
||||
|
||||
create_table "messages", force: :cascade do |t|
|
||||
t.integer "bubble_id", null: false
|
||||
t.string "messageable_type", null: false
|
||||
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
jz_assignments:
|
||||
creator: jz
|
||||
params: <%= { indexed_by: :most_discussed, assignments: ActiveRecord::FixtureSet.identify(:jz).to_s, tag_ids: ActiveRecord::FixtureSet.identify(:mobile).to_s }.to_json %>
|
||||
Reference in New Issue
Block a user