diff --git a/app/javascript/application.js b/app/javascript/application.js index bdafb95b09..c43a45eaa6 100644 --- a/app/javascript/application.js +++ b/app/javascript/application.js @@ -1,8 +1,8 @@ // Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails import "@hotwired/turbo-rails" +import "@hotwired/hotwire-native-bridge" import "initializers" import "controllers" import "lexxy" import "@rails/actiontext" - diff --git a/app/javascript/bridge/controllers/bridge/page_controller.js b/app/javascript/bridge/controllers/bridge/page_controller.js new file mode 100644 index 0000000000..d9fb53b1b7 --- /dev/null +++ b/app/javascript/bridge/controllers/bridge/page_controller.js @@ -0,0 +1,131 @@ +import { BridgeComponent } from "@hotwired/hotwire-native-bridge" +import { BridgeElement } from "@hotwired/hotwire-native-bridge" +import { viewport } from "bridge/helpers/viewport" +import { nextFrame } from "helpers/timing_helpers" + +export default class extends BridgeComponent { + static component = "page" + static targets = [ "header" ] + static values = { title: String } + + async connect() { + super.connect() + this.notifyBridgeOfPageChange() + await nextFrame() + this.startObserver() + window.addEventListener("resize", this.windowResized) + window.addEventListener("turbo:submit-start", this.submitStart) + window.addEventListener("turbo:submit-end", this.submitEnd) + } + + disconnect() { + super.disconnect() + this.stopObserver() + window.removeEventListener("resize", this.windowResized) + window.removeEventListener("turbo:submit-start", this.submitStart) + window.removeEventListener("turbo:submit-end", this.submitEnd) + } + + receive(message) { + switch (message.event) { + case "change": + this.updateHeaderVisibility(message.data) + break + case "set-text-size": + this.setTextSize(message.data) + break + } + } + + setTextSize(data) { + document.documentElement.dataset.textSize = data.textSize + } + + updateHeaderVisibility(data) { + if (!this.hasHeaderTarget) return + + const headerElement = new BridgeElement(this.headerTarget) + + if (data.displayOnPlatform) { + headerElement?.showOnPlatform() + } else { + headerElement?.hideOnPlatform() + } + } + + // Bridge + + notifyBridgeOfPageChange() { + let headerElement = null + const data = { + title: this.title, + url: window.location.href + } + + if (this.hasHeaderTarget) { + // Assume header visible by default until we get IntersectionObserver update + headerElement = new BridgeElement(this.headerTarget) + data.elementVisible = true + data.displayOnPlatform = headerElement.isDisplayedOnPlatform() + } + + this.send("change", data, message => this.receive(message)) + } + + notifyBridgeOfVisibilityChange(visible) { + this.send("visibility", { title: this.title, elementVisible: visible }) + } + + notifyBridgeOfSubmitStart() { + this.send("submitStart") + } + + notifyBridgeOfSubmitEnd() { + this.send("submitEnd") + } + + // Intersection Observer + + startObserver() { + if (!this.hasHeaderTarget) return + + this.observer = new IntersectionObserver(([ entry ]) => + this.notifyBridgeOfVisibilityChange(entry.isIntersecting), + { rootMargin: `-${this.topOffset}px 0px 0px 0px` } + ) + + this.observer.observe(this.headerTarget) + this.previousTopOffset = this.topOffset + } + + stopObserver() { + this.observer?.disconnect() + } + + updateObserverIfNeeded() { + if (this.topOffset === this.previousTopOffset) return + + this.stopObserver() + this.startObserver() + } + + windowResized = () => { + this.updateObserverIfNeeded() + } + + submitStart = () => { + this.notifyBridgeOfSubmitStart() + } + + submitEnd = () => { + this.notifyBridgeOfSubmitEnd() + } + + get title() { + return this.titleValue ? this.titleValue : document.title + } + + get topOffset() { + return viewport.top + } +} diff --git a/app/javascript/bridge/helpers/viewport.js b/app/javascript/bridge/helpers/viewport.js new file mode 100644 index 0000000000..559143607e --- /dev/null +++ b/app/javascript/bridge/helpers/viewport.js @@ -0,0 +1,24 @@ +let top = 0 +const viewportTarget = window.visualViewport || window + +export const viewport = { + get top() { + return top + }, + get height() { + return viewportTarget.height || window.innerHeight + } +} + +function update() { + requestAnimationFrame(() => { + const styles = getComputedStyle(document.documentElement) + const customInset = styles.getPropertyValue("--custom-safe-inset-top") + const fallbackInset = styles.getPropertyValue("--safe-area-inset-top") + const insetValue = (customInset || fallbackInset).trim() + top = parseInt(insetValue || "0", 10) || 0 + }) +} + +viewportTarget.addEventListener("resize", update) +update() diff --git a/app/javascript/bridge/initializers/bridge_element.js b/app/javascript/bridge/initializers/bridge_element.js new file mode 100644 index 0000000000..7e96bae1c1 --- /dev/null +++ b/app/javascript/bridge/initializers/bridge_element.js @@ -0,0 +1,13 @@ +import { BridgeElement } from "@hotwired/hotwire-native-bridge" + +BridgeElement.prototype.isDisplayedOnPlatform = function() { + return !this.hasClass("hide-on-native") +} + +BridgeElement.prototype.showOnPlatform = function() { + this.element.classList.remove("hide-on-native") +} + +BridgeElement.prototype.hideOnPlatform = function() { + this.element.classList.add("hide-on-native") +} diff --git a/app/javascript/bridge/initializers/index.js b/app/javascript/bridge/initializers/index.js new file mode 100644 index 0000000000..8e17fab0c0 --- /dev/null +++ b/app/javascript/bridge/initializers/index.js @@ -0,0 +1 @@ +import "bridge/initializers/bridge_element" diff --git a/app/javascript/initializers/index.js b/app/javascript/initializers/index.js index 10fb369755..d128addc96 100644 --- a/app/javascript/initializers/index.js +++ b/app/javascript/initializers/index.js @@ -1 +1,2 @@ import "initializers/current" +import "bridge/initializers" diff --git a/app/views/account/settings/show.html.erb b/app/views/account/settings/show.html.erb index fc5e799b38..799045c172 100644 --- a/app/views/account/settings/show.html.erb +++ b/app/views/account/settings/show.html.erb @@ -1,7 +1,7 @@ <% @page_title = "Account Settings" %> <% content_for :header do %> -

