diff --git a/app/assets/images/column-left.svg b/app/assets/images/column-left.svg new file mode 100644 index 000000000..c7c215df6 --- /dev/null +++ b/app/assets/images/column-left.svg @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/assets/images/column-right.svg b/app/assets/images/column-right.svg new file mode 100644 index 000000000..eb6cbfcff --- /dev/null +++ b/app/assets/images/column-right.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/assets/stylesheets/icons.css b/app/assets/stylesheets/icons.css index 5b3409c76..e1c4df1d7 100644 --- a/app/assets/stylesheets/icons.css +++ b/app/assets/stylesheets/icons.css @@ -44,6 +44,8 @@ .icon--close-circle { --svg: url("close-circle.svg "); } .icon--collection { --svg: url("collection.svg "); } .icon--collection-add { --svg: url("collection-add.svg "); } + .icon--column-left { --svg: url("column-left.svg "); } + .icon--column-right { --svg: url("column-right.svg "); } .icon--comment { --svg: url("comment.svg "); } .icon--copy-paste { --svg: url("copy-paste.svg "); } .icon--crown { --svg: url("crown.svg "); } diff --git a/app/controllers/columns/left_positions_controller.rb b/app/controllers/columns/left_positions_controller.rb new file mode 100644 index 000000000..7161c093f --- /dev/null +++ b/app/controllers/columns/left_positions_controller.rb @@ -0,0 +1,8 @@ +class Columns::LeftPositionsController < ApplicationController + include ColumnScoped + + def create + @left_column = @column.left_column + @column.move_left + end +end diff --git a/app/controllers/columns/right_positions_controller.rb b/app/controllers/columns/right_positions_controller.rb new file mode 100644 index 000000000..d43beb662 --- /dev/null +++ b/app/controllers/columns/right_positions_controller.rb @@ -0,0 +1,8 @@ +class Columns::RightPositionsController < ApplicationController + include ColumnScoped + + def create + @right_column = @column.right_column + @column.move_right + end +end diff --git a/app/controllers/concerns/column_scoped.rb b/app/controllers/concerns/column_scoped.rb new file mode 100644 index 000000000..ea002b95a --- /dev/null +++ b/app/controllers/concerns/column_scoped.rb @@ -0,0 +1,12 @@ +module ColumnScoped + extend ActiveSupport::Concern + + included do + before_action :set_column + end + + private + def set_column + @column = Column.find(params[:column_id]) + end +end diff --git a/app/javascript/controllers/collapsible_columns_controller.js b/app/javascript/controllers/collapsible_columns_controller.js index 01289baf0..0a87e47d5 100644 --- a/app/javascript/controllers/collapsible_columns_controller.js +++ b/app/javascript/controllers/collapsible_columns_controller.js @@ -1,5 +1,5 @@ import { Controller } from "@hotwired/stimulus" -import { nextFrame } from "helpers/timing_helpers"; +import { nextFrame, debounce } from "helpers/timing_helpers"; export default class extends Controller { static classes = [ "collapsed", "noTransitions" ] @@ -8,12 +8,12 @@ export default class extends Controller { collection: String } - async connect() { - this.#disableTransitions() - this.#restoreColumns() + initialize() { + this.restoreState = debounce(this.restoreState.bind(this), 10) + } - await nextFrame() - this.#enableTransitions() + async connect() { + await this.#restoreColumnsDisablingTransitions() } toggle({ target }) { @@ -27,6 +27,19 @@ export default class extends Controller { } } + async restoreState(event) { + await nextFrame() + await this.#restoreColumnsDisablingTransitions() + } + + async #restoreColumnsDisablingTransitions() { + this.#disableTransitions() + this.#restoreColumns() + + await nextFrame() + this.#enableTransitions() + } + #disableTransitions() { this.element.classList.add(this.noTransitionsClass) } @@ -79,13 +92,17 @@ export default class extends Controller { #restoreColumns() { this.columnTargets.forEach(column => { - const key = this.#localStorageKeyFor(column) - if (localStorage.getItem(key)) { - this.#expand(column) - } + this.#restoreColumn(column) }) } + #restoreColumn(column) { + const key = this.#localStorageKeyFor(column) + if (localStorage.getItem(key)) { + this.#expand(column) + } + } + #localStorageKeyFor(column) { return `expand-${this.collectionValue}-${column.getAttribute("id")}` } diff --git a/app/models/column.rb b/app/models/column.rb index 27bfa8a06..7585c39be 100644 --- a/app/models/column.rb +++ b/app/models/column.rb @@ -1,4 +1,6 @@ class Column < ApplicationRecord + include Positioned + belongs_to :collection, touch: true has_many :cards, dependent: :nullify diff --git a/app/models/column/positioned.rb b/app/models/column/positioned.rb new file mode 100644 index 000000000..333882e36 --- /dev/null +++ b/app/models/column/positioned.rb @@ -0,0 +1,49 @@ +module Column::Positioned + extend ActiveSupport::Concern + + included do + scope :sorted, -> { order(position: :asc) } + + before_create :set_position + end + + def move_left + swap_position_with left_column + end + + def move_right + swap_position_with right_column + end + + def left_column + collection.columns.where("position < ?", position).sorted.last + end + + def right_column + collection.columns.where("position > ?", position).sorted.first + end + + def leftmost? + left_column.nil? + end + + def rightmost? + right_column.nil? + end + + private + def set_position + max_position = collection.columns.maximum(:position) || 0 + self.position = max_position + 1 + end + + def swap_position_with(other_column) + return if other_column.nil? + + transaction do + old_position = self.position + self.update_column(:position, other_column.position) + other_column.update_column(:position, old_position) + end + end +end diff --git a/app/views/collections/show/_column.html.erb b/app/views/collections/show/_column.html.erb index ed667047d..3a6084b0c 100644 --- a/app/views/collections/show/_column.html.erb +++ b/app/views/collections/show/_column.html.erb @@ -4,7 +4,7 @@ data-drag-and-strum-target="container" data-collapsible-columns-target="column" data-controller="clicker" - data-action="turbo:before-morph-attribute->collapsible-columns#preventToggle" + data-action="turbo:before-morph-attribute->collapsible-columns#preventToggle turbo:before-stream-render@document->collapsible-columns#restoreState" data-drag-and-drop-url="<%= columns_card_drops_column_path("__id__", column_id: column.id) %>" > <%= render "collections/show/expander", title: column.name, count: column.cards.active.count, column_id: dom_id(column) do %> diff --git a/app/views/collections/show/_columns.html.erb b/app/views/collections/show/_columns.html.erb index 585093dc6..8c12b164a 100644 --- a/app/views/collections/show/_columns.html.erb +++ b/app/views/collections/show/_columns.html.erb @@ -16,7 +16,7 @@ <%= render "collections/show/stream", collection: collection, page: page %>
- <%= render partial: "collections/show/column", collection: collection.columns, cached: true %> + <%= render partial: "collections/show/column", collection: collection.columns.sorted, cached: true %> <%= render "collections/show/closed", collection: collection %> <%= render "collections/show/menu/columns", collection: collection %> diff --git a/app/views/collections/show/menu/_column.html.erb b/app/views/collections/show/menu/_column.html.erb index 6870e34e7..25c6085a4 100644 --- a/app/views/collections/show/menu/_column.html.erb +++ b/app/views/collections/show/menu/_column.html.erb @@ -11,6 +11,27 @@ Edit column + + + + +