Skip to content

Commit

Permalink
fix: generalize tooltip ui (#233)
Browse files Browse the repository at this point in the history
  • Loading branch information
cdrani authored Jun 4, 2024
1 parent 6b784e5 commit 67b0b43
Show file tree
Hide file tree
Showing 9 changed files with 122 additions and 47 deletions.
2 changes: 1 addition & 1 deletion src/components/icons/icon.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export const SNIP_ICON = {

export const SKIP_ICON = {
role: 'skip',
ariaLabel: 'Skip Song'
ariaLabel: 'Block Track'
}

export const NOW_PLAYING_SKIP_ICON = {
Expand Down
4 changes: 4 additions & 0 deletions src/models/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import TrackList from './tracklist/track-list.js'
import NowPlayingIcons from './now-playing-icons.js'

import Chorus from './chorus.js'
import ToolTip from './tooltip.js'
import QueueObserver from '../observers/queue.js'
import SongTracker from '../observers/song-tracker.js'
import TrackListObserver from '../observers/track-list.js'
Expand All @@ -23,6 +24,7 @@ export default class App {
}

#init() {
this._toolTip = new ToolTip()
this._songTracker = new SongTracker()
this._chorus = new Chorus(this._songTracker)
this._snip = new CurrentSnip(this._songTracker)
Expand Down Expand Up @@ -52,6 +54,7 @@ export default class App {
this._active = false
this._video.reset()

this._toolTip.removeUI()
this._nowPlayingIcons.clearIcons()

this._queueObserver.disconnect()
Expand All @@ -68,6 +71,7 @@ export default class App {
this._active = true
this._chorus.init()

this._toolTip.placeUI()
this._nowPlayingIcons.placeIcons()

this._queueObserver.observe()
Expand Down
24 changes: 18 additions & 6 deletions src/models/heart-icon.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { store } from '../stores/data.js'
import Dispatcher from '../events/dispatcher.js'
import { currentData } from '../data/current.js'
import { createIcon, HEART_ICON } from '../components/icons/icon.js'
import { updateToolTip } from '../utils/tooltip.js'

export default class HeartIcon {
constructor() {
Expand All @@ -13,7 +14,6 @@ export default class HeartIcon {

init() {
this.#placeIcon()
this.#setupListener()
}

removeIcon() {
Expand All @@ -22,10 +22,21 @@ export default class HeartIcon {
}

#placeIcon() {
const heartButton = parseNodeString(this.#createHeartIcon)
const refNode = this.#nowPlayingButton
refNode.parentElement.insertBefore(heartButton, refNode)
this.#toggleNowPlayingButton(false)
this._interval = setInterval(() => {
if (!this._interval) return

const refNode = document.getElementById('chorus')
if (!refNode) return

const heartButton = parseNodeString(this.#createHeartIcon)
const settingsButton = document.getElementById('chorus-icon')
refNode.insertBefore(heartButton, settingsButton)
this.#toggleNowPlayingButton(false)
this.#setupListener()

clearInterval(this._interval)
this._interval = null
}, 25)
}

#toggleNowPlayingButton(show) {
Expand All @@ -44,7 +55,7 @@ export default class HeartIcon {
}

#setupListener() {
this.#heartIcon?.addEventListener('click', async () => this.#handleClick())
this.#heartIcon?.addEventListener('click', async () => await this.#handleClick())
}

async #dispatchIsInCollection(ids) {
Expand Down Expand Up @@ -192,5 +203,6 @@ export default class HeartIcon {
#updateIconLabel(highlight) {
const text = `${highlight ? 'Remove from' : 'Save to'} Liked`
this.#heartIcon?.setAttribute('aria-label', text)
updateToolTip(this.#heartIcon)
}
}
74 changes: 74 additions & 0 deletions src/models/tooltip.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { parseNodeString } from '../utils/parser.js'
import { updateToolTip } from '../utils/tooltip.js'

export default class ToolTip {
placeUI() {
this.#setupToolTip()
}

get #toolTip() {
return document.getElementById('tooltip')
}

#setupToolTip() {
if (this.#toolTip) return

const toolTipEl = `<div id="tooltip" class="tooltip" />`
const toolTip = parseNodeString(toolTipEl)
document.body.appendChild(toolTip)

this.#setupNowPlayingListeners()
}

setupTrackListListeners(row) {
const buttons = row.querySelectorAll('button[role]')
buttons.forEach((button) => {
button.addEventListener('mouseenter', this.#showToolTip)
button.addEventListener('mouseleave', this.#hideToolTip)
})
}

#setupNowPlayingListeners() {
this._interval = setInterval(() => {
if (!this._interval) return

const chorusButtons = document.querySelectorAll('#chorus > button[role]')
const generalControls = document.querySelectorAll(
'[data-testid="general-controls"] > div > button[id]'
)
if (chorusButtons.length !== 3 || generalControls.length !== 3) return
;[...chorusButtons, ...generalControls].forEach((button) => {
button.addEventListener('mouseenter', this.#showToolTip)
button.addEventListener('mouseleave', this.#hideToolTip)
})

clearInterval(this._interval)
this._interval = null
}, 25)
}

#isChorusUI(target) {
const role = target.getAttribute('role')
if (!role) return false

return ['heart', 'settings', 'skip', 'ff', 'rw', 'loop'].includes(role)
}

#showToolTip = (event) => {
if (!this.#isChorusUI(event.target)) return

this.#toolTip.style.display = 'inline-block'
updateToolTip(event.target)
}

#hideToolTip = () => {
if (!this.#toolTip) return
this.#toolTip.style.display = 'none'
}

removeUI() {
this.#toolTip?.removeEventListener('mouseenter', this.#showToolTip)
this.#toolTip?.removeEventListener('mouseleave', this.#hideToolTip)
this.#toolTip?.remove()
}
}
2 changes: 2 additions & 0 deletions src/models/tracklist/heart-icon.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { getTrackId, trackSongInfo } from '../../utils/song.js'
import Dispatcher from '../../events/dispatcher.js'
import { currentData } from '../../data/current.js'
import { highlightIconTimer } from '../../utils/highlight.js'
import { updateToolTip } from '../../utils/tooltip.js'

export default class HeartIcon extends TrackListIcon {
constructor(store) {
Expand Down Expand Up @@ -50,6 +51,7 @@ export default class HeartIcon extends TrackListIcon {

const icon = row.querySelector(this._selector)
this.animate(icon, saved)
updateToolTip(icon)

const { id: songId } = trackSongInfo(row)
await this.#updateCurrentTrack({ songId, highlight: saved })
Expand Down
5 changes: 5 additions & 0 deletions src/models/tracklist/track-list.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ import SkipIcon from './skip-icon.js'
import SnipIcon from './snip-icon.js'
import HeartIcon from './heart-icon.js'

import ToolTip from '../tooltip.js'
import Chorus from '../chorus.js'
import TrackSnip from '../snip/track-snip.js'
import Dispatcher from '../../events/dispatcher.js'

import { store } from '../../stores/data.js'
import { getTrackId, trackSongInfo } from '../../utils/song.js'
import { updateToolTip } from '../../utils/tooltip.js'

export default class TrackList {
constructor(songTracker) {
Expand All @@ -17,6 +19,7 @@ export default class TrackList {
this._heartIcon = new HeartIcon(store)
this._snipIcon = new SnipIcon(store)
this._trackSnip = new TrackSnip(store)
this._toolTip = new ToolTip()

this._visibleEvents = ['mouseenter']
this._events = ['mouseenter', 'mouseleave']
Expand Down Expand Up @@ -200,6 +203,7 @@ export default class TrackList {
const icon = row.querySelector('button[role="skip"]')
await this._skipIcon._saveTrack(row)
this._skipIcon._animate(icon)
updateToolTip(icon)
} else {
const icon = row.querySelector('button[role="heart"]')
await this._heartIcon.toggleTrackLiked(row)
Expand Down Expand Up @@ -236,6 +240,7 @@ export default class TrackList {
icon.setInitialState(row)
})
this.#setMouseEvents(row)
this._toolTip.setupTrackListListeners(row)
})
}
}
5 changes: 4 additions & 1 deletion src/models/tracklist/tracklist-icon.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { parseNodeString } from '../../utils/parser.js'
import { trackSongInfo, currentSongInfo } from '../../utils/song.js'
import { updateToolTip } from '../../utils/tooltip.js'

import Queue from '../queue.js'

Expand Down Expand Up @@ -105,6 +106,8 @@ export default class TrackListIcon {
const display = snipInfo?.[this._key] ?? false
this._burn({ icon, burn: display })
this._glow({ icon, glow: display })
icon.setAttribute('aria-label', `${display ? 'Un' : 'B'}lock Track`)
updateToolTip(icon)
}

#getStyleProp(icon) {
Expand All @@ -118,7 +121,7 @@ export default class TrackListIcon {
}

if (icon.role == 'skip') {
icon.setAttribute('aria-label', `${burn ? 'Uns' : 'S'}kip Song`)
icon.setAttribute('aria-label', `${burn ? 'Unb' : 'B'}lock Track`)
}

const styleProp = this.#getStyleProp(icon)
Expand Down
40 changes: 1 addition & 39 deletions src/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -312,43 +312,17 @@ input[type='number']::-webkit-outer-spin-button {
justify-content: flex-end;
}

[role=ff][aria-label],
[role=rw][aria-label],
[role=loop][aria-label],
[role=skip][aria-label],
[role=snip][aria-label],
[role=speed][aria-label],
[role=heart][aria-label],
[role=settings][aria-label],
[role=artist-disco][aria-label] {
position: relative;
display: inline-block;
}

[role=ff][aria-label]:after,
[role=rw][aria-label]:after,
[role=loop][aria-label]:after,
[role=skip][aria-label]:after,
[role=snip][aria-label]:after,
[role=speed][aria-label]:after,
[role=heart][aria-label]:after,
[role=settings][aria-label]:after,
[role=artist-disco][aria-label]:after {
content: attr(aria-label);
.tooltip {
display: none;
position: absolute;
top: -1.25rem;
z-index: 99999;
right: 50%;
transform: translate3d(calc(50%), -1.5rem, 10rem);
pointer-events: none;
padding: 8px;
padding-top: 7px;
line-height: 15px;
white-space: nowrap;
text-decoration: none;
text-indent: 0;
overflow: visible;
font-size: 0.9rem;
font-weight: normal;
color: #fff;
Expand All @@ -357,18 +331,6 @@ input[type='number']::-webkit-outer-spin-button {
box-shadow: 0px 4px 6px 0px rgb(0 0 0 / 35%);
}

[role=ff][aria-label]:hover:after,
[role=rw][aria-label]:hover:after,
[role=loop][aria-label]:hover:after,
[role=skip][aria-label]:hover:after,
[role=snip][aria-label]:hover:after,
[role=speed][aria-label]:hover:after,
[role=heart][aria-label]:hover:after,
[role=settings][aria-label]:hover:after,
[role=artist-disco][aria-label]:hover:after {
display: inline-block;
}

hr {
margin: 0;
width: 100%;
Expand Down
13 changes: 13 additions & 0 deletions src/utils/tooltip.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export function updateToolTip(element) {
const ariaLabel = element?.getAttribute('aria-label')
const toolTip = document.getElementById('tooltip')

if (!toolTip || !ariaLabel) return

toolTip.textContent = ariaLabel
const rect = element.getBoundingClientRect()
const toolTipRect = toolTip.getBoundingClientRect()

toolTip.style.left = `${rect.left + rect.width / 2 - toolTipRect.width / 2}px`
toolTip.style.top = `${rect.top - 40}px`
}

0 comments on commit 67b0b43

Please sign in to comment.