From 388fe2de73e19d8079fc4f9a90cbf285fce6aaaa Mon Sep 17 00:00:00 2001 From: Mike Dalessio Date: Thu, 15 May 2025 14:05:07 -0400 Subject: [PATCH] Introduce a stimulus controller for bubbles The "closing soon" bubbles were introduced in #406, and the "falling back" bubbles in #500. However, these bubbles are part of the cached card and so as time passes, the relative time doesn't change unless the card is touched. This PR introduces a stimulus controller for bubbles, which takes care of: - making the bubble visible during the reminder period - calculating the relative number of days until entropy kicks in - rendering appropriate text around the day count ref: https://37s.fizzy.37signals.com/collections/693169850/cards/999009091 --- .../controllers/bubble_controller.js | 48 +++++++++++++++++++ app/javascript/helpers/date_helpers.js | 4 ++ app/models/card/closeable.rb | 1 - app/models/card/engageable.rb | 8 ---- app/views/cards/display/_preview.html.erb | 10 ++-- .../cards/display/preview/_bubble.html.erb | 17 +++---- 6 files changed, 63 insertions(+), 25 deletions(-) create mode 100644 app/javascript/controllers/bubble_controller.js diff --git a/app/javascript/controllers/bubble_controller.js b/app/javascript/controllers/bubble_controller.js new file mode 100644 index 000000000..8ca4328d9 --- /dev/null +++ b/app/javascript/controllers/bubble_controller.js @@ -0,0 +1,48 @@ +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 = [ "closingTop", "consideringTop", "days", "bottom" ] + static values = { "closesAt": String, "reminderBefore": Number } + + #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 + } + + if (this.hasClosingTopTarget) { + this.closingTopTarget.innerHTML = closesInDays < 1 ? "Closes" : "Closes in" + } + if (this.hasConsideringTopTarget) { + this.consideringTopTarget.innerHTML = closesInDays < 1 ? "Falls Back" : "Falls Back 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") + } +} diff --git a/app/javascript/helpers/date_helpers.js b/app/javascript/helpers/date_helpers.js index 9f3dffc4d..529b2968e 100644 --- a/app/javascript/helpers/date_helpers.js +++ b/app/javascript/helpers/date_helpers.js @@ -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()) } diff --git a/app/models/card/closeable.rb b/app/models/card/closeable.rb index 121a0e2d4..46c3a9696 100644 --- a/app/models/card/closeable.rb +++ b/app/models/card/closeable.rb @@ -1,7 +1,6 @@ module Card::Closeable extend ActiveSupport::Concern - AUTO_CLOSE_AFTER = 30.days AUTO_CLOSE_REMINDER_BEFORE = 7.days included do diff --git a/app/models/card/engageable.rb b/app/models/card/engageable.rb index 493d5b9ae..aa5279d64 100644 --- a/app/models/card/engageable.rb +++ b/app/models/card/engageable.rb @@ -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 diff --git a/app/views/cards/display/_preview.html.erb b/app/views/cards/display/_preview.html.erb index 6c25054aa..c874d8151 100644 --- a/app/views/cards/display/_preview.html.erb +++ b/app/views/cards/display/_preview.html.erb @@ -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 %> diff --git a/app/views/cards/display/preview/_bubble.html.erb b/app/views/cards/display/preview/_bubble.html.erb index 41319eef8..d34bd8bff 100644 --- a/app/views/cards/display/preview/_bubble.html.erb +++ b/app/views/cards/display/preview/_bubble.html.erb @@ -1,24 +1,21 @@ -
+