Merge pull request #501 from basecamp/flavorjones/dynamic-bubble-formatting-v2
Introduce a stimulus controller for bubbles
This commit is contained in:
@@ -48,4 +48,12 @@ module CardsHelper
|
||||
title << "assigned to #{card.assignees.map(&:name).to_sentence}" if card.assignees.any?
|
||||
title.join(" ")
|
||||
end
|
||||
|
||||
def card_entropy_action(card)
|
||||
if card.doing?
|
||||
"Falls Back"
|
||||
elsif card.considering?
|
||||
"Closes"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import { Controller } from "@hotwired/stimulus"
|
||||
import { signedDifferenceInDays } from "helpers/date_helpers"
|
||||
|
||||
const REFRESH_INTERVAL = 3_600_000 // 1 hour (in milliseconds)
|
||||
|
||||
export default class extends Controller {
|
||||
static targets = [ "top", "days", "bottom" ]
|
||||
static values = { "closesAt": String, "reminderBefore": Number, "entropyAction": String }
|
||||
|
||||
#timer
|
||||
|
||||
connect() {
|
||||
this.#timer = setInterval(this.update.bind(this), REFRESH_INTERVAL)
|
||||
this.update()
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
clearInterval(this.#timer)
|
||||
}
|
||||
|
||||
update() {
|
||||
const closesInDays = signedDifferenceInDays(new Date(), new Date(this.closesAtValue))
|
||||
|
||||
if (closesInDays > this.reminderBeforeValue) {
|
||||
this.#hide()
|
||||
return
|
||||
}
|
||||
|
||||
this.topTarget.innerHTML = closesInDays < 1 ? this.entropyActionValue : `${this.entropyActionValue} in`
|
||||
this.daysTarget.innerHTML = closesInDays < 1 ? "!" : closesInDays
|
||||
this.bottomTarget.innerHTML = closesInDays < 1 ? "Today" : (closesInDays === 1 ? "day" : "days")
|
||||
|
||||
this.#show()
|
||||
}
|
||||
|
||||
#hide() {
|
||||
this.element.setAttribute("hidden", "")
|
||||
}
|
||||
|
||||
#show() {
|
||||
this.element.removeAttribute("hidden")
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,10 @@ export function differenceInDays(fromDate, toDate) {
|
||||
return Math.round(Math.abs((beginningOfDay(toDate) - beginningOfDay(fromDate)) / (1000 * 60 * 60 * 24)))
|
||||
}
|
||||
|
||||
export function signedDifferenceInDays(fromDate, toDate) {
|
||||
return Math.round((beginningOfDay(toDate) - beginningOfDay(fromDate)) / (1000 * 60 * 60 * 24))
|
||||
}
|
||||
|
||||
export function beginningOfDay(date) {
|
||||
return new Date(date.getFullYear(), date.getMonth(), date.getDate())
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
module Card::Closeable
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
AUTO_CLOSE_AFTER = 30.days
|
||||
AUTO_CLOSE_REMINDER_BEFORE = 7.days
|
||||
|
||||
included do
|
||||
|
||||
@@ -29,10 +29,6 @@ module Card::Engageable
|
||||
last_active_at + STAGNATED_AFTER if last_active_at
|
||||
end
|
||||
|
||||
def days_until_reconsider
|
||||
(auto_reconsider_at.to_date - Date.current).to_i if auto_reconsider_at
|
||||
end
|
||||
|
||||
def doing?
|
||||
open? && published? && engagement.present?
|
||||
end
|
||||
@@ -41,10 +37,6 @@ module Card::Engageable
|
||||
open? && published? && engagement.blank?
|
||||
end
|
||||
|
||||
def reconsidering_soon?
|
||||
doing? && Time.current >= auto_reconsider_at - AUTO_RECONSIDER_REMINDER_BEFORE
|
||||
end
|
||||
|
||||
def engage
|
||||
unless doing?
|
||||
transaction do
|
||||
|
||||
@@ -29,12 +29,10 @@
|
||||
|
||||
<%= render "cards/display/common/background", card: card %>
|
||||
|
||||
<% if card.closing_soon? %>
|
||||
<%= render "cards/display/preview/bubble", label: "Closes", days: card.days_until_close, class: "bubble--closing" %>
|
||||
<% end %>
|
||||
|
||||
<% if card.reconsidering_soon? %>
|
||||
<%= render "cards/display/preview/bubble", label: "Falls Back", days: card.days_until_close, class: "bubble--considering" %>
|
||||
<% if card.considering? %>
|
||||
<%= render "cards/display/preview/bubble", card: card, style: "closing" %>
|
||||
<% elsif card.doing? %>
|
||||
<%= render "cards/display/preview/bubble", card: card, style: "considering" %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
@@ -1,24 +1,22 @@
|
||||
<div class="bubble <%= local_assigns[:class] %>">
|
||||
<div hidden class="bubble bubble--<%= style %>"
|
||||
data-controller="bubble" data-action="turbo:morph-element->bubble#update"
|
||||
data-bubble-reminder-before-value="<%= Card::AUTO_CLOSE_REMINDER_BEFORE.in_days.to_i %>"
|
||||
data-bubble-closes-at-value="<%= card.auto_close_at.iso8601 %>"
|
||||
data-bubble-entropy-action-value="<%= card_entropy_action(card) %>">
|
||||
<svg viewBox="0 0 200 100">
|
||||
<path id="top-half" fill="transparent" d="M 20,100 A 80,80 0 0,1 180,100" />
|
||||
<text text-anchor="middle" fill="currentColor">
|
||||
<textPath href="#top-half" startOffset="50%" dominant-baseline="middle">
|
||||
<%= local_assigns[:label] %>
|
||||
<%= " in" if days > 1 %>
|
||||
</textPath>
|
||||
<textPath href="#top-half" startOffset="50%" dominant-baseline="middle" data-bubble-target="top"></textPath>
|
||||
</text>
|
||||
</svg>
|
||||
|
||||
<span class="bubble__number">
|
||||
<%= days < 1 ? "!" : days %>
|
||||
<span class="bubble__number" data-bubble-target="days">
|
||||
</span>
|
||||
|
||||
<svg viewBox="0 0 200 100">
|
||||
<path id="bottom-half" d="M 20,0 A 80,80 0 0,0 180,0" fill="transparent" />
|
||||
<text text-anchor="middle" fill="currentColor">
|
||||
<textPath href="#bottom-half" startOffset="50%" dominant-baseline="middle">
|
||||
<%= days < 1 ? "today" : "Day".pluralize(days) %>
|
||||
</textPath>
|
||||
<textPath href="#bottom-half" startOffset="50%" dominant-baseline="middle" data-bubble-target="bottom"></textPath>
|
||||
</text>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user