Skip to content

Commit

Permalink
Add lane feature (#344)
Browse files Browse the repository at this point in the history
  • Loading branch information
JannikStreek authored Sep 29, 2024
1 parent 353d253 commit b26c7b6
Show file tree
Hide file tree
Showing 43 changed files with 1,557 additions and 543 deletions.
13 changes: 8 additions & 5 deletions assets/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import Sortable from 'sortablejs';
import { setIdeaLabelBackgroundColor } from "./label"

// activate all tooltips:
const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]')
const tooltipList = [...tooltipTriggerList].map(tooltipTriggerEl => new Tooltip(tooltipTriggerEl))
const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]');
[...tooltipTriggerList].map(tooltipTriggerEl => new Tooltip(tooltipTriggerEl));

// webpack automatically bundles all modules in your
// entry points. Those entry points can be configured
Expand All @@ -27,7 +27,7 @@ import { buildQrCodeOptions } from "./qrCodeUtils.js"
let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content")

let Hooks = {}
let sortable;
const sortables = [];

Hooks.CopyBrainstormingLinkButton = {
mounted() {
Expand Down Expand Up @@ -56,23 +56,26 @@ Hooks.NativeSharingButton = {
// see https://github.com/drag-drop-touch-js/dragdroptouch for mobile support
Hooks.Sortable = {
mounted(){
sortable = new Sortable(this.el, {
const sortable = new Sortable(this.el, {
group: { put: true, pull: true },
disabled: this.el.dataset.sortableEnabled !== 'true',
delayOnTouchOnly: true,
delay: 50,
onEnd: (event) => {
this.pushEventTo(this.el, "change_position", {
id: event.item.dataset.id,
brainstorming_id: event.item.dataset.brainstormingId,
lane_id: event.to.dataset.laneId || event.item.dataset.laneId,
// on the server, positions start with 1 not 0
new_position: event.newIndex + 1,
old_position: event.oldIndex + 1
})
}
})
sortables.push(sortable);
},
updated(){
sortable.option("disabled", this.el.dataset.sortableEnabled !== 'true')
sortables.forEach((sortable) => sortable.option("disabled", this.el.dataset.sortableEnabled !== 'true'));
}
}

Expand Down
2 changes: 1 addition & 1 deletion assets/scss/_bootstrap_custom.scss
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,5 @@

.card-mindwendel {
@extend .card;
width: 394px;
width: 285px;
}
25 changes: 25 additions & 0 deletions assets/scss/app.scss
Original file line number Diff line number Diff line change
Expand Up @@ -95,4 +95,29 @@ a .bi {

.heading-error {
font-size: 8rem;
}

.lane-header {
display: inline-block;
margin-left: 16px;
}

.lane {
border: 1px dashed $gray-400;
border-radius: 5px;
margin-right: 5px;
padding-top: 16px;

&--column-width {
width: 322px;
flex-shrink: 0;
}

&--full-width {
width: 100%;
}

&--half-width {
width: 100%;
}
}
2 changes: 1 addition & 1 deletion assets/scss/live/idea_live/_index_component.scss
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,6 @@
display: block;
}

.lightbulb-large {
.icon-large {
font-size: 5rem;
}
37 changes: 31 additions & 6 deletions lib/mindwendel/brainstormings.ex
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ defmodule Mindwendel.Brainstormings do
alias Mindwendel.Brainstormings.Idea
alias Mindwendel.Accounts.User
alias Mindwendel.Brainstormings.IdeaLabel
alias Mindwendel.Brainstormings.Lane
alias Mindwendel.Brainstormings.Brainstorming
alias Mindwendel.Brainstormings.BrainstormingModeratingUser

Expand Down Expand Up @@ -76,11 +77,13 @@ defmodule Mindwendel.Brainstormings do
:users,
:moderating_users,
labels: from(idea_label in IdeaLabel, order_by: idea_label.position_order),
ideas: [
:link,
:likes,
:label,
:idea_labels
lanes: [
ideas: [
:link,
:likes,
:label,
:idea_labels
]
]
])
|> update_last_accessed_at
Expand Down Expand Up @@ -112,6 +115,7 @@ defmodule Mindwendel.Brainstormings do
user
|> Ecto.build_assoc(:created_brainstormings,
labels: Brainstorming.idea_label_factory(),
lanes: [%Lane{position_order: 1}],
moderating_users: [user],
users: [user]
)
Expand Down Expand Up @@ -159,7 +163,7 @@ defmodule Mindwendel.Brainstormings do

def empty(%Brainstorming{} = brainstorming) do
# we only delete ideas - labels and users should be left intact:
Repo.delete_all(from idea in Idea, where: idea.brainstorming_id == ^brainstorming.id)
Repo.delete_all(from lane in Lane, where: lane.brainstorming_id == ^brainstorming.id)

broadcast({:ok, brainstorming}, :brainstorming_updated)
end
Expand Down Expand Up @@ -272,5 +276,26 @@ defmodule Mindwendel.Brainstormings do
{:ok, idea}
end

def broadcast({:ok, %Lane{} = lane}, event) do
Phoenix.PubSub.broadcast(
Mindwendel.PubSub,
"brainstormings:" <> lane.brainstorming_id,
{
event,
lane
|> Repo.preload(
ideas: [
:link,
:likes,
:label,
:idea_labels
]
)
}
)

{:ok, lane}
end

def broadcast({:error, _reason} = error, _event), do: error
end
2 changes: 2 additions & 0 deletions lib/mindwendel/brainstormings/brainstorming.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ defmodule Mindwendel.Brainstormings.Brainstorming do
import MindwendelWeb.Gettext
alias Mindwendel.Brainstormings.Idea
alias Mindwendel.Brainstormings.IdeaLabel
alias Mindwendel.Brainstormings.Lane
alias Mindwendel.Brainstormings.BrainstormingModeratingUser
alias Mindwendel.Accounts.User
alias Mindwendel.Accounts.BrainstormingUser
Expand All @@ -20,6 +21,7 @@ defmodule Mindwendel.Brainstormings.Brainstorming do
field :last_accessed_at, :utc_datetime
belongs_to :creating_user, User
has_many :ideas, Idea
has_many :lanes, Lane, preload_order: [asc: :position_order]
has_many :labels, IdeaLabel
many_to_many :users, User, join_through: BrainstormingUser
many_to_many :moderating_users, User, join_through: BrainstormingModeratingUser
Expand Down
3 changes: 3 additions & 0 deletions lib/mindwendel/brainstormings/idea.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ defmodule Mindwendel.Brainstormings.Idea do
alias Mindwendel.Brainstormings.IdeaLabel
alias Mindwendel.Brainstormings.IdeaIdeaLabel
alias Mindwendel.Brainstormings.Like
alias Mindwendel.Brainstormings.Lane
alias Mindwendel.Attachments.Link
alias Mindwendel.UrlPreview
alias Mindwendel.Accounts.User
Expand All @@ -22,6 +23,7 @@ defmodule Mindwendel.Brainstormings.Idea do
has_many :likes, Like
belongs_to :brainstorming, Brainstorming, foreign_key: :brainstorming_id, type: :binary_id
belongs_to :label, IdeaLabel, foreign_key: :label_id, type: :binary_id, on_replace: :nilify
belongs_to :lane, Lane, foreign_key: :lane_id, type: :binary_id
many_to_many :idea_labels, IdeaLabel, join_through: IdeaIdeaLabel, on_replace: :delete

timestamps()
Expand All @@ -34,6 +36,7 @@ defmodule Mindwendel.Brainstormings.Idea do
:username,
:body,
:brainstorming_id,
:lane_id,
:deprecated_label,
:label_id,
:user_id,
Expand Down
50 changes: 50 additions & 0 deletions lib/mindwendel/brainstormings/lane.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
defmodule Mindwendel.Brainstormings.Lane do
use Mindwendel.Schema

import Ecto.Changeset
alias Mindwendel.Lanes
alias Mindwendel.Brainstormings.Idea
alias Mindwendel.Brainstormings.Brainstorming

schema "lanes" do
field :name, :string
field :position_order, :integer
belongs_to :brainstorming, Brainstorming, type: :binary_id
has_many :ideas, Idea, preload_order: [asc: :position_order]

timestamps()
end

@doc false
def changeset(lane, attrs) do
lane
|> cast(attrs, [:name, :position_order, :brainstorming_id])
|> validate_required([:brainstorming_id])
|> add_position_order_if_missing()
end

defp add_position_order_if_missing(%Ecto.Changeset{changes: %{position_order: _}} = changeset) do
changeset
end

defp add_position_order_if_missing(
%Ecto.Changeset{
data: %Mindwendel.Brainstormings.Lane{
position_order: nil,
brainstorming_id: brainstorming_id
}
} = changeset
) do
changeset
|> put_change(:position_order, generate_position_order(brainstorming_id))
end

defp add_position_order_if_missing(changeset) do
changeset
end

defp generate_position_order(brainstorming_id) do
max = Lanes.get_max_position_order(brainstorming_id)
if max, do: max + 1, else: 1
end
end
31 changes: 21 additions & 10 deletions lib/mindwendel/ideas.ex
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ defmodule Mindwendel.Ideas do
alias Mindwendel.Repo

alias Mindwendel.Brainstormings
alias Mindwendel.Lanes
alias Mindwendel.Brainstormings.Like
alias Mindwendel.Brainstormings.Idea

Expand All @@ -26,11 +27,11 @@ defmodule Mindwendel.Ideas do
end

@doc """
Returns the list of ideas depending on the brainstorming id, ordered by position.
Returns the list of ideas depending on the brainstorming id and lane id, ordered by position.
## Examples
iex> list_ideas(3)
iex> list_ideas(3, 1)
[%Idea{}, ...]
"""
Expand Down Expand Up @@ -68,7 +69,7 @@ defmodule Mindwendel.Ideas do
%{1, nil}
"""
def update_ideas_for_brainstorming_by_likes(id) do
def update_ideas_for_brainstorming_by_likes(brainstorming_id, lane_id) do
idea_count_query =
from like in Like,
group_by: like.idea_id,
Expand All @@ -79,7 +80,7 @@ defmodule Mindwendel.Ideas do
from(idea in Idea,
left_join: idea_counts in subquery(idea_count_query),
on: idea_counts.idea_id == idea.id,
where: idea.brainstorming_id == ^id,
where: idea.brainstorming_id == ^brainstorming_id and idea.lane_id == ^lane_id,
select: %{
idea_id: idea.id,
like_count: idea_counts.like_count,
Expand All @@ -91,10 +92,13 @@ defmodule Mindwendel.Ideas do
from(idea in Idea,
join: idea_ranks in subquery(idea_rank_query),
on: idea_ranks.idea_id == idea.id,
where: idea.brainstorming_id == ^id,
where: idea.brainstorming_id == ^brainstorming_id and idea.lane_id == ^lane_id,
update: [set: [position_order: idea_ranks.idea_rank]]
)
|> Repo.update_all([])

lane = Lanes.get_lane!(lane_id)
Brainstormings.broadcast({:ok, lane}, :lane_updated)
end

@doc """
Expand All @@ -106,11 +110,11 @@ defmodule Mindwendel.Ideas do
%{1, nil}
"""
def update_ideas_for_brainstorming_by_labels(id) do
def update_ideas_for_brainstorming_by_labels(brainstorming_id, lane_id) do
idea_rank_query =
from(idea in Idea,
left_join: l in assoc(idea, :idea_labels),
where: idea.brainstorming_id == ^id,
where: idea.brainstorming_id == ^brainstorming_id and idea.lane_id == ^lane_id,
select: %{
idea_id: idea.id,
idea_rank:
Expand All @@ -124,10 +128,13 @@ defmodule Mindwendel.Ideas do
from(idea in Idea,
join: idea_ranks in subquery(idea_rank_query),
on: idea_ranks.idea_id == idea.id,
where: idea.brainstorming_id == ^id,
where: idea.brainstorming_id == ^brainstorming_id and idea.lane_id == ^lane_id,
update: [set: [position_order: idea_ranks.idea_rank]]
)
|> Repo.update_all([])

lane = Lanes.get_lane!(lane_id)
Brainstormings.broadcast({:ok, lane}, :lane_updated)
end

@doc """
Expand All @@ -141,11 +148,12 @@ defmodule Mindwendel.Ideas do
"""
def update_ideas_for_brainstorming_by_user_move(
brainstorming_id,
lane_id,
idea_id,
new_position,
old_position
) do
get_idea!(idea_id) |> update_idea(%{position_order: new_position})
get_idea!(idea_id) |> update_idea(%{position_order: new_position, lane_id: lane_id})

# depending on moving a card bottom up or up to bottom, we need to correct the ordering
order =
Expand All @@ -155,7 +163,7 @@ defmodule Mindwendel.Ideas do

idea_rank_query =
from(idea in Idea,
where: idea.brainstorming_id == ^brainstorming_id,
where: idea.brainstorming_id == ^brainstorming_id and idea.lane_id == ^lane_id,
windows: [o: [order_by: ^order]],
select: %{
idea_id: idea.id,
Expand All @@ -170,6 +178,9 @@ defmodule Mindwendel.Ideas do
update: [set: [position_order: idea_ranks.idea_rank]]
)
|> Repo.update_all([])

lane = Lanes.get_lane!(lane_id)
Brainstormings.broadcast({:ok, lane}, :lane_updated)
end

@doc """
Expand Down
Loading

0 comments on commit b26c7b6

Please sign in to comment.