feat(v0.3): QR connect (M5) + PWA installable (M4)
- M5: 📱 toolbar button → modal with a client-side QR of location.origin
(shareable when opened via the LAN IP) + localhost tip
- M4: manifest.webmanifest + icon.svg + sw.js (network-first, never intercepts
/term or /hook) + registration in main.ts; apple-touch + theme-color meta
- Browser-verified: QR canvas renders, SW 'controlled', manifest linked,
no console errors. 199 tests green, build ok.
This commit is contained in:
63
public/qr.ts
Normal file
63
public/qr.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* public/qr.ts — "open on another device" QR modal (M5).
|
||||
*
|
||||
* Renders a QR of the current origin (client-side, no server call). When the
|
||||
* page is opened via the host's LAN IP, the QR is directly shareable to a
|
||||
* phone/tablet on the same network.
|
||||
*/
|
||||
|
||||
import QRCode from 'qrcode'
|
||||
|
||||
export function mountQrConnect(toolbar: HTMLElement): void {
|
||||
const modal = document.createElement('div')
|
||||
modal.id = 'qrmodal'
|
||||
modal.style.display = 'none'
|
||||
|
||||
const card = document.createElement('div')
|
||||
card.className = 'qr-card'
|
||||
|
||||
const title = document.createElement('div')
|
||||
title.className = 'qr-title'
|
||||
title.textContent = 'Open on another device'
|
||||
|
||||
const canvas = document.createElement('canvas')
|
||||
const urlEl = document.createElement('div')
|
||||
urlEl.className = 'qr-url'
|
||||
const note = document.createElement('div')
|
||||
note.className = 'qr-note'
|
||||
|
||||
const close = document.createElement('button')
|
||||
close.className = 'qr-close'
|
||||
close.textContent = 'Close'
|
||||
|
||||
card.append(title, canvas, urlEl, note, close)
|
||||
modal.appendChild(card)
|
||||
document.body.appendChild(modal)
|
||||
|
||||
const open = (): void => {
|
||||
const origin = location.origin
|
||||
urlEl.textContent = origin
|
||||
void QRCode.toCanvas(canvas, origin, { width: 220, margin: 1 })
|
||||
const host = location.hostname
|
||||
note.textContent =
|
||||
host === 'localhost' || host === '127.0.0.1'
|
||||
? 'Tip: open this page via your LAN IP (not localhost) for a shareable code.'
|
||||
: 'Scan on a device on the same network.'
|
||||
modal.style.display = 'flex'
|
||||
}
|
||||
const hide = (): void => {
|
||||
modal.style.display = 'none'
|
||||
}
|
||||
modal.addEventListener('click', (e) => {
|
||||
if (e.target === modal) hide()
|
||||
})
|
||||
close.addEventListener('click', hide)
|
||||
|
||||
const toggle = document.createElement('button')
|
||||
toggle.className = 'toolbtn'
|
||||
toggle.textContent = '📱'
|
||||
toggle.title = 'Connect another device (QR)'
|
||||
toggle.setAttribute('aria-label', 'Connect another device')
|
||||
toggle.addEventListener('click', open)
|
||||
toolbar.appendChild(toggle)
|
||||
}
|
||||
Reference in New Issue
Block a user