From dac9aa9d266871c3f39ef5cbb0f73ba65df7022b Mon Sep 17 00:00:00 2001 From: Jose Farias Date: Wed, 15 Jan 2025 17:22:25 -0600 Subject: [PATCH 01/15] Drag bubble divider --- app/assets/stylesheets/bubbles.css | 18 ++++- .../controllers/divider_controller.js | 72 +++++++++++++++++++ .../controllers/query_merger_controller.js | 10 --- app/views/bubbles/index.html.erb | 20 ++---- app/views/bubbles/list/_divider.html.erb | 13 ++++ .../bubbles/sidebar/_assignment.html.erb | 2 +- app/views/bubbles/sidebar/_tag.html.erb | 2 +- 7 files changed, 111 insertions(+), 26 deletions(-) create mode 100644 app/javascript/controllers/divider_controller.js delete mode 100644 app/javascript/controllers/query_merger_controller.js create mode 100644 app/views/bubbles/list/_divider.html.erb diff --git a/app/assets/stylesheets/bubbles.css b/app/assets/stylesheets/bubbles.css index 9b7f88d5e..b4b114e1f 100644 --- a/app/assets/stylesheets/bubbles.css +++ b/app/assets/stylesheets/bubbles.css @@ -397,13 +397,29 @@ --divider-color: var(--color-subtle-dark); font-size: 1rem; + cursor: grab; + visibility: hidden; +} + +.bubbles-list__divider--positioned { + visibility: visible; +} + +.divider-drag-image { + background-color: var(--color-link); + border-radius: 1rem; + color: var(--color-ink-reversed); + inset: auto 100% 100% auto; + line-height: 2rem; + padding: 0 1rem; + position: fixed; + text-wrap: nowrap; } .btn--reversed.bubbles-list__handle { --btn-background: var(--divider-color); font-size: 0.9rem; - cursor: grab; } .bubbles-list__count { diff --git a/app/javascript/controllers/divider_controller.js b/app/javascript/controllers/divider_controller.js new file mode 100644 index 000000000..2840a6b72 --- /dev/null +++ b/app/javascript/controllers/divider_controller.js @@ -0,0 +1,72 @@ +import { Controller } from "@hotwired/stimulus" +import { get, patch } from "@rails/request.js" + +const MOVE_ITEM_DATA_TYPE = "x-fizzy/move" +const DIVIDER_ITEM_NODE_NAME = "LI" + +export default class extends Controller { + static targets = [ "divider", "dragImage", "count" ] + static classes = [ "positioned" ] + static values = { startCount: Number, maxCount: Number } + + connect() { + this.install() + } + + install() { + this.#positionDividerElement(this.startCountValue) + } + + configureDrag(event) { + if (event.target == this.dividerTarget) { + event.dataTransfer.dropEffect = "move" + event.dataTransfer.setData(MOVE_ITEM_DATA_TYPE, event.target) + event.dataTransfer.setDragImage(this.dragImageTarget, 0, 0) + } + } + + moveDivider(event) { + if (event.target.nodeName != DIVIDER_ITEM_NODE_NAME) return + + const targetIndex = this.#items.indexOf(event.target) + + if (targetIndex > this.maxCountValue) return + + if (this.#dividerIndex < targetIndex) { + event.target.after(this.dividerTarget) + } else { + event.target.before(this.dividerTarget) + } + + this.countTarget.textContent = targetIndex + } + + persist() { + // TODO + } + + acceptDrop(event) { + const isDroppable = event.dataTransfer.types.includes(MOVE_ITEM_DATA_TYPE) + if (isDroppable) event.preventDefault() + } + + #positionDividerElement(index) { + if (index == 0) { + this.#items[0].before(this.dividerTarget) + } else if (index <= this.#items.length - 1) { + this.#items[index - 1].after(this.dividerTarget) + } else { + this.#items[this.#items.length - 1].after(this.dividerTarget) + } + + this.dividerTarget.classList.add(this.positionedClass) + } + + get #items() { + return Array.from(this.element.children) + } + + get #dividerIndex() { + return this.#items.indexOf(this.dividerTarget) + } +} diff --git a/app/javascript/controllers/query_merger_controller.js b/app/javascript/controllers/query_merger_controller.js deleted file mode 100644 index 3681578e3..000000000 --- a/app/javascript/controllers/query_merger_controller.js +++ /dev/null @@ -1,10 +0,0 @@ -import { Controller } from "@hotwired/stimulus" - -// FIXME: Can we do this without a controller? https://github.com/basecamp/fizzy/pull/130#discussion_r1833094616 -export default class extends Controller { - merge({ params: { key, value } }) { - const url = new URL(window.location.href) - url.searchParams.set(key, value) - Turbo.visit(url) - } -} diff --git a/app/views/bubbles/index.html.erb b/app/views/bubbles/index.html.erb index 814530d10..4308ddf2c 100644 --- a/app/views/bubbles/index.html.erb +++ b/app/views/bubbles/index.html.erb @@ -34,20 +34,14 @@
-
diff --git a/app/views/bubbles/list/_divider.html.erb b/app/views/bubbles/list/_divider.html.erb new file mode 100644 index 000000000..633408719 --- /dev/null +++ b/app/views/bubbles/list/_divider.html.erb @@ -0,0 +1,13 @@ +
  • +
    Drag up or down
    + +
    + + Top 10 + +
    + +
  • diff --git a/app/views/bubbles/sidebar/_assignment.html.erb b/app/views/bubbles/sidebar/_assignment.html.erb index fa858aa99..ba759ebdf 100644 --- a/app/views/bubbles/sidebar/_assignment.html.erb +++ b/app/views/bubbles/sidebar/_assignment.html.erb @@ -1,4 +1,4 @@ -
    +

    - Top 10 + Top
    From 3ab6ee84d0d104599e7c3e62c0f0563e55c171dd Mon Sep 17 00:00:00 2001 From: Jose Farias Date: Wed, 15 Jan 2025 20:13:18 -0600 Subject: [PATCH 03/15] DividerElement -> Divider --- app/javascript/controllers/divider_controller.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/javascript/controllers/divider_controller.js b/app/javascript/controllers/divider_controller.js index d3c35f389..1612d2fd6 100644 --- a/app/javascript/controllers/divider_controller.js +++ b/app/javascript/controllers/divider_controller.js @@ -13,7 +13,7 @@ export default class extends Controller { } install() { - this.#positionDividerElementBefore(this.startCountValue) + this.#positionDividerBefore(this.startCountValue) this.dividerTarget.classList.add(this.positionedClass) } @@ -32,9 +32,9 @@ export default class extends Controller { if (targetIndex != this.#dividerIndex && targetIndex <= this.maxCountValue) { if (this.#dividerIndex < targetIndex) { - this.#positionDividerElementAfter(targetIndex) + this.#positionDividerAfter(targetIndex) } else { - this.#positionDividerElementBefore(targetIndex) + this.#positionDividerBefore(targetIndex) } } } @@ -48,13 +48,13 @@ export default class extends Controller { if (isDroppable) event.preventDefault() } - #positionDividerElementAfter(index) { + #positionDividerAfter(index) { const position = Math.min(index, this.#items.length - 1, this.maxCountValue) this.#items[position].after(this.dividerTarget) this.countTarget.textContent = position } - #positionDividerElementBefore(index) { + #positionDividerBefore(index) { const position = Math.max(index, 1) this.#items[position].before(this.dividerTarget) this.countTarget.textContent = position From 40085b16d6aef6da477d0dced2eca968ccfde7cc Mon Sep 17 00:00:00 2001 From: Jose Farias Date: Wed, 15 Jan 2025 20:14:30 -0600 Subject: [PATCH 04/15] Before comes before After --- app/javascript/controllers/divider_controller.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/javascript/controllers/divider_controller.js b/app/javascript/controllers/divider_controller.js index 1612d2fd6..1d4436643 100644 --- a/app/javascript/controllers/divider_controller.js +++ b/app/javascript/controllers/divider_controller.js @@ -48,18 +48,18 @@ export default class extends Controller { if (isDroppable) event.preventDefault() } - #positionDividerAfter(index) { - const position = Math.min(index, this.#items.length - 1, this.maxCountValue) - this.#items[position].after(this.dividerTarget) - this.countTarget.textContent = position - } - #positionDividerBefore(index) { const position = Math.max(index, 1) this.#items[position].before(this.dividerTarget) this.countTarget.textContent = position } + #positionDividerAfter(index) { + const position = Math.min(index, this.#items.length - 1, this.maxCountValue) + this.#items[position].after(this.dividerTarget) + this.countTarget.textContent = position + } + get #items() { return Array.from(this.element.children) } From 775e29fb8ee78c48455a9902f93460bdecf370d5 Mon Sep 17 00:00:00 2001 From: Jose Farias Date: Wed, 15 Jan 2025 20:26:56 -0600 Subject: [PATCH 05/15] Pull out moveDividerTo --- .../controllers/divider_controller.js | 26 ++++++++++--------- app/views/bubbles/list/_divider.html.erb | 2 +- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/app/javascript/controllers/divider_controller.js b/app/javascript/controllers/divider_controller.js index 1d4436643..e93b98a58 100644 --- a/app/javascript/controllers/divider_controller.js +++ b/app/javascript/controllers/divider_controller.js @@ -13,7 +13,7 @@ export default class extends Controller { } install() { - this.#positionDividerBefore(this.startCountValue) + this.#moveDividerTo(this.startCountValue) this.dividerTarget.classList.add(this.positionedClass) } @@ -25,17 +25,9 @@ export default class extends Controller { } } - moveDivider(event) { - if (event.target.nodeName != DIVIDER_ITEM_NODE_NAME) return - - const targetIndex = this.#items.indexOf(event.target) - - if (targetIndex != this.#dividerIndex && targetIndex <= this.maxCountValue) { - if (this.#dividerIndex < targetIndex) { - this.#positionDividerAfter(targetIndex) - } else { - this.#positionDividerBefore(targetIndex) - } + moveDivider({ target }) { + if (target.nodeName == DIVIDER_ITEM_NODE_NAME) { + this.#moveDividerTo(this.#items.indexOf(target)) } } @@ -48,6 +40,16 @@ export default class extends Controller { if (isDroppable) event.preventDefault() } + #moveDividerTo(index) { + if (index <= this.maxCountValue) { + if (this.#dividerIndex < index) { + this.#positionDividerAfter(index) + } else if (this.#dividerIndex > index) { + this.#positionDividerBefore(index) + } + } + } + #positionDividerBefore(index) { const position = Math.max(index, 1) this.#items[position].before(this.dividerTarget) diff --git a/app/views/bubbles/list/_divider.html.erb b/app/views/bubbles/list/_divider.html.erb index f11a6b033..633408719 100644 --- a/app/views/bubbles/list/_divider.html.erb +++ b/app/views/bubbles/list/_divider.html.erb @@ -6,7 +6,7 @@
    - Top + Top 10
    From 622e340e83135a20ae72e58d8e47da5e7e13c66f Mon Sep 17 00:00:00 2001 From: Jose Farias Date: Wed, 15 Jan 2025 20:31:09 -0600 Subject: [PATCH 06/15] positionedClass -> installedClass --- app/assets/stylesheets/bubbles.css | 2 +- app/javascript/controllers/divider_controller.js | 15 ++++++++------- app/views/bubbles/index.html.erb | 2 +- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/app/assets/stylesheets/bubbles.css b/app/assets/stylesheets/bubbles.css index b4b114e1f..fcd1e2420 100644 --- a/app/assets/stylesheets/bubbles.css +++ b/app/assets/stylesheets/bubbles.css @@ -401,7 +401,7 @@ visibility: hidden; } -.bubbles-list__divider--positioned { +.bubbles-list__divider--installed { visibility: visible; } diff --git a/app/javascript/controllers/divider_controller.js b/app/javascript/controllers/divider_controller.js index e93b98a58..847563d8f 100644 --- a/app/javascript/controllers/divider_controller.js +++ b/app/javascript/controllers/divider_controller.js @@ -5,7 +5,7 @@ const DIVIDER_ITEM_NODE_NAME = "LI" export default class extends Controller { static targets = [ "divider", "dragImage", "count" ] - static classes = [ "positioned" ] + static classes = [ "installed" ] static values = { startCount: Number, maxCount: Number } connect() { @@ -14,7 +14,7 @@ export default class extends Controller { install() { this.#moveDividerTo(this.startCountValue) - this.dividerTarget.classList.add(this.positionedClass) + this.dividerTarget.classList.add(this.installedClass) } configureDrag(event) { @@ -25,6 +25,12 @@ export default class extends Controller { } } + acceptDrop(event) { + if (event.dataTransfer.types.includes(MOVE_ITEM_DATA_TYPE)) { + event.preventDefault() + } + } + moveDivider({ target }) { if (target.nodeName == DIVIDER_ITEM_NODE_NAME) { this.#moveDividerTo(this.#items.indexOf(target)) @@ -35,11 +41,6 @@ export default class extends Controller { // TODO } - acceptDrop(event) { - const isDroppable = event.dataTransfer.types.includes(MOVE_ITEM_DATA_TYPE) - if (isDroppable) event.preventDefault() - } - #moveDividerTo(index) { if (index <= this.maxCountValue) { if (this.#dividerIndex < index) { diff --git a/app/views/bubbles/index.html.erb b/app/views/bubbles/index.html.erb index 4308ddf2c..f7255893a 100644 --- a/app/views/bubbles/index.html.erb +++ b/app/views/bubbles/index.html.erb @@ -38,7 +38,7 @@ data-controller="divider" data-divider-start-count-value="10" data-divider-max-count-value="10" - data-divider-positioned-class="bubbles-list__divider--positioned" + data-divider-installed-class="bubbles-list__divider--installed" data-action="turbo:morph@document->divider#install dragstart->divider#configureDrag dragenter->divider#acceptDrop dragover->divider#acceptDrop dragover->divider#moveDivider drop->divider#persist"> <%= render partial: "bubbles/list/bubble", collection: @bubbles, cached: true %> <%= render "bubbles/list/divider", filter: @filter %> From 6f8c92989827994c2fceb0eea1af63b0c08513fb Mon Sep 17 00:00:00 2001 From: Kevin McConnell Date: Thu, 16 Jan 2025 17:46:40 +0000 Subject: [PATCH 07/15] Use cache-friendly "time ago" Also introduces a local time helper that we can use to format dates and times. --- app/helpers/time_helper.rb | 5 ++ .../controllers/local_time_controller.js | 62 +++++++++++++++++++ app/views/layouts/application.html.erb | 2 +- .../notifications/_notification.html.erb | 2 +- 4 files changed, 69 insertions(+), 2 deletions(-) create mode 100644 app/helpers/time_helper.rb create mode 100644 app/javascript/controllers/local_time_controller.js diff --git a/app/helpers/time_helper.rb b/app/helpers/time_helper.rb new file mode 100644 index 000000000..c3fd4f066 --- /dev/null +++ b/app/helpers/time_helper.rb @@ -0,0 +1,5 @@ +module TimeHelper + def local_datetime_tag(datetime, style: :time, **attributes) + tag.time **attributes, datetime: datetime.iso8601, data: { local_time_target: style } + end +end diff --git a/app/javascript/controllers/local_time_controller.js b/app/javascript/controllers/local_time_controller.js new file mode 100644 index 000000000..61a750dbf --- /dev/null +++ b/app/javascript/controllers/local_time_controller.js @@ -0,0 +1,62 @@ +import { Controller } from "@hotwired/stimulus" + +export default class extends Controller { + static targets = ["time", "date", "datetime", "ago"] + + initialize() { + this.timeFormatter = new Intl.DateTimeFormat(undefined, { timeStyle: "short" }) + this.dateFormatter = new Intl.DateTimeFormat(undefined, { dateStyle: "long" }) + this.dateTimeFormatter = new Intl.DateTimeFormat(undefined, { timeStyle: "short", dateStyle: "short" }) + this.agoFormatter = new AgoFormatter() + } + + timeTargetConnected(target) { + this.#formatTime(this.timeFormatter, target) + } + + dateTargetConnected(target) { + this.#formatTime(this.dateFormatter, target) + } + + datetimeTargetConnected(target) { + this.#formatTime(this.dateTimeFormatter, target) + } + + agoTargetConnected(target) { + this.#formatTime(this.agoFormatter, target) + } + + #formatTime(formatter, target) { + const dt = new Date(target.getAttribute("datetime")) + target.textContent = formatter.format(dt) + target.title = this.dateTimeFormatter.format(dt) + } +} + +class AgoFormatter { + format(dt) { + const now = new Date() + const seconds = (now - dt) / 1000 + const minutes = seconds / 60 + const hours = minutes / 60 + const days = hours / 24 + const weeks = days / 7 + const months = days / (365 / 12) + const years = days / 365 + + if (years >= 1) return this.#pluralize("year", years) + if (months >= 1) return this.#pluralize("month", months) + if (weeks >= 1) return this.#pluralize("week", weeks) + if (days >= 1) return this.#pluralize("day", days) + if (hours >= 1) return this.#pluralize("hour", hours) + if (minutes >= 1) return this.#pluralize("minute", minutes) + + return "Less than a minute ago" + } + + #pluralize(word, quantity) { + quantity = Math.round(quantity) + const suffix = (quantity === 1) ? "" : "s" + return `${quantity} ${word}${suffix} ago` + } +} diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 2121cf22a..251af449a 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -19,7 +19,7 @@ <%= yield :head %> - +
    <% end %> From af0783d8ffc946212e3efd352cf864e0f1b3aacb Mon Sep 17 00:00:00 2001 From: Jose Farias Date: Thu, 16 Jan 2025 13:54:42 -0600 Subject: [PATCH 08/15] Fix divider drag jankiness --- app/assets/stylesheets/bubbles.css | 28 +++++++++++-------- .../controllers/divider_controller.js | 21 ++++++++++---- app/views/bubbles/index.html.erb | 3 +- 3 files changed, 34 insertions(+), 18 deletions(-) diff --git a/app/assets/stylesheets/bubbles.css b/app/assets/stylesheets/bubbles.css index fcd1e2420..0440a40d7 100644 --- a/app/assets/stylesheets/bubbles.css +++ b/app/assets/stylesheets/bubbles.css @@ -366,18 +366,24 @@ text-decoration: none; } - li { - border-radius: 0.6em; - list-style: none; - padding: 0.5em var(--inline-space); - position: relative; - transition: background-color 200ms ease-out; + ul { + li { + border-radius: 0.6em; + list-style: none; + padding: 0.5em var(--inline-space); + position: relative; + transition: background-color 200ms ease-out; + } - @media (hover: hover) { - &:hover { - background-color: color(from var(--bubble-color) srgb r g b / 0.15); - border: 0; - border-radius: 0.6em; + &:not(.dragging) { + li { + @media (hover: hover) { + &:hover { + background-color: color(from var(--bubble-color) srgb r g b / 0.15); + border: 0; + border-radius: 0.6em; + } + } } } } diff --git a/app/javascript/controllers/divider_controller.js b/app/javascript/controllers/divider_controller.js index 847563d8f..0047d252c 100644 --- a/app/javascript/controllers/divider_controller.js +++ b/app/javascript/controllers/divider_controller.js @@ -5,7 +5,7 @@ const DIVIDER_ITEM_NODE_NAME = "LI" export default class extends Controller { static targets = [ "divider", "dragImage", "count" ] - static classes = [ "installed" ] + static classes = [ "installed", "dragging" ] static values = { startCount: Number, maxCount: Number } connect() { @@ -22,6 +22,7 @@ export default class extends Controller { event.dataTransfer.dropEffect = "move" event.dataTransfer.setData(MOVE_ITEM_DATA_TYPE, event.target) event.dataTransfer.setDragImage(this.dragImageTarget, 0, 0) + this.element.classList.add(this.draggingClass) } } @@ -31,14 +32,22 @@ export default class extends Controller { } } - moveDivider({ target }) { - if (target.nodeName == DIVIDER_ITEM_NODE_NAME) { - this.#moveDividerTo(this.#items.indexOf(target)) + moveDivider(event) { + if (event.target.nodeName == DIVIDER_ITEM_NODE_NAME) { + const rect = this.dividerTarget.getBoundingClientRect() + const distanceToTop = Math.abs(event.clientY - rect.top) + const distanceToBottom = Math.abs(event.clientY - (rect.top + rect.height)) + const distanceToNearestEdge = Math.min(distanceToTop, distanceToBottom) + const distancePercentage = (distanceToNearestEdge / rect.height) * 100 + + if (distancePercentage > 50) { + this.#moveDividerTo(this.#items.indexOf(event.target)) + } } } - persist() { - // TODO + drop() { + this.element.classList.remove(this.draggingClass) } #moveDividerTo(index) { diff --git a/app/views/bubbles/index.html.erb b/app/views/bubbles/index.html.erb index f7255893a..5dff555d4 100644 --- a/app/views/bubbles/index.html.erb +++ b/app/views/bubbles/index.html.erb @@ -39,7 +39,8 @@ data-divider-start-count-value="10" data-divider-max-count-value="10" data-divider-installed-class="bubbles-list__divider--installed" - data-action="turbo:morph@document->divider#install dragstart->divider#configureDrag dragenter->divider#acceptDrop dragover->divider#acceptDrop dragover->divider#moveDivider drop->divider#persist"> + data-divider-dragging-class="dragging" + data-action="turbo:morph@document->divider#install dragstart->divider#configureDrag dragenter->divider#acceptDrop dragover->divider#acceptDrop dragover->divider#moveDivider drop->divider#drop"> <%= render partial: "bubbles/list/bubble", collection: @bubbles, cached: true %> <%= render "bubbles/list/divider", filter: @filter %> From dc6da76b38e76e7255ea18c4c5a0652d56aafe50 Mon Sep 17 00:00:00 2001 From: Jose Farias Date: Thu, 16 Jan 2025 14:01:25 -0600 Subject: [PATCH 09/15] Simplify overlap logic --- app/javascript/controllers/divider_controller.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/app/javascript/controllers/divider_controller.js b/app/javascript/controllers/divider_controller.js index 0047d252c..2cec61d8c 100644 --- a/app/javascript/controllers/divider_controller.js +++ b/app/javascript/controllers/divider_controller.js @@ -35,12 +35,9 @@ export default class extends Controller { moveDivider(event) { if (event.target.nodeName == DIVIDER_ITEM_NODE_NAME) { const rect = this.dividerTarget.getBoundingClientRect() - const distanceToTop = Math.abs(event.clientY - rect.top) - const distanceToBottom = Math.abs(event.clientY - (rect.top + rect.height)) - const distanceToNearestEdge = Math.min(distanceToTop, distanceToBottom) - const distancePercentage = (distanceToNearestEdge / rect.height) * 100 + const overlap = Math.abs((event.clientY - rect.top) / rect.height) - if (distancePercentage > 50) { + if (overlap > 0.5) { this.#moveDividerTo(this.#items.indexOf(event.target)) } } From 8877d2e66df96b61b741cd0dd6de1762521d9990 Mon Sep 17 00:00:00 2001 From: Jose Farias Date: Thu, 16 Jan 2025 15:09:46 -0600 Subject: [PATCH 10/15] Use distance to nearest edge This feels the smoothest --- app/javascript/controllers/divider_controller.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/javascript/controllers/divider_controller.js b/app/javascript/controllers/divider_controller.js index 2cec61d8c..553065c30 100644 --- a/app/javascript/controllers/divider_controller.js +++ b/app/javascript/controllers/divider_controller.js @@ -2,6 +2,7 @@ import { Controller } from "@hotwired/stimulus" const MOVE_ITEM_DATA_TYPE = "x-fizzy/move" const DIVIDER_ITEM_NODE_NAME = "LI" +const OVERLAP_THRESHOLD = 0.25 export default class extends Controller { static targets = [ "divider", "dragImage", "count" ] @@ -35,9 +36,12 @@ export default class extends Controller { moveDivider(event) { if (event.target.nodeName == DIVIDER_ITEM_NODE_NAME) { const rect = this.dividerTarget.getBoundingClientRect() - const overlap = Math.abs((event.clientY - rect.top) / rect.height) + const distanceToTop = Math.abs(event.clientY - rect.top) + const distanceToBottom = Math.abs(event.clientY - (rect.top + rect.height)) + const distanceToNearestEdge = Math.min(distanceToTop, distanceToBottom) + const overlap = (distanceToNearestEdge / rect.height) - if (overlap > 0.5) { + if (overlap > OVERLAP_THRESHOLD) { this.#moveDividerTo(this.#items.indexOf(event.target)) } } From fb27cee9b0d410d3e654617d197acbca120dde18 Mon Sep 17 00:00:00 2001 From: Jose Farias Date: Thu, 16 Jan 2025 15:11:44 -0600 Subject: [PATCH 11/15] Unnecessary parens --- app/javascript/controllers/divider_controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/javascript/controllers/divider_controller.js b/app/javascript/controllers/divider_controller.js index 553065c30..058bcb6a0 100644 --- a/app/javascript/controllers/divider_controller.js +++ b/app/javascript/controllers/divider_controller.js @@ -39,7 +39,7 @@ export default class extends Controller { const distanceToTop = Math.abs(event.clientY - rect.top) const distanceToBottom = Math.abs(event.clientY - (rect.top + rect.height)) const distanceToNearestEdge = Math.min(distanceToTop, distanceToBottom) - const overlap = (distanceToNearestEdge / rect.height) + const overlap = distanceToNearestEdge / rect.height if (overlap > OVERLAP_THRESHOLD) { this.#moveDividerTo(this.#items.indexOf(event.target)) From 340ff19a8cb70df588a3dae2c38b7f911d80ce4b Mon Sep 17 00:00:00 2001 From: Jason Zimdars Date: Thu, 16 Jan 2025 14:26:50 -0600 Subject: [PATCH 12/15] Drafts should always use the dashed outline wherever they're displayed --- app/assets/stylesheets/bubbles.css | 23 +++++++++++++++++------ app/views/bubbles/list/_bubble.html.erb | 2 +- app/views/buckets/_bucket.html.erb | 2 +- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/app/assets/stylesheets/bubbles.css b/app/assets/stylesheets/bubbles.css index 0440a40d7..864f0d34d 100644 --- a/app/assets/stylesheets/bubbles.css +++ b/app/assets/stylesheets/bubbles.css @@ -439,6 +439,10 @@ flex-grow: 1; white-space: nowrap; + .drafted & { + color: color(from var(--color-ink) srgb r g b / 0.5); + } + &::after { border-block-end: 1px dotted var(--color-subtle-dark); content: ""; @@ -448,9 +452,12 @@ } .bubble__shape { - background-color: var(--bubble-color); + background-color: var(--bubble-background-color, var(--bubble-color)); block-size: var(--bubble-size, 1.2em); + border-color: var(--bubble-border-color, var(--bubble-color)); border-radius: var(--bubble-shape); + border-style: var(--bubble-border-style, solid); + border-width: var(--bubble-border-width, 0); inline-size: var(--bubble-size, 1.2em); pointer-events: none; position: relative; @@ -460,13 +467,17 @@ .bubble & { --bubble-size: 100%; - - background-color: color(from var(--bubble-color) srgb r g b / 0.15); - border: var(--bubble-border-width) solid var(--bubble-color); + --bubble-background-color: color(from var(--bubble-color) srgb r g b / 0.15); } - .bubble.drafted & { - border-style: dashed; + .drafted & { + --bubble-border-style: dashed; + + .bubbles-list & { + --bubble-background-color: color(from var(--bubble-color) srgb r g b / 0.15); + --bubble-border-color: var(--bubble-color); + --bubble-border-width: 0.15rem; + } } .bubble:has(.bubble__link) & { diff --git a/app/views/bubbles/list/_bubble.html.erb b/app/views/bubbles/list/_bubble.html.erb index 68588b64b..8836fd8f2 100644 --- a/app/views/bubbles/list/_bubble.html.erb +++ b/app/views/bubbles/list/_bubble.html.erb @@ -1,4 +1,4 @@ -
  • " style="--bubble-color: <%= bubble.color %>; <%= bubble_rotation(bubble) %>" data-controller="animation" data-animation-play-class="bubble--wobble" data-animation-play-on-load-value="true" data-action="mouseover->animation#play">
    diff --git a/app/views/buckets/_bucket.html.erb b/app/views/buckets/_bucket.html.erb index e0893f351..3963d5987 100644 --- a/app/views/buckets/_bucket.html.erb +++ b/app/views/buckets/_bucket.html.erb @@ -3,7 +3,7 @@ <%= link_to bubbles_path(bucket_ids: [ bucket ]), class: "border border-radius margin-block-end-half windshield__container flex justify-center align-center position-relative" do %>
    <% bucket.bubbles.ordered_by_activity.limit(10).each do |bubble| %> -
    +
    " style="--bubble-color: <%= bubble.color %>; <%= bubble_rotation(bubble) %> <%= bubble_size(bubble) %>">
    <% end %> From 29b221f65c54f893dd89ac380a2442915fc5fb17 Mon Sep 17 00:00:00 2001 From: Jason Zimdars Date: Thu, 16 Jan 2025 19:21:27 -0600 Subject: [PATCH 13/15] Banner for draft state --- app/assets/stylesheets/utilities.css | 6 ++++-- app/views/bubbles/_publish.html.erb | 3 +-- app/views/bubbles/show.html.erb | 12 +++++++++--- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/app/assets/stylesheets/utilities.css b/app/assets/stylesheets/utilities.css index 761a24b5d..a512aca62 100644 --- a/app/assets/stylesheets/utilities.css +++ b/app/assets/stylesheets/utilities.css @@ -168,8 +168,10 @@ .translucent { opacity: var(--opacity, 0.5); } /* Borders */ -.border { border: var(--border-size, 1px) solid var(--border-color, var(--color-subtle)); } -.border-top { border-top: var(--border-size, 1px) solid var(--border-color, var(--color-subtle)); } +.border { border: var(--border-size, 1px) var(--border-style, solid) var(--border-color, var(--color-subtle)); } +.border-block { border-block: var(--border-size, 1px) var(--border-style, solid) var(--border-color, var(--color-subtle)); } +.border-bottom { border-block-end: var(--border-size, 1px) var(--border-style, solid) var(--border-color, var(--color-subtle)); } +.border-top { border-block-start: var(--border-size, 1px) var(--border-style, solid) var(--border-color, var(--color-subtle)); } .borderless { border: 0; } /* Border radius */ diff --git a/app/views/bubbles/_publish.html.erb b/app/views/bubbles/_publish.html.erb index 0f7af76c3..1079e1b0b 100644 --- a/app/views/bubbles/_publish.html.erb +++ b/app/views/bubbles/_publish.html.erb @@ -1,4 +1,3 @@ -<%= button_to bucket_bubble_publish_path(bubble.bucket, bubble), class: "btn btn--positive full-width justify-start borderless" do %> - <%= image_tag "alert.svg", aria: { hidden: true }, size: 24 %> +<%= button_to bucket_bubble_publish_path(bubble.bucket, bubble), class: "btn txt-small btn--link" do %> Publish <% end %> diff --git a/app/views/bubbles/show.html.erb b/app/views/bubbles/show.html.erb index 2ce7194fe..cc98b66fb 100644 --- a/app/views/bubbles/show.html.erb +++ b/app/views/bubbles/show.html.erb @@ -1,6 +1,14 @@ <% @page_title = @bubble.title %> <% content_for :header do %> + <% if @bubble.drafted? %> +
    + This a draft, it’s only visible to you. + <%= render "bubbles/publish", bubble: @bubble %> +
    + <% end %> +