From 410cc953968109dbf3bf4e26a57f282658c6cde2 Mon Sep 17 00:00:00 2001 From: Jason Zimdars Date: Wed, 15 Jan 2025 17:16:59 -0600 Subject: [PATCH 01/42] Stub out basic design --- app/assets/images/history.svg | 1 + app/assets/stylesheets/events.css | 13 ++++++++++ app/controllers/events_controller.rb | 5 ++++ app/helpers/events_helper.rb | 14 ++++++++++ app/views/buckets/index.html.erb | 7 ++++- app/views/events/_event.html.erb | 10 ++++++++ app/views/events/index.html.erb | 38 ++++++++++++++++++++++++++++ config/routes.rb | 1 + 8 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 app/assets/images/history.svg create mode 100644 app/assets/stylesheets/events.css create mode 100644 app/controllers/events_controller.rb create mode 100644 app/helpers/events_helper.rb create mode 100644 app/views/events/_event.html.erb create mode 100644 app/views/events/index.html.erb diff --git a/app/assets/images/history.svg b/app/assets/images/history.svg new file mode 100644 index 000000000..f36d73631 --- /dev/null +++ b/app/assets/images/history.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/assets/stylesheets/events.css b/app/assets/stylesheets/events.css new file mode 100644 index 000000000..c7ca9a056 --- /dev/null +++ b/app/assets/stylesheets/events.css @@ -0,0 +1,13 @@ +.events { + border-block-start: 1px solid var(--color-subtle); + display: grid; + gap: var(--block-space-half); + grid-template-columns: repeat(4, 1fr); + grid-auto-rows: auto; + overflow: hidden; + padding-block-start: var(--block-space); + + h3 { + font-size: var(--font-size-small); + } +} diff --git a/app/controllers/events_controller.rb b/app/controllers/events_controller.rb new file mode 100644 index 000000000..1f290f788 --- /dev/null +++ b/app/controllers/events_controller.rb @@ -0,0 +1,5 @@ +class EventsController < ApplicationController + def index + @events = Event.all + end +end diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb new file mode 100644 index 000000000..fabb3600e --- /dev/null +++ b/app/helpers/events_helper.rb @@ -0,0 +1,14 @@ +module EventsHelper + def event_column(event) + case event.action + when "popped" + 4 + when "created" + 3 + when "commented" + 2 + else + 1 + end + end +end diff --git a/app/views/buckets/index.html.erb b/app/views/buckets/index.html.erb index 7c2f59a91..97353daaf 100644 --- a/app/views/buckets/index.html.erb +++ b/app/views/buckets/index.html.erb @@ -2,11 +2,16 @@ <% content_for :header do %> <% end %> -

Today

-
-

Active

-

Discussed

-

Added

-

Popped

- - <%= render @events %> -
- -

Yesterday

+

<%= event_day_title(@activity_day) %>

Active

Discussed