+

<%= @page_title %> <% unless Current.user.admin? %>
Only admins can change these settings
diff --git a/app/views/boards/columns/closeds/show.html.erb b/app/views/boards/columns/closeds/show.html.erb index 8a9d1f56cb..cfa56d721f 100644 --- a/app/views/boards/columns/closeds/show.html.erb +++ b/app/views/boards/columns/closeds/show.html.erb @@ -5,7 +5,7 @@ <%= link_back_to_board(@board) %> -

+

<%= @page_title %>

<% end %> diff --git a/app/views/boards/columns/not_nows/show.html.erb b/app/views/boards/columns/not_nows/show.html.erb index 88ea338685..3fb62213c5 100644 --- a/app/views/boards/columns/not_nows/show.html.erb +++ b/app/views/boards/columns/not_nows/show.html.erb @@ -5,7 +5,7 @@ <%= link_back_to_board(@board) %> -

+

<%= @page_title %>

<% end %> diff --git a/app/views/boards/columns/show.html.erb b/app/views/boards/columns/show.html.erb index 4fef4eec70..aa730b1798 100644 --- a/app/views/boards/columns/show.html.erb +++ b/app/views/boards/columns/show.html.erb @@ -5,7 +5,7 @@ <%= link_back_to_board(@column.board) %> -

+

<%= @page_title %>

<% end %> diff --git a/app/views/boards/columns/streams/show.html.erb b/app/views/boards/columns/streams/show.html.erb index 9f0477b390..c98bd28b16 100644 --- a/app/views/boards/columns/streams/show.html.erb +++ b/app/views/boards/columns/streams/show.html.erb @@ -5,7 +5,7 @@ <%= link_back_to_board(@board) %> -

+

<%= @page_title %>

<% end %> diff --git a/app/views/boards/edit.html.erb b/app/views/boards/edit.html.erb index 36beee3c86..92b74ebbb1 100644 --- a/app/views/boards/edit.html.erb +++ b/app/views/boards/edit.html.erb @@ -5,7 +5,7 @@ <%= link_back_to_board(@board) %> -

+

<%= @page_title %>
<% unless Current.user.can_administer_board?(@board) %>
Only admins can change these settings
diff --git a/app/views/boards/show.html.erb b/app/views/boards/show.html.erb index 9f0c2ebeef..c2a9320d08 100644 --- a/app/views/boards/show.html.erb +++ b/app/views/boards/show.html.erb @@ -8,7 +8,7 @@ <%= link_to_webhooks(@board) if Current.user.admin? %> -

+

<%= @board.name %>

diff --git a/app/views/cards/index.html.erb b/app/views/cards/index.html.erb index c11876e165..e59a91eb46 100644 --- a/app/views/cards/index.html.erb +++ b/app/views/cards/index.html.erb @@ -4,7 +4,7 @@ <%= render "cards/broadcasts", filter: @filter %> <% content_for :header do %> -

+

<%= @user_filtering.selected_boards_label %>

diff --git a/app/views/events/day_timeline/columns/show.html.erb b/app/views/events/day_timeline/columns/show.html.erb index dada2ebc4f..ae85d6acef 100644 --- a/app/views/events/day_timeline/columns/show.html.erb +++ b/app/views/events/day_timeline/columns/show.html.erb @@ -5,7 +5,7 @@ <%= back_link_to "Activity", root_path, "keydown.left@document->hotkey#click keydown.esc@document->hotkey#click" %> -

+

<%= @column.title %>

<% end %> diff --git a/app/views/events/index.html.erb b/app/views/events/index.html.erb index 1279e4dac1..c9b9d0e8c1 100644 --- a/app/views/events/index.html.erb +++ b/app/views/events/index.html.erb @@ -6,7 +6,7 @@ <% content_for :header do %> <%= render "events/index/add_card_button", user_filtering: @user_filtering %> -

+

<% if @user_filtering.boards.many? %> Activity <%= @user_filtering.filter.boards.any? ? "in" : "across" %> <% else %> diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 7211e718d9..2b6aa7370d 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -2,7 +2,7 @@ <%= render "layouts/shared/head" %> - + >