Skip to content

Commit

Permalink
feat: ready up notification (#104)
Browse files Browse the repository at this point in the history
  • Loading branch information
garrappachc authored Dec 12, 2024
1 parent 6d12137 commit 7d893a5
Show file tree
Hide file tree
Showing 7 changed files with 126 additions and 39 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
"cssnano": "7.0.6",
"eslint": "9.16.0",
"globals": "15.13.0",
"howler": "2.2.4",
"htmx.org": "1.9.12",
"pino-princess": "1.0.0",
"postcss-cli": "11.0.0",
Expand Down
8 changes: 8 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions public/js/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ import './countdown.js'
import './fade-scroll.js'
import './flash-message.js'
import './map-thumbnail.js'
import './notification.js'
56 changes: 56 additions & 0 deletions public/js/notification.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import htmx from './htmx.js'
import { Howl } from 'https://cdn.jsdelivr.net/npm/howler@2.2.4/+esm'

function init(/** @type {HTMLElement} */ element) {
const title = element.getAttribute('data-notification-title')
if (!title) {
return
}

const body = element.getAttribute('data-notification-body') ?? ''
const icon = element.getAttribute('data-notification-icon')
const notification = new Notification(title, { body, ...(icon ? { icon } : {}) })

let /** @type {Howl} */ howl
const sound = element.getAttribute('data-notification-sound')
if (sound) {
let volume = 1.0

const v = element.getAttribute('data-notification-sound-volume')
if (v) {
volume = parseFloat(v)
}

howl = new Howl({
src: sound,
autoplay: true,
volume,
})
}

function afterSwap() {
if (document.body.contains(element)) {
return
}

notification.close()
howl.stop()
htmx.off('htmx:afterSwap', afterSwap)
}

htmx.on('htmx:afterSwap', afterSwap)
}

htmx.onLoad(element => {
if (!(element instanceof HTMLElement)) return

if (element.hasAttribute('data-notification-title')) {
init(element)
}

element.querySelectorAll(`[data-notification-title]`).forEach(element => {
if (element instanceof HTMLElement) {
init(element)
}
})
})
4 changes: 4 additions & 0 deletions public/js/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ declare module 'https://esm.sh/chart.js@4.4.6?bundle-deps&exports=Chart,PieContr
export * from 'chart.js'
}

declare module 'https://cdn.jsdelivr.net/npm/howler@2.2.4/+esm' {
export { Howl } from 'howler'
}

interface Window {
htmx: typeof import('htmx.org').default
}
3 changes: 2 additions & 1 deletion src/html/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export function Layout(
const title = <title safe>{props?.title ?? environment.WEBSITE_NAME}</title>
const body = (
<>
{props?.embedStyle && <style type="text/css">{embed(props.embedStyle)}</style>}
{props?.embedStyle ? <style type="text/css">{embed(props.embedStyle)}</style> : <></>}
<div class="flex h-full flex-col">{props?.children}</div>
<div id="notify-container"></div>
<ReadyUpDialog />
Expand All @@ -44,6 +44,7 @@ export function Layout(
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
<script src="/js/main.js" type="module"></script>
<link href={mainCss} rel="stylesheet" hx-preserve></link>
{title}
Expand Down
92 changes: 54 additions & 38 deletions src/queue/views/html/ready-up-dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,60 +4,76 @@ const dialogId = 'ready-up-dialog'

export function ReadyUpDialog() {
return (
<dialog
class="w-[616px] rounded-xl bg-abru-dark-29 px-[59px] py-[42px] shadow-xl"
id={dialogId}
_={`
<>
<dialog
class="w-[616px] rounded-xl bg-abru-dark-29 px-[59px] py-[42px] shadow-xl"
id={dialogId}
_={`
on show remove [@disabled] from <#${dialogId} button/> then me.showModal() end
on close me.close() end
`}
>
<form
class="flex flex-col items-center gap-11"
ws-send
_={`on submit add [@disabled] to <#${dialogId} button/>`}
>
<div class="flex flex-col items-center text-[32px] font-bold text-abru-light-75">
<span>Game is starting!</span>
<span>Are you ready to play?</span>
</div>
<form
class="flex flex-col items-center gap-11"
ws-send
_={`on submit add [@disabled] to <#${dialogId} button/>`}
>
<div class="flex flex-col items-center text-[32px] font-bold text-abru-light-75">
<span>Game is starting!</span>
<span>Are you ready to play?</span>
</div>

<div class="flex flex-col gap-4">
<button
name="ready"
value=""
class="w-[242px] rounded bg-accent-600 py-[12px] text-xl font-bold uppercase text-gray-50"
autofocus=""
>
I'm ready
</button>
<button
name="leave"
value=""
class="w-[242px] rounded bg-abru-light-5 py-[12px] text-xl font-bold text-gray-50"
>
No, I can't play now
</button>
</div>
</form>
</dialog>
<div class="flex flex-col gap-4">
<button
name="ready"
value=""
class="w-[242px] rounded bg-accent-600 py-[12px] text-xl font-bold uppercase text-gray-50"
autofocus=""
>
I'm ready
</button>
<button
name="leave"
value=""
class="w-[242px] rounded bg-abru-light-5 py-[12px] text-xl font-bold text-gray-50"
>
No, I can't play now
</button>
</div>
</form>
</dialog>
<div id="ready-up-notification-container" class="hidden"></div>
</>
)
}

ReadyUpDialog.show = () => {
const id = nanoid()
return (
<div id="notify-container" hx-swap-oob="beforeend">
<div id={id} _={`on load trigger show on #${dialogId} then remove me`}></div>
</div>
<>
<div id="notify-container" hx-swap-oob="beforeend">
<div id={id} _={`on load trigger show on #${dialogId} then remove me`}></div>
</div>
<div id="ready-up-notification-container">
<div
data-notification-title="Ready up!"
data-notification-body="A game is starting"
data-notification-icon="/favicon.png"
data-notification-sound="/sounds/ready_up.webm"
></div>
</div>
</>
)
}

ReadyUpDialog.close = () => {
const id = nanoid()
return (
<div id="notify-container" hx-swap-oob="beforeend">
<div id={id} _={`on load trigger close on #${dialogId} then remove me`}></div>
</div>
<>
<div id="notify-container" hx-swap-oob="beforeend">
<div id={id} _={`on load trigger close on #${dialogId} then remove me`}></div>
</div>
<div id="ready-up-notification-container"></div>
</>
)
}

0 comments on commit 7d893a5

Please sign in to comment.