From 11454a1952aa4b196acf74a25218a72586d3c194 Mon Sep 17 00:00:00 2001 From: Kevin McConnell Date: Fri, 17 Jan 2025 17:28:07 +0000 Subject: [PATCH 04/42] Lazy-load activity pages --- app/assets/stylesheets/events.css | 4 +++ app/helpers/events_helper.rb | 4 +++ .../fetch_on_visible_controller.js | 28 +++++++++++++++++++ app/views/events/_day.html.erb | 13 +++++++++ app/views/events/index.html.erb | 12 +++----- app/views/events/index.turbo_stream.erb | 7 +++++ 6 files changed, 60 insertions(+), 8 deletions(-) create mode 100644 app/javascript/controllers/fetch_on_visible_controller.js create mode 100644 app/views/events/_day.html.erb create mode 100644 app/views/events/index.turbo_stream.erb diff --git a/app/assets/stylesheets/events.css b/app/assets/stylesheets/events.css index c7ca9a056..bc7181f4d 100644 --- a/app/assets/stylesheets/events.css +++ b/app/assets/stylesheets/events.css @@ -11,3 +11,7 @@ font-size: var(--font-size-small); } } + +.events--none { + padding: calc(3 * var(--block-space)); +} diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb index 0d75cf63f..fba345a32 100644 --- a/app/helpers/events_helper.rb +++ b/app/helpers/events_helper.rb @@ -22,4 +22,8 @@ module EventsHelper 1 end end + + def event_next_page_link(next_day) + tag.div id: "next_page", data: { controller: "fetch-on-visible", fetch_on_visible_url_value: events_path(day: next_day) } + end end diff --git a/app/javascript/controllers/fetch_on_visible_controller.js b/app/javascript/controllers/fetch_on_visible_controller.js new file mode 100644 index 000000000..a486fc4b4 --- /dev/null +++ b/app/javascript/controllers/fetch_on_visible_controller.js @@ -0,0 +1,28 @@ +import { Controller } from "@hotwired/stimulus" +import { get } from "@rails/request.js" + +export default class extends Controller { + static values = { url: String } + + connect() { + this.#observe() + } + + #observe() { + const observer = new IntersectionObserver((entries) => { + const visible = !!entries.find(entry => entry.isIntersecting) + if (visible) { + this.#fetch() + } + }) + + observer.observe(this.element) + } + + async #fetch() { + const resp = await get(this.urlValue, { responseKind: "turbo-stream" }) + const html = await resp.html + + Turbo.renderStreamMessage(html) + } +} diff --git a/app/views/events/_day.html.erb b/app/views/events/_day.html.erb new file mode 100644 index 000000000..865ef94a0 --- /dev/null +++ b/app/views/events/_day.html.erb @@ -0,0 +1,13 @@ +

<%= event_day_title(activity_day) %>

+<% if @events.any? %> +
+

Active

+

Discussed

+

Added

+

Popped

+ + <%= render events %> +
+<% else %> +

No activity for this day.

+<% end %> diff --git a/app/views/events/index.html.erb b/app/views/events/index.html.erb index ee8f75034..3f6f45024 100644 --- a/app/views/events/index.html.erb +++ b/app/views/events/index.html.erb @@ -17,12 +17,8 @@ <% end %> -

<%= event_day_title(@activity_day) %>

-
-

Active

-

Discussed

-

Added

-

Popped

- - <%= render @events %> +
+ <%= render "events/day", activity_day: @activity_day, next_day: @next_day, events: @events %>
+ +<%= event_next_page_link(@next_day) %> diff --git a/app/views/events/index.turbo_stream.erb b/app/views/events/index.turbo_stream.erb new file mode 100644 index 000000000..984b01ea3 --- /dev/null +++ b/app/views/events/index.turbo_stream.erb @@ -0,0 +1,7 @@ +<%= turbo_stream.append :activity do %> + <%= render "events/day", activity_day: @activity_day, next_day: @next_day, events: @events %> +<% end %> + +<%= turbo_stream.replace :next_page do %> + <%= event_next_page_link(@next_day) %> +<% end %> From e11b5095d58428bc716a143b50e4b3f51436ac4f Mon Sep 17 00:00:00 2001 From: Jason Zimdars Date: Tue, 21 Jan 2025 18:29:56 -0600 Subject: [PATCH 05/42] Add grid cell styles and time markers --- app/assets/stylesheets/colors.css | 2 ++ app/assets/stylesheets/events.css | 48 +++++++++++++++++++++++++++---- app/helpers/events_helper.rb | 33 +++++++++++++++++++++ app/views/events/_day.html.erb | 26 ++++++++--------- app/views/events/_event.html.erb | 2 +- 5 files changed, 91 insertions(+), 20 deletions(-) diff --git a/app/assets/stylesheets/colors.css b/app/assets/stylesheets/colors.css index 0be8c4ee4..b5a822ed5 100644 --- a/app/assets/stylesheets/colors.css +++ b/app/assets/stylesheets/colors.css @@ -12,6 +12,7 @@ --lch-red: 51% 0.2 31; --lch-green: 65.59% 0.234 142.49; --lch-green-light: 95% 0.03 142.49; + --lch-yellow: 96.01% 0.0348 81.29; --lch-always-black: 0% 0 0; --lch-always-white: 100% 0 0; @@ -28,6 +29,7 @@ --color-subtle-dark: oklch(var(--lch-gray-dark)); --color-selected: oklch(var(--lch-blue-light)); --color-selected-dark: oklch(var(--lch-blue-dark)); + --color-highlight: oklch(var(--lch-yellow)); --color-marker: oklch(var(--lch-orange)); --color-always-black: oklch(var(--lch-always-black)); --color-always-white: oklch(var(--lch-always-white)); diff --git a/app/assets/stylesheets/events.css b/app/assets/stylesheets/events.css index bc7181f4d..4180e749d 100644 --- a/app/assets/stylesheets/events.css +++ b/app/assets/stylesheets/events.css @@ -1,17 +1,53 @@ .events { + --grid-lines: 3px; + border-block-start: 1px solid var(--color-subtle); display: grid; - gap: var(--block-space-half); + gap: var(--grid-lines); grid-template-columns: repeat(4, 1fr); - grid-auto-rows: auto; - overflow: hidden; padding-block-start: var(--block-space); +} - h3 { - font-size: var(--font-size-small); - } +.event { + margin: var(--inline-space); } .events--none { padding: calc(3 * var(--block-space)); } + +.event-grid-item { + background-color: var(--color-selected); + block-size: 100%; + border-radius: var(--grid-lines); + display: flex; + inline-size: 100%; + min-block-size: var(--block-space); + + &:nth-child(-n + 8) { + background-color: var(--color-bg); + } + + &:nth-child(1n + 57) { + background-color: var(--color-highlight); + } +} + +.event-grid-time { + position: relative; + + time { + align-items: center; + background: var(--color-bg); + border-radius: 2em; + display: inline-flex; + font-size: var(--text-small); + font-weight: 600; + inline-size: max-content; + margin: 0 auto; + padding: 0 var(--inline-space-double); + position: absolute; + text-transform: uppercase; + transform: translate(-50%, 50%); + } +} diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb index fba345a32..187276241 100644 --- a/app/helpers/events_helper.rb +++ b/app/helpers/events_helper.rb @@ -26,4 +26,37 @@ module EventsHelper def event_next_page_link(next_day) tag.div id: "next_page", data: { controller: "fetch-on-visible", fetch_on_visible_url_value: events_path(day: next_day) } end + + def render_event_grid_cells(columns: 4, rows: 24) + safe_join((2..rows + 1).map do |row| + (1..columns).map do |col| + content_tag(:div, "", class: "event-grid-item", style: "grid-area: #{row}/#{col};") + end + end.flatten) + end + + def render_time_labels + safe_join((0..24).step(6).to_a.map do |hour| + time = Time.current.beginning_of_day + hour.hours + label = if hour == 24 + "midnight" + else + time.strftime("%l:%M %P").strip + end + content_tag(:div, + content_tag(:time, + label, + datetime: time.strftime("%H:%M") + ), + class: "event-grid-time", + style: "grid-area: #{25 - (hour * 23/24)}/2 / #{25 - (hour * 23/24)}/4;" + ) + end) + end + + def render_column_headers + [ "Active", "Discussed", "Added", "Popped" ].map do |header| + content_tag(:h3, header, class: "event-grid-column-title") + end.join.html_safe + end end diff --git a/app/views/events/_day.html.erb b/app/views/events/_day.html.erb index 865ef94a0..834472545 100644 --- a/app/views/events/_day.html.erb +++ b/app/views/events/_day.html.erb @@ -1,13 +1,13 @@ -

<%= event_day_title(activity_day) %>

-<% if @events.any? %> -
-

Active

-

Discussed

-

Added

-

Popped

- - <%= render events %> -
-<% else %> -

No activity for this day.

-<% end %> +
+

<%= event_day_title(activity_day) %>

+ <% if @events.any? %> +
+ <%= render_column_headers %> + <%= render_event_grid_cells %> + <%= render_time_labels %> + <%= render events %> +
+ <% else %> +

No activity for this day.

+ <% end %> +
diff --git a/app/views/events/_event.html.erb b/app/views/events/_event.html.erb index 3bc836c3a..8db780b16 100644 --- a/app/views/events/_event.html.erb +++ b/app/views/events/_event.html.erb @@ -1,6 +1,6 @@ <% bubble = event.summary.bubble %> -<%= link_to bucket_bubble_path(bubble.bucket, bubble), class: "panel shadow center flex-inline align-center justify-start gap position-relative", +<%= link_to bucket_bubble_path(bubble.bucket, bubble), class: "event panel shadow center flex-inline align-center justify-start gap position-relative", style: "grid-area: #{ event.created_at.hour }/ #{ event_column(event) }; --panel-border-radius: 0.7em; --panel-padding: 0.3em 0.6em; --panel-size: auto; --bubble-color: #{ bubble.color }; #{ bubble_rotation(bubble) }", data: { controller: "animation", animation_play_class: "bubble--wobble", animation_play_on_load_value: "true", action: "mouseover->animation#play" } do %>
From b2f72ff66868d1b6a66e85c499ecb3ec2f7550d7 Mon Sep 17 00:00:00 2001 From: Kevin McConnell Date: Wed, 22 Jan 2025 14:38:38 +0000 Subject: [PATCH 06/42] Use Stimulus to position events by timezone --- app/assets/stylesheets/events.css | 8 ++++++++ app/helpers/events_helper.rb | 12 +++++++----- .../controllers/timeline_controller.js | 17 +++++++++++++++++ app/views/events/_day.html.erb | 6 +++--- app/views/events/_event.html.erb | 7 ++++--- 5 files changed, 39 insertions(+), 11 deletions(-) create mode 100644 app/javascript/controllers/timeline_controller.js diff --git a/app/assets/stylesheets/events.css b/app/assets/stylesheets/events.css index 4180e749d..34a180b3e 100644 --- a/app/assets/stylesheets/events.css +++ b/app/assets/stylesheets/events.css @@ -31,6 +31,10 @@ &:nth-child(1n + 57) { background-color: var(--color-highlight); } + + &.future { + display: none; + } } .event-grid-time { @@ -50,4 +54,8 @@ text-transform: uppercase; transform: translate(-50%, 50%); } + + &.future { + display: none; + } } diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb index 187276241..a58a168a5 100644 --- a/app/helpers/events_helper.rb +++ b/app/helpers/events_helper.rb @@ -27,17 +27,18 @@ module EventsHelper tag.div id: "next_page", data: { controller: "fetch-on-visible", fetch_on_visible_url_value: events_path(day: next_day) } end - def render_event_grid_cells(columns: 4, rows: 24) + def render_event_grid_cells(day, columns: 4, rows: 24) safe_join((2..rows + 1).map do |row| (1..columns).map do |col| - content_tag(:div, "", class: "event-grid-item", style: "grid-area: #{row}/#{col};") + time = day.beginning_of_day + (rows - row + 1).hours + tag.div class: "event-grid-item", style: "grid-area: #{row}/#{col};", data: { datetime: time.iso8601, timeline_target: "cell" } end end.flatten) end - def render_time_labels + def render_time_labels(day) safe_join((0..24).step(6).to_a.map do |hour| - time = Time.current.beginning_of_day + hour.hours + time = day.beginning_of_day + hour.hours label = if hour == 24 "midnight" else @@ -49,7 +50,8 @@ module EventsHelper datetime: time.strftime("%H:%M") ), class: "event-grid-time", - style: "grid-area: #{25 - (hour * 23/24)}/2 / #{25 - (hour * 23/24)}/4;" + style: "grid-area: #{25 - (hour * 23/24)}/2 / #{25 - (hour * 23/24)}/4;", + data: { timeline_target: "cell", datetime: time.iso8601 } ) end) end diff --git a/app/javascript/controllers/timeline_controller.js b/app/javascript/controllers/timeline_controller.js new file mode 100644 index 000000000..370239176 --- /dev/null +++ b/app/javascript/controllers/timeline_controller.js @@ -0,0 +1,17 @@ +import { Controller } from "@hotwired/stimulus" + +const MAX_ROWS = 25 + +export default class extends Controller { + static targets = [ "cell", "item" ] + + cellTargetConnected(target) { + const dt = new Date(target.dataset.datetime) + target.classList.toggle("future", dt > new Date()) + } + + itemTargetConnected(target) { + const dt = new Date(target.dataset.datetime) + target.style.gridRowStart = MAX_ROWS - dt.getHours() + } +} diff --git a/app/views/events/_day.html.erb b/app/views/events/_day.html.erb index 834472545..0eb310096 100644 --- a/app/views/events/_day.html.erb +++ b/app/views/events/_day.html.erb @@ -1,10 +1,10 @@ -
+

