Compare commits
1 Commits
191fd5659b
...
8c0b262282
Author | SHA1 | Date | |
---|---|---|---|
8c0b262282 |
@ -8,13 +8,13 @@ import (
|
|||||||
|
|
||||||
templ Zap(inv *lightning.Invoice) {
|
templ Zap(inv *lightning.Invoice) {
|
||||||
<div class="bg-[#212529] w-fit zap-animate-in border border-[#212529] m-3 rounded-lg" id={ inv.PaymentHash }>
|
<div class="bg-[#212529] w-fit zap-animate-in border border-[#212529] m-3 rounded-lg" id={ inv.PaymentHash }>
|
||||||
<div class="flex flex-row gap-3 py-1 items-center">
|
<div class="flex flex-row gap-3 items-center">
|
||||||
<svg width="32" height="32" viewBox="0 0 200 307" fill="#fada5e" xmlns="http://www.w3.org/2000/svg" class="zap-svg ps-3">
|
<svg width="32" height="32" viewBox="0 0 200 307" fill="#fada5e" xmlns="http://www.w3.org/2000/svg" class="ps-3">
|
||||||
<path d="M56 0L107.606 131H90.2129H89L1.52588e-05 131L177 307L106.979 165H121H160H200L56 0Z"/>
|
<path d="M56 0L107.606 131H90.2129H89L1.52588e-05 131L177 307L106.979 165H121H160H200L56 0Z"/>
|
||||||
</svg>
|
</svg>
|
||||||
<div class="flex flex-col pe-3 py-1">
|
<div class="flex flex-col pe-3 py-1">
|
||||||
<div class="zap-description text-[#f0f0f0]">{ inv.Description }</div>
|
<div class="text-lg text-[#f0f0f0]">{ inv.Description }</div>
|
||||||
<div class="zap-amount text-slate-300">{ fmt.Sprintf("%s", humanize(inv.Msats)) }</div>
|
<div class="text-sm text-slate-300">{ fmt.Sprintf("%.8s / %s", inv.PaymentHash, humanize(inv.Msats)) }</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -3,7 +3,7 @@ package pages
|
|||||||
import "github.com/skip2/go-qrcode"
|
import "github.com/skip2/go-qrcode"
|
||||||
import "encoding/base64"
|
import "encoding/base64"
|
||||||
|
|
||||||
templ Overlay(lnurl string, lnaddr string) {
|
templ Overlay(lnurl string) {
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
@ -16,57 +16,10 @@ templ Overlay(lnurl string, lnaddr string) {
|
|||||||
if GetEnv(ctx) == "development" {
|
if GetEnv(ctx) == "development" {
|
||||||
<script src={ GetBaseUrl(ctx) + "/js/livereload.js" }></script>
|
<script src={ GetBaseUrl(ctx) + "/js/livereload.js" }></script>
|
||||||
}
|
}
|
||||||
<style id="zap-custom-css"></style>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="controls" class="hidden grid grid-cols-[auto_auto] fixed top-0 left-0 items-center gap-3 m-3">
|
<div hx-ext="sse" sse-connect="/overlay/sse" sse-swap="zap" hx-swap="beforeend" class="fixed bottom-0 right-0" />
|
||||||
<label for="qr-scale-slider" class="text-black">QR code size:</label>
|
|
||||||
<input
|
|
||||||
type="range"
|
|
||||||
id="qr-scale-slider"
|
|
||||||
min="0.5"
|
|
||||||
max="5"
|
|
||||||
step="0.1"
|
|
||||||
value="1.0"
|
|
||||||
class="w-32 accent-teal"
|
|
||||||
/>
|
|
||||||
<label for="zap-scale-slider" class="text-black">Zap message size:</label>
|
|
||||||
<input
|
|
||||||
type="range"
|
|
||||||
id="zap-scale-slider"
|
|
||||||
min="1"
|
|
||||||
max="10"
|
|
||||||
step="1"
|
|
||||||
value="1.0"
|
|
||||||
class="w-32 accent-teal"
|
|
||||||
/>
|
|
||||||
<label for="stream-url" class="text-black">Stream URL:</label>
|
|
||||||
<a id="stream-url" class="text-sky-600 hover:text-sky-900 underline">click me</a>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
id="zap-container"
|
|
||||||
hx-ext="sse"
|
|
||||||
sse-connect="/overlay/sse"
|
|
||||||
sse-swap="zap"
|
|
||||||
hx-swap="beforeend"
|
|
||||||
class="fixed bottom-0 right-0 cursor-move"
|
|
||||||
draggable="true"
|
|
||||||
>
|
|
||||||
<div id="zap-template" class="hidden items-center justify-center text-center border border-dashed border-[#212529] m-0 p-3 w-fit">
|
|
||||||
zap messages will show up here
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div id="qr" class="bg-white fixed bottom-0 left-0 text-center p-3 cursor-move" draggable="true">
|
|
||||||
<div id="qr-scaler">
|
|
||||||
<div class="p-2">
|
|
||||||
<div class="font-bold">{ lnaddr }</div>
|
|
||||||
<img src={ "data:image/jpeg;base64," + qrEncode(lnurl) }/>
|
|
||||||
</div>
|
|
||||||
<div class="italic">zap us a message!</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<script>
|
<script>
|
||||||
// zap messages disappear after a minute
|
|
||||||
document.body.addEventListener('htmx:sseMessage', function (e) {
|
document.body.addEventListener('htmx:sseMessage', function (e) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const div = document.getElementById(e.detail.lastEventId)
|
const div = document.getElementById(e.detail.lastEventId)
|
||||||
@ -74,163 +27,11 @@ templ Overlay(lnurl string, lnaddr string) {
|
|||||||
div.addEventListener('animationend', div.remove)
|
div.addEventListener('animationend', div.remove)
|
||||||
}, 60_000)
|
}, 60_000)
|
||||||
})
|
})
|
||||||
|
|
||||||
function enableDrag(elementId, xParam, yParam) {
|
|
||||||
const element = document.getElementById(elementId);
|
|
||||||
const localStorageKey = `${elementId}-position`
|
|
||||||
|
|
||||||
const urlParams = new URLSearchParams(window.location.search);
|
|
||||||
let queryX = parseInt(urlParams.get(xParam));
|
|
||||||
let queryY = parseInt(urlParams.get(yParam));
|
|
||||||
const [localX,localY] = localStorage.getItem(localStorageKey)?.split(',').map(Number) ?? [0,0];
|
|
||||||
|
|
||||||
let currentX = !isNaN(queryX) ? queryX : localX;
|
|
||||||
let currentY = !isNaN(queryY) ? queryY : localY;
|
|
||||||
element.style.transform = `translate(${currentX}px, ${currentY}px)`;
|
|
||||||
|
|
||||||
let startX = 0;
|
|
||||||
let startY = 0;
|
|
||||||
|
|
||||||
element.addEventListener("dragstart", (e) => {
|
|
||||||
console.log("dragstart", e)
|
|
||||||
startX = e.clientX - currentX;
|
|
||||||
startY = e.clientY - currentY;
|
|
||||||
e.dataTransfer.setData("text/plain", `${currentX},${currentY}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
element.addEventListener("drag", (e) => {
|
|
||||||
if (e.clientX && e.clientY) {
|
|
||||||
currentX = e.clientX - startX;
|
|
||||||
currentY = e.clientY - startY;
|
|
||||||
element.style.transform = `translate(${currentX}px, ${currentY}px)`;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
element.addEventListener("dragend", (e) => {
|
|
||||||
currentX = e.clientX - startX;
|
|
||||||
currentY = e.clientY - startY;
|
|
||||||
element.style.transform = `translate(${currentX}px, ${currentY}px)`;
|
|
||||||
localStorage.setItem(localStorageKey, `${currentX},${currentY}`);
|
|
||||||
const params = new URLSearchParams(window.location.search);
|
|
||||||
params.set(xParam, currentX);
|
|
||||||
params.set(yParam, currentY);
|
|
||||||
window.location.search = params.toString();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
enableDrag("qr", "qrX", "qrY")
|
|
||||||
enableDrag("zap-container", "zapX", "zapY")
|
|
||||||
|
|
||||||
// hide 'zap messages will show up here' if query param given
|
|
||||||
function showControls() {
|
|
||||||
document.querySelector('#zap-template').style.display = "flex";
|
|
||||||
document.querySelector('#controls').style.display = "grid";
|
|
||||||
}
|
|
||||||
|
|
||||||
const params = new URLSearchParams(window.location.search)
|
|
||||||
if (!params.has("stream", 1) && (!params.has("config") || params.has("config", 1))) {
|
|
||||||
showControls()
|
|
||||||
}
|
|
||||||
|
|
||||||
function enableSlider(sliderId, elementId, param, onChange) {
|
|
||||||
const slider = document.getElementById(sliderId);
|
|
||||||
const target = document.getElementById(elementId);
|
|
||||||
const initialValue = localStorage.getItem(param) ?? 1.0;
|
|
||||||
slider.value = initialValue;
|
|
||||||
onChange(target, initialValue);
|
|
||||||
|
|
||||||
let timeout
|
|
||||||
slider.oninput = function (e) {
|
|
||||||
const value = e.target.value
|
|
||||||
clearTimeout(timeout);
|
|
||||||
localStorage.setItem(param, value);
|
|
||||||
onChange(target, value);
|
|
||||||
updateStreamUrl();
|
|
||||||
timeout = setTimeout(() => {
|
|
||||||
const params = new URLSearchParams(window.location.search);
|
|
||||||
params.set(param, value);
|
|
||||||
window.location.search = params.toString();
|
|
||||||
}, 1_000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enableSlider('qr-scale-slider', 'qr-scaler', 'qrScale', (element, value) => {
|
|
||||||
element.style.transform = `scaleX(${value}) scaleY(${value})`;
|
|
||||||
})
|
|
||||||
enableSlider('zap-scale-slider', 'zap-custom-css', 'zapScale', (element, value) => {
|
|
||||||
// https://tailwindcss.com/docs/font-size
|
|
||||||
const description = {
|
|
||||||
1.0: 'font-size: 18px', // text-lg
|
|
||||||
2.0: 'font-size: 20px', // text-xl
|
|
||||||
3.0: 'font-size: 24px', // text-2xl
|
|
||||||
4.0: 'font-size: 30px', // text-3xl
|
|
||||||
5.0: 'font-size: 36px', // text-4xl
|
|
||||||
6.0: 'font-size: 48px', // text-5xl
|
|
||||||
7.0: 'font-size: 60px', // text-6xl
|
|
||||||
8.0: 'font-size: 72px', // text-7xl
|
|
||||||
9.0: 'font-size: 96px', // text-8xl
|
|
||||||
10.0: 'font-size: 120px', // text-9xl
|
|
||||||
}
|
|
||||||
const amount = {
|
|
||||||
1.0: 'font-size: 14px', // text-sm
|
|
||||||
2.0: 'font-size: 16px', // text-base
|
|
||||||
3.0: 'font-size: 18px', // text-lg
|
|
||||||
4.0: 'font-size: 20px', // text-xl
|
|
||||||
5.0: 'font-size: 24px', // text-2xl
|
|
||||||
6.0: 'font-size: 30px', // text-3xl
|
|
||||||
7.0: 'font-size: 36px', // text-4xl
|
|
||||||
8.0: 'font-size: 48px', // text-5xl
|
|
||||||
9.0: 'font-size: 60px', // text-6xl
|
|
||||||
10.0: 'font-size: 72px', // text-7xl
|
|
||||||
}
|
|
||||||
const svg = {
|
|
||||||
1.0: 'width: 32px; height: 32px',
|
|
||||||
2.0: 'width: 40px; height: 40px',
|
|
||||||
3.0: 'width: 48px; height: 48px',
|
|
||||||
4.0: 'width: 56px; height: 56px',
|
|
||||||
5.0: 'width: 64px; height: 64px',
|
|
||||||
6.0: 'width: 72px; height: 72px',
|
|
||||||
7.0: 'width: 80px; height: 80px',
|
|
||||||
8.0: 'width: 88px; height: 88px',
|
|
||||||
9.0: 'width: 96px; height: 96px',
|
|
||||||
10.0: 'width: 104px; height: 104px',
|
|
||||||
}
|
|
||||||
element.textContent = `
|
|
||||||
#zap-template {
|
|
||||||
${description[value]}
|
|
||||||
}
|
|
||||||
.zap-description {
|
|
||||||
${description[value]}
|
|
||||||
}
|
|
||||||
.zap-amount {
|
|
||||||
${amount[value]}
|
|
||||||
}
|
|
||||||
.zap-svg {
|
|
||||||
${svg[value]}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
})
|
|
||||||
|
|
||||||
function updateStreamUrl() {
|
|
||||||
const params = new URLSearchParams({ stream: 1 });
|
|
||||||
const [qrX,qrY] = localStorage.getItem('qr-position')?.split(',').map(Number) ?? [0,0];
|
|
||||||
params.set("qrX", qrX);
|
|
||||||
params.set("qrY", qrY);
|
|
||||||
const [zapX,zapY] = localStorage.getItem('zap-container-position')?.split(',').map(Number) ?? [0,0];
|
|
||||||
params.set("zapX", zapX);
|
|
||||||
params.set("zapY", zapY);
|
|
||||||
const qrScale = localStorage.getItem('qrScale') ?? 1.0;
|
|
||||||
params.set("qrScale", qrScale);
|
|
||||||
const zapScale = localStorage.getItem('zapScale') ?? 1.0;
|
|
||||||
params.set("zapScale", zapScale);
|
|
||||||
const streamUrl = `${window.location.origin}${window.location.pathname}?${params.toString()}`
|
|
||||||
const element = document.getElementById('stream-url');
|
|
||||||
element.href = streamUrl;
|
|
||||||
element.textContent = '/overlay?' + params.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
updateStreamUrl();
|
|
||||||
</script>
|
</script>
|
||||||
|
<div class="bg-white fixed bottom-0 left-0 text-center p-3">
|
||||||
|
<img src={ "data:image/jpeg;base64," + qrEncode(lnurl) }/>
|
||||||
|
<div>scan to zap message</div>
|
||||||
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
}
|
}
|
||||||
|
@ -27,9 +27,9 @@ func GetEnv(ctx context.Context) string {
|
|||||||
return "development"
|
return "development"
|
||||||
}
|
}
|
||||||
|
|
||||||
func OverlayHandler(lnurl string, lnaddr string) echo.HandlerFunc {
|
func OverlayHandler(lnurl string) echo.HandlerFunc {
|
||||||
return func(c echo.Context) error {
|
return func(c echo.Context) error {
|
||||||
return render(c, http.StatusOK, Overlay(lnurl, lnaddr))
|
return render(c, http.StatusOK, Overlay(lnurl))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,11 +27,6 @@ func NewServer() *Server {
|
|||||||
CustomTimeFormat: "2006-01-02 15:04:05.00000-0700",
|
CustomTimeFormat: "2006-01-02 15:04:05.00000-0700",
|
||||||
}))
|
}))
|
||||||
|
|
||||||
u, err := url.Parse(env.PublicUrl)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
webhookPath := "/overlay/webhook"
|
webhookPath := "/overlay/webhook"
|
||||||
webhookUrl, err := url.JoinPath(env.PublicUrl, webhookPath)
|
webhookUrl, err := url.JoinPath(env.PublicUrl, webhookPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -51,8 +46,7 @@ func NewServer() *Server {
|
|||||||
s.Static("/", "public/")
|
s.Static("/", "public/")
|
||||||
|
|
||||||
s.GET("/overlay", pages.OverlayHandler(
|
s.GET("/overlay", pages.OverlayHandler(
|
||||||
lnurl.Encode(fmt.Sprintf("%s/.well-known/lnurlp/%s", env.PublicUrl, "snl")),
|
lnurl.Encode(fmt.Sprintf("%s/.well-known/lnurlp/%s", env.PublicUrl, "SNL")),
|
||||||
fmt.Sprintf("%s@%s", "snl", u.Host),
|
|
||||||
))
|
))
|
||||||
s.GET("/overlay/sse", sseHandler(p.IncomingPayments()))
|
s.GET("/overlay/sse", sseHandler(p.IncomingPayments()))
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user