diff --git a/app/assets/stylesheets/application/sidebar.css b/app/assets/stylesheets/application/sidebar.css index 5a9e69f7..f3c04c81 100644 --- a/app/assets/stylesheets/application/sidebar.css +++ b/app/assets/stylesheets/application/sidebar.css @@ -419,12 +419,45 @@ padding-inline-end: 0; /* Remove padding on the right side */ } -/* Style for the star button container */ -[data-type="list_node"] .txt-small { +/* Style for the involvement button container */ +[data-type="list_node"] .txt-small, +[data-type="list_node"] turbo-frame.txt-small { margin-inline-start: 0; /* Reset left margin to maintain spacing with timestamp */ margin-inline-end: -1ch; /* Pull from the right side only */ } +/* Lazy-loaded involvement button placeholder */ +turbo-frame[data-controller*="lazy-involvement"]:not([src]):empty::before { + content: ""; + display: inline-block; + width: 20px; + height: 20px; +} + +/* Sidebar loading indicator */ +.sidebar__loading { + display: flex; + align-items: center; + justify-content: center; + padding: var(--block-space); + min-height: 200px; +} + +.sidebar__loading .spinner { + color: var(--color-text); + opacity: 0.6; +} + +/* Show loading indicator when turbo frame is loading */ +turbo-frame#user_sidebar[src]:not([src=""]):not([complete]) .sidebar__loading { + display: flex; +} + +turbo-frame#user_sidebar:not([src]) .sidebar__loading, +turbo-frame#user_sidebar[complete] .sidebar__loading { + display: none; +} + .rooms__new-btn { position: relative; z-index: 4; diff --git a/app/frontend/controllers/lazy_involvement_controller.js b/app/frontend/controllers/lazy_involvement_controller.js new file mode 100644 index 00000000..eae5b19c --- /dev/null +++ b/app/frontend/controllers/lazy_involvement_controller.js @@ -0,0 +1,19 @@ +import { Controller } from "@hotwired/stimulus" + +export default class extends Controller { + static values = { src: String, loaded: Boolean } + + connect() { + this.loadedValue = false + } + + load() { + if (this.loadedValue) return + + const frame = this.element + if (this.srcValue && !frame.src) { + frame.src = this.srcValue + this.loadedValue = true + } + } +} diff --git a/app/frontend/controllers/lazy_involvement_trigger_controller.js b/app/frontend/controllers/lazy_involvement_trigger_controller.js new file mode 100644 index 00000000..24994b3b --- /dev/null +++ b/app/frontend/controllers/lazy_involvement_trigger_controller.js @@ -0,0 +1,18 @@ +import { Controller } from "@hotwired/stimulus" + +export default class extends Controller { + static values = { target: String } + + load() { + if (!this.targetValue) return + + const frame = document.getElementById(this.targetValue) + if (frame) { + // Trigger the lazy-involvement controller to load + const lazyController = this.application.getControllerForElementAndIdentifier(frame, "lazy-involvement") + if (lazyController) { + lazyController.load() + } + } + } +} diff --git a/app/frontend/controllers/sidebar_loading_controller.js b/app/frontend/controllers/sidebar_loading_controller.js new file mode 100644 index 00000000..409fdba6 --- /dev/null +++ b/app/frontend/controllers/sidebar_loading_controller.js @@ -0,0 +1,24 @@ +import { Controller } from "@hotwired/stimulus" + +export default class extends Controller { + static targets = ["indicator"] + + connect() { + // Hide loading indicator if content is already loaded + if (this.hasIndicatorTarget && this.element.querySelector(".sidebar__container")) { + this.hide() + } + } + + show() { + if (this.hasIndicatorTarget) { + this.indicatorTarget.style.display = "flex" + } + } + + hide() { + if (this.hasIndicatorTarget) { + this.indicatorTarget.style.display = "none" + } + } +} diff --git a/app/helpers/users/sidebar_helper.rb b/app/helpers/users/sidebar_helper.rb index 15a9f4f2..87b78cbb 100644 --- a/app/helpers/users/sidebar_helper.rb +++ b/app/helpers/users/sidebar_helper.rb @@ -6,11 +6,26 @@ def has_unreads? end def sidebar_turbo_frame_tag(src: nil, &) - turbo_frame_tag :user_sidebar, src: src, target: "_top", data: { - controller: "rooms-list read-rooms turbo-frame", - rooms_list_unread_class: "unread", - rooms_list_badge_class: "badge", - action: "presence:present@window->rooms-list#read read-rooms:read->rooms-list#read turbo:frame-load->rooms-list#loaded refresh-room:visible@window->turbo-frame#reload".html_safe # otherwise -> is escaped - }, & + if block_given? + turbo_frame_tag :user_sidebar, src: src, target: "_top", data: { + controller: "rooms-list read-rooms turbo-frame", + rooms_list_unread_class: "unread", + rooms_list_badge_class: "badge", + action: "presence:present@window->rooms-list#read read-rooms:read->rooms-list#read turbo:frame-load->rooms-list#loaded refresh-room:visible@window->turbo-frame#reload".html_safe # otherwise -> is escaped + } do + yield + end + else + turbo_frame_tag :user_sidebar, src: src, target: "_top", data: { + controller: "rooms-list read-rooms turbo-frame sidebar-loading", + rooms_list_unread_class: "unread", + rooms_list_badge_class: "badge", + action: "presence:present@window->rooms-list#read read-rooms:read->rooms-list#read turbo:frame-load->rooms-list#loaded turbo:frame-load->sidebar-loading#hide refresh-room:visible@window->turbo-frame#reload".html_safe # otherwise -> is escaped + } do + content_tag :div, class: "sidebar__loading", data: { sidebar_loading_target: "indicator" } do + content_tag :div, "", class: "spinner" + end + end + end end end diff --git a/app/views/users/sidebars/rooms/_shared.html.erb b/app/views/users/sidebars/rooms/_shared.html.erb index 90fa1012..59b8f5a1 100644 --- a/app/views/users/sidebars/rooms/_shared.html.erb +++ b/app/views/users/sidebars/rooms/_shared.html.erb @@ -13,7 +13,12 @@ data-sidebar-starred-rooms-target="room" data-involvement="<%= involvement %>" data-type="list_node" <%= "hidden" if hide_in_starred_list %> - class="flex gap align-center justify-space-between"> + class="flex gap align-center justify-space-between" + <% unless room.conversation_room? %> + data-controller="lazy-involvement-trigger" + data-lazy-involvement-trigger-target-value="<%= dom_id(room, dom_prefix(:sidebar, :involvement)) %>" + data-action="mouseenter->lazy-involvement-trigger#load" + <% end %>> <%= link_to_room room, class: [ "flex flex-item-grow gap align-center justify-space-between room full-height txt-nowrap overflow-ellipsis txt-lighter txt-undecorated pad-block position-relative", "unread": unread, "badge": has_notifications ], style: "padding-block: calc(var(--block-space) / 4);" do %> @@ -25,10 +30,12 @@ <% end %> <% unless room.conversation_room? %> - - <%= turbo_frame_tag dom_id(room, dom_prefix(:sidebar, :involvement)) do %> - <%= button_to_change_involvement(room, involvement, from_sidebar: true) %> - <% end %> - + <%= turbo_frame_tag dom_id(room, dom_prefix(:sidebar, :involvement)), + class: "txt-small", + data: { + controller: "lazy-involvement", + lazy_involvement_src_value: room_involvement_path(room, from_sidebar: true) + } do %> + <% end %> <% end %> \ No newline at end of file