<%= event_day_title(activity_day) %>

<% if @events.any? %>
<%= render_column_headers %> - <%= render_event_grid_cells %> - <%= render_time_labels %> + <%= render_event_grid_cells(activity_day) %> + <%= render_time_labels(activity_day) %> <%= render events %>
<% else %> diff --git a/app/views/events/_event.html.erb b/app/views/events/_event.html.erb index 8db780b16..6afa4ce39 100644 --- a/app/views/events/_event.html.erb +++ b/app/views/events/_event.html.erb @@ -1,8 +1,9 @@ <% bubble = event.summary.bubble %> -<%= link_to bucket_bubble_path(bubble.bucket, bubble), class: "event panel shadow center flex-inline align-center justify-start gap position-relative", - style: "grid-area: #{ event.created_at.hour }/ #{ event_column(event) }; --panel-border-radius: 0.7em; --panel-padding: 0.3em 0.6em; --panel-size: auto; --bubble-color: #{ bubble.color }; #{ bubble_rotation(bubble) }", - data: { controller: "animation", animation_play_class: "bubble--wobble", animation_play_on_load_value: "true", action: "mouseover->animation#play" } do %> +<%= link_to bucket_bubble_path(bubble.bucket, bubble), + class: "event panel shadow center flex-inline align-center justify-start gap position-relative", + style: "grid-column-start: #{ event_column(event) }; --panel-border-radius: 0.7em; --panel-padding: 0.3em 0.6em; --panel-size: auto; --bubble-color: #{ bubble.color }; #{ bubble_rotation(bubble) }", + data: { controller: "animation", animation_play_class: "bubble--wobble", animation_play_on_load_value: "true", action: "mouseover->animation#play", timeline_target: "item", datetime: event.created_at.iso8601 } do %>
<%= bubble.title %> From 4807d3cf7d79315d22124d9afd64d9b7750a3c57 Mon Sep 17 00:00:00 2001 From: Kevin McConnell Date: Wed, 22 Jan 2025 16:18:10 +0000 Subject: [PATCH 07/42] Don't try to render fetched response Because it's a turbo stream response, it will be rendered automatically. --- app/javascript/controllers/fetch_on_visible_controller.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/app/javascript/controllers/fetch_on_visible_controller.js b/app/javascript/controllers/fetch_on_visible_controller.js index a486fc4b4..42f1a36da 100644 --- a/app/javascript/controllers/fetch_on_visible_controller.js +++ b/app/javascript/controllers/fetch_on_visible_controller.js @@ -19,10 +19,7 @@ export default class extends Controller { observer.observe(this.element) } - async #fetch() { - const resp = await get(this.urlValue, { responseKind: "turbo-stream" }) - const html = await resp.html - - Turbo.renderStreamMessage(html) + #fetch() { + get(this.urlValue, { responseKind: "turbo-stream" }) } } From 86cb6271203fd92c1ce0cbac8e1c98f138d6222a Mon Sep 17 00:00:00 2001 From: Kevin McConnell Date: Wed, 22 Jan 2025 16:44:30 +0000 Subject: [PATCH 08/42] Include neighbouring events in paginated query Since we're rendering according to the local timezone, we'll need to include events that fall before or after the specific date on the server, as they might get be inside the current day in the client's timezone. So let's just grab a window of events either side of today, and then hide anything that falls outside the client's day. --- app/assets/stylesheets/events.css | 4 ++++ app/controllers/events_controller.rb | 2 +- app/javascript/controllers/timeline_controller.js | 9 +++++++++ app/views/events/_day.html.erb | 2 +- 4 files changed, 15 insertions(+), 2 deletions(-) diff --git a/app/assets/stylesheets/events.css b/app/assets/stylesheets/events.css index 34a180b3e..a4f212033 100644 --- a/app/assets/stylesheets/events.css +++ b/app/assets/stylesheets/events.css @@ -10,6 +10,10 @@ .event { margin: var(--inline-space); + + &.out-of-range { + display: none; + } } .events--none { diff --git a/app/controllers/events_controller.rb b/app/controllers/events_controller.rb index 7eaa09243..5ebc0243c 100644 --- a/app/controllers/events_controller.rb +++ b/app/controllers/events_controller.rb @@ -4,7 +4,7 @@ class EventsController < ApplicationController def index @events = Event. where(bubble: user_bubbles). - where(created_at: @activity_day.all_day) + where(created_at: @activity_day.yesterday.beginning_of_day..@activity_day.tomorrow.end_of_day) @next_day = @activity_day.yesterday.strftime("%Y-%m-%d") end diff --git a/app/javascript/controllers/timeline_controller.js b/app/javascript/controllers/timeline_controller.js index 370239176..a9eb906b5 100644 --- a/app/javascript/controllers/timeline_controller.js +++ b/app/javascript/controllers/timeline_controller.js @@ -4,6 +4,7 @@ const MAX_ROWS = 25 export default class extends Controller { static targets = [ "cell", "item" ] + static values = { date: String } cellTargetConnected(target) { const dt = new Date(target.dataset.datetime) @@ -12,6 +13,14 @@ export default class extends Controller { itemTargetConnected(target) { const dt = new Date(target.dataset.datetime) + target.classList.toggle("out-of-range", !this.#dateIsToday(dt)) target.style.gridRowStart = MAX_ROWS - dt.getHours() } + + #dateIsToday(dt) { + const date = new Date(this.dateValue) + return dt.getYear() == date.getYear() && + dt.getMonth() == date.getMonth() && + dt.getDay() == date.getDay() + } } diff --git a/app/views/events/_day.html.erb b/app/views/events/_day.html.erb index 0eb310096..fa289203e 100644 --- a/app/views/events/_day.html.erb +++ b/app/views/events/_day.html.erb @@ -1,4 +1,4 @@ -
+

<%= event_day_title(activity_day) %>

<% if @events.any? %>
From 2320a10c7485acaff3acb36c762e7b3220eb8089 Mon Sep 17 00:00:00 2001 From: Kevin McConnell Date: Wed, 22 Jan 2025 16:46:19 +0000 Subject: [PATCH 09/42] Show "published" in the added column Making a draft shouldn't show up here. --- app/helpers/events_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb index a58a168a5..23d47007f 100644 --- a/app/helpers/events_helper.rb +++ b/app/helpers/events_helper.rb @@ -14,7 +14,7 @@ module EventsHelper case event.action when "popped" 4 - when "created" + when "published" 3 when "commented" 2 From 0857f449a2844e05bf0ddae40e9e3b65b5f24da3 Mon Sep 17 00:00:00 2001 From: Jason Zimdars Date: Wed, 22 Jan 2025 15:59:24 -0600 Subject: [PATCH 10/42] Let's display the whole day after all --- app/assets/stylesheets/events.css | 4 ---- 1 file changed, 4 deletions(-) diff --git a/app/assets/stylesheets/events.css b/app/assets/stylesheets/events.css index a4f212033..679098e43 100644 --- a/app/assets/stylesheets/events.css +++ b/app/assets/stylesheets/events.css @@ -35,10 +35,6 @@ &:nth-child(1n + 57) { background-color: var(--color-highlight); } - - &.future { - display: none; - } } .event-grid-time { From c160e3c6ebd54dbe51ff2c57ca2a7d42070a5066 Mon Sep 17 00:00:00 2001 From: Jason Zimdars Date: Wed, 22 Jan 2025 15:59:56 -0600 Subject: [PATCH 11/42] `Active` => `Touched` --- app/helpers/events_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb index 23d47007f..a083e8e24 100644 --- a/app/helpers/events_helper.rb +++ b/app/helpers/events_helper.rb @@ -57,7 +57,7 @@ module EventsHelper end def render_column_headers - [ "Active", "Discussed", "Added", "Popped" ].map do |header| + [ "Touched", "Discussed", "Added", "Popped" ].map do |header| content_tag(:h3, header, class: "event-grid-column-title") end.join.html_safe end From 46b46aeb79324e024b3f3f7a94bff6b82470ecfe Mon Sep 17 00:00:00 2001 From: Jason Zimdars Date: Wed, 22 Jan 2025 16:00:33 -0600 Subject: [PATCH 12/42] Show this always, too --- app/assets/stylesheets/events.css | 4 ---- 1 file changed, 4 deletions(-) diff --git a/app/assets/stylesheets/events.css b/app/assets/stylesheets/events.css index 679098e43..3700c91d4 100644 --- a/app/assets/stylesheets/events.css +++ b/app/assets/stylesheets/events.css @@ -54,8 +54,4 @@ text-transform: uppercase; transform: translate(-50%, 50%); } - - &.future { - display: none; - } } From 63da34c2d966bc7b1a7ef995a0fcca2f41a3ee80 Mon Sep 17 00:00:00 2001 From: Jason Zimdars Date: Wed, 22 Jan 2025 16:50:07 -0600 Subject: [PATCH 13/42] Show bucket name --- app/views/events/_event.html.erb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/views/events/_event.html.erb b/app/views/events/_event.html.erb index 6afa4ce39..cc94bbb16 100644 --- a/app/views/events/_event.html.erb +++ b/app/views/events/_event.html.erb @@ -5,7 +5,8 @@ style: "grid-column-start: #{ event_column(event) }; --panel-border-radius: 0.7em; --panel-padding: 0.3em 0.6em; --panel-size: auto; --bubble-color: #{ bubble.color }; #{ bubble_rotation(bubble) }", data: { controller: "animation", animation_play_class: "bubble--wobble", animation_play_on_load_value: "true", action: "mouseover->animation#play", timeline_target: "item", datetime: event.created_at.iso8601 } do %>
-
+
<%= bubble.title %> + <%= bubble.bucket.name %>
<% end %> From 4674a45820129a6b824299b51c9bef351b895651 Mon Sep 17 00:00:00 2001 From: Jason Zimdars Date: Wed, 22 Jan 2025 21:40:01 -0600 Subject: [PATCH 14/42] Space between days --- app/views/events/_day.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/events/_day.html.erb b/app/views/events/_day.html.erb index fa289203e..d421db034 100644 --- a/app/views/events/_day.html.erb +++ b/app/views/events/_day.html.erb @@ -1,4 +1,4 @@ -
+

<%= event_day_title(activity_day) %>

<% if @events.any? %>
From 1c20872b833c402c8855dbca5b44eda6b3fca98b Mon Sep 17 00:00:00 2001 From: Jason Zimdars Date: Wed, 22 Jan 2025 21:57:38 -0600 Subject: [PATCH 15/42] Highlight the current hour FIXME: only do this on the current day --- app/assets/stylesheets/events.css | 6 +++++- app/javascript/controllers/timeline_controller.js | 8 ++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/app/assets/stylesheets/events.css b/app/assets/stylesheets/events.css index 3700c91d4..3417716ca 100644 --- a/app/assets/stylesheets/events.css +++ b/app/assets/stylesheets/events.css @@ -35,6 +35,10 @@ &:nth-child(1n + 57) { background-color: var(--color-highlight); } + + &.current-hour { + background-color: var(--color-selected-dark); + } } .event-grid-time { @@ -52,6 +56,6 @@ padding: 0 var(--inline-space-double); position: absolute; text-transform: uppercase; - transform: translate(-50%, 50%); + transform: translate(-50%, 0); } } diff --git a/app/javascript/controllers/timeline_controller.js b/app/javascript/controllers/timeline_controller.js index a9eb906b5..9dfccc63e 100644 --- a/app/javascript/controllers/timeline_controller.js +++ b/app/javascript/controllers/timeline_controller.js @@ -9,6 +9,7 @@ export default class extends Controller { cellTargetConnected(target) { const dt = new Date(target.dataset.datetime) target.classList.toggle("future", dt > new Date()) + target.classList.toggle("current-hour", this.#isCurrentHour(dt)) } itemTargetConnected(target) { @@ -23,4 +24,11 @@ export default class extends Controller { dt.getMonth() == date.getMonth() && dt.getDay() == date.getDay() } + + #isCurrentHour(dt) { + const now = new Date() + const utcHour = dt.getUTCHours() + const localHour = now.getHours() + return utcHour === localHour + } } From 3185e261e984ab4fce273b6274e3cde70b4a8110 Mon Sep 17 00:00:00 2001 From: Jason Zimdars Date: Wed, 22 Jan 2025 22:02:55 -0600 Subject: [PATCH 16/42] Stub project filter --- app/views/events/index.html.erb | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/views/events/index.html.erb b/app/views/events/index.html.erb index 3f6f45024..f984485e5 100644 --- a/app/views/events/index.html.erb +++ b/app/views/events/index.html.erb @@ -18,6 +18,14 @@ <% end %>
+
+ +
<%= render "events/day", activity_day: @activity_day, next_day: @next_day, events: @events %>
From 6bc88466a37bd60aa58a784ebf4cd2dd9842c269 Mon Sep 17 00:00:00 2001 From: Kevin McConnell Date: Thu, 23 Jan 2025 12:45:02 +0000 Subject: [PATCH 17/42] Only highlight current hour on today's entry --- app/javascript/controllers/timeline_controller.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/app/javascript/controllers/timeline_controller.js b/app/javascript/controllers/timeline_controller.js index 9dfccc63e..d7ca0638d 100644 --- a/app/javascript/controllers/timeline_controller.js +++ b/app/javascript/controllers/timeline_controller.js @@ -20,15 +20,17 @@ export default class extends Controller { #dateIsToday(dt) { const date = new Date(this.dateValue) - return dt.getYear() == date.getYear() && - dt.getMonth() == date.getMonth() && - dt.getDay() == date.getDay() + return this.#sameDay(dt, date) } #isCurrentHour(dt) { const now = new Date() - const utcHour = dt.getUTCHours() - const localHour = now.getHours() - return utcHour === localHour + return this.#sameDay(dt, now) && dt.getHours() === now.getHours() + } + + #sameDay(dt1, dt2) { + return dt1.getYear() === dt2.getYear() && + dt1.getMonth() === dt2.getMonth() && + dt1.getDay() === dt2.getDay() } } From 2cd2d5e2a194b26e2f992a721ef294d708720113 Mon Sep 17 00:00:00 2001 From: Kevin McConnell Date: Thu, 23 Jan 2025 12:45:22 +0000 Subject: [PATCH 18/42] We're not using the `future` class now --- app/javascript/controllers/timeline_controller.js | 1 - 1 file changed, 1 deletion(-) diff --git a/app/javascript/controllers/timeline_controller.js b/app/javascript/controllers/timeline_controller.js index d7ca0638d..1c6910ab4 100644 --- a/app/javascript/controllers/timeline_controller.js +++ b/app/javascript/controllers/timeline_controller.js @@ -8,7 +8,6 @@ export default class extends Controller { cellTargetConnected(target) { const dt = new Date(target.dataset.datetime) - target.classList.toggle("future", dt > new Date()) target.classList.toggle("current-hour", this.#isCurrentHour(dt)) } From 570d4ff66c7f5be8d7aa1d87afeaf69afffbb9ca Mon Sep 17 00:00:00 2001 From: Kevin McConnell Date: Thu, 23 Jan 2025 13:11:04 +0000 Subject: [PATCH 19/42] Control the "no activity" label client-side This allows us to take into account the local timezone -- any days where all the events were outside the local 24-hour period are the ones that are empty. --- app/assets/stylesheets/events.css | 12 +++++++++++- app/views/events/_day.html.erb | 21 ++++++++++----------- 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/app/assets/stylesheets/events.css b/app/assets/stylesheets/events.css index 3417716ca..e56cb85f6 100644 --- a/app/assets/stylesheets/events.css +++ b/app/assets/stylesheets/events.css @@ -2,7 +2,7 @@ --grid-lines: 3px; border-block-start: 1px solid var(--color-subtle); - display: grid; + display: none; gap: var(--grid-lines); grid-template-columns: repeat(4, 1fr); padding-block-start: var(--block-space); @@ -20,6 +20,16 @@ padding: calc(3 * var(--block-space)); } +.events--day:has(.event:not(.out-of-range)) { + .events { + display: grid; + } + + .events--none { + display: none; + } +} + .event-grid-item { background-color: var(--color-selected); block-size: 100%; diff --git a/app/views/events/_day.html.erb b/app/views/events/_day.html.erb index d421db034..a59347f07 100644 --- a/app/views/events/_day.html.erb +++ b/app/views/events/_day.html.erb @@ -1,13 +1,12 @@ -
+

<%= event_day_title(activity_day) %>

- <% if @events.any? %> -
- <%= render_column_headers %> - <%= render_event_grid_cells(activity_day) %> - <%= render_time_labels(activity_day) %> - <%= render events %> -
- <% else %> -

No activity for this day.

- <% end %> + +
+ <%= render_column_headers %> + <%= render_event_grid_cells(activity_day) %> + <%= render_time_labels(activity_day) %> + <%= render events %> +
+ +

No activity for this day.

From c0a2b360002adfede470eb2052f907c2edb6a98d Mon Sep 17 00:00:00 2001 From: Jason Zimdars Date: Thu, 23 Jan 2025 09:53:12 -0600 Subject: [PATCH 20/42] Color all cells --- app/assets/stylesheets/events.css | 4 ---- app/helpers/events_helper.rb | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/app/assets/stylesheets/events.css b/app/assets/stylesheets/events.css index e56cb85f6..67ae78a1b 100644 --- a/app/assets/stylesheets/events.css +++ b/app/assets/stylesheets/events.css @@ -38,10 +38,6 @@ inline-size: 100%; min-block-size: var(--block-space); - &:nth-child(-n + 8) { - background-color: var(--color-bg); - } - &:nth-child(1n + 57) { background-color: var(--color-highlight); } diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb index a083e8e24..ed065e9ba 100644 --- a/app/helpers/events_helper.rb +++ b/app/helpers/events_helper.rb @@ -58,7 +58,7 @@ module EventsHelper def render_column_headers [ "Touched", "Discussed", "Added", "Popped" ].map do |header| - content_tag(:h3, header, class: "event-grid-column-title") + content_tag(:h3, header, class: "event-grid-column-title margin-block-end-half") end.join.html_safe end end From 7f9647534450ae1b66eef6b35cddff46e81c51b1 Mon Sep 17 00:00:00 2001 From: Kevin McConnell Date: Thu, 23 Jan 2025 17:07:49 +0000 Subject: [PATCH 21/42] Each cluster of events should have a parent --- app/assets/stylesheets/events.css | 4 ++++ app/controllers/events_controller.rb | 3 ++- app/views/events/_day.html.erb | 8 +++++++- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/app/assets/stylesheets/events.css b/app/assets/stylesheets/events.css index 67ae78a1b..bcd8dd804 100644 --- a/app/assets/stylesheets/events.css +++ b/app/assets/stylesheets/events.css @@ -8,6 +8,10 @@ padding-block-start: var(--block-space); } +.event--cluster { + display: contents; +} + .event { margin: var(--inline-space); diff --git a/app/controllers/events_controller.rb b/app/controllers/events_controller.rb index 5ebc0243c..cd924167e 100644 --- a/app/controllers/events_controller.rb +++ b/app/controllers/events_controller.rb @@ -4,7 +4,8 @@ class EventsController < ApplicationController def index @events = Event. where(bubble: user_bubbles). - where(created_at: @activity_day.yesterday.beginning_of_day..@activity_day.tomorrow.end_of_day) + where(created_at: @activity_day.yesterday.beginning_of_day..@activity_day.tomorrow.end_of_day). + group_by { |event| [ event.created_at.hour, helpers.event_column(event) ] } @next_day = @activity_day.yesterday.strftime("%Y-%m-%d") end diff --git a/app/views/events/_day.html.erb b/app/views/events/_day.html.erb index a59347f07..ce9fb1ed8 100644 --- a/app/views/events/_day.html.erb +++ b/app/views/events/_day.html.erb @@ -5,7 +5,13 @@ <%= render_column_headers %> <%= render_event_grid_cells(activity_day) %> <%= render_time_labels(activity_day) %> - <%= render events %> + + <% events.each do |cell, cluster| %> +
+ <%= render cluster %> +
+ <% end %> +

No activity for this day.

From 269f8b0f049ea153a02abe9fc81d182fd6ba59b4 Mon Sep 17 00:00:00 2001 From: Kevin McConnell Date: Thu, 23 Jan 2025 17:11:19 +0000 Subject: [PATCH 22/42] Remove unused variable name --- app/views/events/_day.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/events/_day.html.erb b/app/views/events/_day.html.erb index ce9fb1ed8..731b90c0d 100644 --- a/app/views/events/_day.html.erb +++ b/app/views/events/_day.html.erb @@ -6,7 +6,7 @@ <%= render_event_grid_cells(activity_day) %> <%= render_time_labels(activity_day) %> - <% events.each do |cell, cluster| %> + <% events.each do |_, cluster| %>
<%= render cluster %>
From 6e086cc0bbb639ee083d00744864dc72374049dd Mon Sep 17 00:00:00 2001 From: Jason Zimdars Date: Fri, 24 Jan 2025 16:23:39 -0600 Subject: [PATCH 23/42] Style filter drop-down --- app/assets/stylesheets/inputs.css | 15 +++++++++++++-- app/views/events/index.html.erb | 4 ++-- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/app/assets/stylesheets/inputs.css b/app/assets/stylesheets/inputs.css index eb4fbc821..5b8a9a6db 100644 --- a/app/assets/stylesheets/inputs.css +++ b/app/assets/stylesheets/inputs.css @@ -79,9 +79,20 @@ } .input--select { - --input-padding: 0.5em 0em 0.5em 0.7em; - + --input-border-radius: 2em; + --input-padding: 0.5em 1.8em 0.5em 1.2em; + + -webkit-appearance: none; + appearance: none; + background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='m12 19.5c-.7 0-1.3-.3-1.7-.8l-9.8-11.1c-.7-.8-.6-1.9.2-2.6.8-.6 1.9-.6 2.5.2l8.6 9.8c0 .1.2.1.4 0l8.6-9.8c.7-.8 1.8-.9 2.6-.2s.9 1.8.2 2.6l-9.8 11.1c-.4.5-1.1.8-1.7.8z' fill='%23000'/%3E%3C/svg%3E"); + background-size: 0.5em; + background-position: center right 0.9em; + background-repeat: no-repeat; text-align: start; + + @media (prefers-color-scheme: dark) { + background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='m12 19.5c-.7 0-1.3-.3-1.7-.8l-9.8-11.1c-.7-.8-.6-1.9.2-2.6.8-.6 1.9-.6 2.5.2l8.6 9.8c0 .1.2.1.4 0l8.6-9.8c.7-.8 1.8-.9 2.6-.2s.9 1.8.2 2.6l-9.8 11.1c-.4.5-1.1.8-1.7.8z' fill='%23fff'/%3E%3C/svg%3E"); + } } .input--textara { diff --git a/app/views/events/index.html.erb b/app/views/events/index.html.erb index f984485e5..9c7579ae2 100644 --- a/app/views/events/index.html.erb +++ b/app/views/events/index.html.erb @@ -18,8 +18,8 @@ <% end %>
-
- <% Bucket.all.each do |bucket| %> From ea650389e22195a97179ff58bfece41c56adc0ae Mon Sep 17 00:00:00 2001 From: Jason Zimdars Date: Fri, 24 Jan 2025 16:23:48 -0600 Subject: [PATCH 24/42] Brighten up meta text --- app/views/events/_event.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/events/_event.html.erb b/app/views/events/_event.html.erb index cc94bbb16..b8356da97 100644 --- a/app/views/events/_event.html.erb +++ b/app/views/events/_event.html.erb @@ -7,6 +7,6 @@
<%= bubble.title %> - <%= bubble.bucket.name %> + <%= bubble.bucket.name %>
<% end %> From 93af7a0e75ebc6e986219e34eda500f04b5dbbfe Mon Sep 17 00:00:00 2001 From: Jason Zimdars Date: Fri, 24 Jan 2025 16:31:14 -0600 Subject: [PATCH 25/42] Dark mode variant --- app/assets/stylesheets/colors.css | 1 + 1 file changed, 1 insertion(+) diff --git a/app/assets/stylesheets/colors.css b/app/assets/stylesheets/colors.css index b5a822ed5..135b0d0cc 100644 --- a/app/assets/stylesheets/colors.css +++ b/app/assets/stylesheets/colors.css @@ -47,6 +47,7 @@ --lch-red: 73.8% 0.184 29.18; --lch-green: 75% 0.21 141.89; --lch-green-light: 28.11% 0.02 142.49; + --lch-yellow: 35.29% 0.0493 84.59; } } From 13dacceca23cd711282047434daaa79b0432b3fd Mon Sep 17 00:00:00 2001 From: Jason Zimdars Date: Fri, 24 Jan 2025 19:55:28 -0600 Subject: [PATCH 26/42] Ensure there is space between clustered events --- app/views/events/_event.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/events/_event.html.erb b/app/views/events/_event.html.erb index b8356da97..20bdacf1d 100644 --- a/app/views/events/_event.html.erb +++ b/app/views/events/_event.html.erb @@ -1,7 +1,7 @@ <% bubble = event.summary.bubble %> <%= link_to bucket_bubble_path(bubble.bucket, bubble), - class: "event panel shadow center flex-inline align-center justify-start gap position-relative", + class: "event panel shadow center center-block flex-inline align-center justify-start gap position-relative", style: "grid-column-start: #{ event_column(event) }; --panel-border-radius: 0.7em; --panel-padding: 0.3em 0.6em; --panel-size: auto; --bubble-color: #{ bubble.color }; #{ bubble_rotation(bubble) }", data: { controller: "animation", animation_play_class: "bubble--wobble", animation_play_on_load_value: "true", action: "mouseover->animation#play", timeline_target: "item", datetime: event.created_at.iso8601 } do %>
From d01e62a2edae0db70c2224a378c2bfea62871441 Mon Sep 17 00:00:00 2001 From: Jason Zimdars Date: Fri, 24 Jan 2025 20:06:58 -0600 Subject: [PATCH 27/42] Stub CSS for displaying events in a cluster @kevinmcconnell in order for the clusters to work, we'll need to update `timeline_conteroller.js` to set `grid-column-start` and `grid-row-start` on `.cluster` so it displays in the correct hour/action cell. Then we'll enable these styles to display them together without overlapping. --- app/assets/stylesheets/events.css | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/app/assets/stylesheets/events.css b/app/assets/stylesheets/events.css index bcd8dd804..21f19199b 100644 --- a/app/assets/stylesheets/events.css +++ b/app/assets/stylesheets/events.css @@ -10,6 +10,19 @@ .event--cluster { display: contents; + + /* FIXME re-enable this once we can set grid-column-start and grid-row-start on the cluster element */ + + /* &:has(.event + .event) { + display: grid; + grid-row-start: 12; + padding-block: var(--block-space-half); + + .event { + grid-column-start: unset !important; + grid-row-start: unset !important; + } + } */ } .event { From bd6e2ab1abd688b9f09ed255eb04ca4465a34343 Mon Sep 17 00:00:00 2001 From: Jason Zimdars Date: Fri, 24 Jan 2025 20:09:22 -0600 Subject: [PATCH 28/42] Stub expanded filters design --- app/views/events/index.html.erb | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/app/views/events/index.html.erb b/app/views/events/index.html.erb index 9c7579ae2..0186d1240 100644 --- a/app/views/events/index.html.erb +++ b/app/views/events/index.html.erb @@ -18,13 +18,18 @@ <% end %>
-
- + + <% Bucket.all.each do |bucket| %> - + <% end %> -
<%= render "events/day", activity_day: @activity_day, next_day: @next_day, events: @events %>
From 9b2d25e564982c3b323f92b826f3800f85ee4b64 Mon Sep 17 00:00:00 2001 From: Jason Zimdars Date: Fri, 24 Jan 2025 20:24:07 -0600 Subject: [PATCH 29/42] Oops, should be this --- app/assets/stylesheets/events.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/stylesheets/events.css b/app/assets/stylesheets/events.css index 21f19199b..c7f0a5323 100644 --- a/app/assets/stylesheets/events.css +++ b/app/assets/stylesheets/events.css @@ -12,10 +12,10 @@ display: contents; /* FIXME re-enable this once we can set grid-column-start and grid-row-start on the cluster element */ - + /* &:has(.event + .event) { display: grid; - grid-row-start: 12; + gap: var(--inline-space); padding-block: var(--block-space-half); .event { From 94b2c256b907182a5e4ac320a3728a99187676e5 Mon Sep 17 00:00:00 2001 From: Kevin McConnell Date: Mon, 27 Jan 2025 14:16:15 +0000 Subject: [PATCH 30/42] Set request timezone to match browser --- app/controllers/application_controller.rb | 2 +- app/controllers/concerns/current_timezone.rb | 17 +++++++++++++++++ .../controllers/timezone_cookie_controller.js | 12 ++++++++++++ app/views/layouts/application.html.erb | 2 +- 4 files changed, 31 insertions(+), 2 deletions(-) create mode 100644 app/controllers/concerns/current_timezone.rb create mode 100644 app/javascript/controllers/timezone_cookie_controller.js diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index c54063eef..4f75453ff 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,5 +1,5 @@ class ApplicationController < ActionController::Base - include Authentication + include Authentication, CurrentTimezone stale_when_importmap_changes allow_browser versions: :modern diff --git a/app/controllers/concerns/current_timezone.rb b/app/controllers/concerns/current_timezone.rb new file mode 100644 index 000000000..9c36757c8 --- /dev/null +++ b/app/controllers/concerns/current_timezone.rb @@ -0,0 +1,17 @@ +module CurrentTimezone + extend ActiveSupport::Concern + + included do + around_action :set_current_timezone + end + + private + def set_current_timezone(&) + Time.use_zone(timezone_from_cookie, &) + end + + def timezone_from_cookie + timezone = cookies[:timezone] + timezone if timezone.present? && ActiveSupport::TimeZone[timezone] + end +end diff --git a/app/javascript/controllers/timezone_cookie_controller.js b/app/javascript/controllers/timezone_cookie_controller.js new file mode 100644 index 000000000..f469a3f8f --- /dev/null +++ b/app/javascript/controllers/timezone_cookie_controller.js @@ -0,0 +1,12 @@ +import { Controller } from "@hotwired/stimulus" + +export default class extends Controller { + connect() { + this.#setTimezoneCookie() + } + + #setTimezoneCookie() { + const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone + document.cookie = `timezone=${encodeURIComponent(timezone)}; path=/` + } +} diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 251af449a..3a4b0e53e 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -19,7 +19,7 @@ <%= yield :head %> - +