lightning details view

This commit is contained in:
Paul Miller
2023-05-15 12:25:41 -05:00
parent 0c6e729521
commit f48b8db55e
8 changed files with 166 additions and 16 deletions

View File

@@ -0,0 +1,3 @@
<svg width="14" height="16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.0778 6.68331 4.44176 15.5633c-.24.246-.638-.039-.482-.345l3.074-6.06599c.02328-.04578.03442-.09677.03235-.14809-.00207-.05132-.01728-.10125-.04418-.14501-.02689-.04375-.06457-.07987-.10943-.10489-.04485-.02502-.09538-.03811-.14674-.03801H.299757c-.059058-.00005-.116788-.01752-.165955-.05024-.0491673-.03272-.087584-.07922-.1104352-.13368-.02285107-.05446-.02912021-.11445-.01802154-.17246.01109864-.058.03907164-.11144.08041244-.15362L8.09576.0913129c.232-.2349999.618.0230001.489.3280001l-2.297 5.414997c-.01945.04591-.02715.09594-.02241.14557.00475.04963.02179.0973.04958.13869.02779.04139.06546.07521.10961.09838.04414.02318.09336.03499.14322.03436l6.29104-.078c.0593-.00095.1176.01573.1675.04794.0499.03221.0891.0785.1127.133.0235.0545.0304.11477.0197.17317-.0108.0584-.0386.11231-.0799.15489l-.001.001Z" fill="#000"/>
</svg>

After

Width:  |  Height:  |  Size: 921 B

View File

@@ -0,0 +1,4 @@
<svg width="17" height="17" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="m3.2916 7.53561-2.12 2.121C.421438 10.4068 0 11.4242 0 12.4851s.421438 2.0783 1.1716 2.8285c.75017.7502 1.76761 1.1716 2.8285 1.1716 1.0609 0 2.07834-.4214 2.8285-1.1716l2.828-2.828c.3715-.3714.6661-.8124.8671-1.2977.2011-.4853.3045-1.0055.3045-1.53079 0-.5253-.1034-1.04546-.3045-1.53078-.201-.48531-.4956-.92628-.8671-1.29772l-1.06 1.06c.23222.23216.41643.50778.54211.81114.12567.30336.19036.6285.19036.95686 0 .32836-.06469.65349-.19036.95689-.12568.3033-.30989.579-.54211.8111l-2.831 2.828c-.4715.4554-1.10301.7074-1.7585.7017-.65549-.0057-1.28252-.2686-1.74604-.7321-.46352-.4636-.72645-1.0906-.73214-1.7461-.0057-.6555.24629-1.287.70168-1.7585l2.12-2.12099-1.06-1.061h.001Z" fill="#000"/>
<path d="m12.1304 7.8886 2.121-2.12c.4655-.46947.7261-1.10423.7248-1.76538-.0014-.66114-.2646-1.29483-.732-1.7624-.4674-.46756-1.1011-.73093-1.7622-.73247-.6612-.00154-1.296.25887-1.7656.72425l-2.82899 2.828c-.23222.23216-.41643.50779-.5421.81114-.12568.30336-.19037.6285-.19037.95686 0 .32836.06469.65351.19037.95686.12567.30336.30988.57899.5421.81114l-1.06 1.06c-.37146-.37143-.66612-.8124-.86715-1.29772-.20103-.48531-.3045-1.00547-.3045-1.53078 0-.5253.10347-1.04546.3045-1.53078.20103-.48531.49569-.92628.86715-1.29772l2.828-2.828C10.4056.421438 11.423-1e-8 12.4839 0c1.0609 1e-8 2.0783.421438 2.8285 1.1716.7502.75017 1.1716 1.76761 1.1716 2.8285 0 1.0609-.4214 2.07834-1.1716 2.8285l-2.121 2.121-1.061-1.06v-.001Z" fill="#000"/>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -9,6 +9,7 @@ import { getRedshifted } from '~/utils/fakeLabels';
import { ActivityItem } from './ActivityItem';
import { MutinyTagItem } from '~/utils/tags';
import { Network } from '~/logic/mutinyWalletSetup';
import { DetailsModal } from './DetailsModal';
export const THREE_COLUMNS = 'grid grid-cols-[auto,1fr,auto] gap-4 py-2 px-2 border-b border-neutral-800 last:border-b-0'
export const CENTER_COLUMN = 'min-w-0 overflow-hidden max-w-full'
@@ -53,7 +54,6 @@ function OnChainItem(props: { item: OnChainTx, labels: MutinyTagItem[], network:
Mempool Link
</a>
</JsonModal>
{/* {JSON.stringify(props.labels)} */}
<ActivityItem
kind={"onchain"}
labels={props.labels}
@@ -74,7 +74,7 @@ function InvoiceItem(props: { item: MutinyInvoice, labels: MutinyTagItem[] }) {
return (
<>
<JsonModal open={open()} data={props.item} title="Lightning Transaction" setOpen={setOpen} />
<DetailsModal open={open()} data={props.item} title="Lightning Transaction" setOpen={setOpen} />
<ActivityItem kind={"lightning"} labels={props.labels} amount={props.item.amount_sats || 0n} date={props.item.last_updated} positive={!isSend()} onClick={() => setOpen(!open())} />
</>
)

View File

@@ -1,13 +1,13 @@
import { ParentComponent, createMemo, createResource } from "solid-js";
import { InlineAmount } from "./AmountCard";
import { satsToUsd } from "~/utils/conversions";
import bolt from "~/assets/icons/bolt.svg"
import chain from "~/assets/icons/chain.svg"
import { timeAgo } from "~/utils/prettyPrintTime";
import { MutinyTagItem } from "~/utils/tags";
import { generateGradient } from "~/utils/gradientHash";
import { useMegaStore } from "~/state/megaStore";
export const ActivityAmount: ParentComponent<{ amount: string, price: number, positive?: boolean }> = (props) => {
export const ActivityAmount: ParentComponent<{ amount: string, price: number, positive?: boolean, center?: boolean }> = (props) => {
const amountInUsd = createMemo(() => {
const parsed = Number(props.amount);
if (isNaN(parsed)) {
@@ -27,7 +27,8 @@ export const ActivityAmount: ParentComponent<{ amount: string, price: number, po
})
return (
<div class="flex flex-col items-end">
<div class="flex flex-col"
classList={{ "items-end": !props.center, "items-center": props.center }}>
<div class="text-base"
classList={{ "text-m-green": props.positive }}
>{props.positive && "+ "}{prettyPrint()}&nbsp;<span class="text-sm">SATS</span>
@@ -74,6 +75,7 @@ function labelString(labels: MutinyTagItem[]) {
export function ActivityItem(props: { kind: "lightning" | "onchain", labels: MutinyTagItem[], amount: number | bigint, date?: number | bigint, positive?: boolean, onClick?: () => void }) {
const labels = () => sortLabels(props.labels)
const [state, _actions] = useMegaStore();
return (
<div
onClick={() => props.onClick && props.onClick()}
@@ -93,7 +95,7 @@ export function ActivityItem(props: { kind: "lightning" | "onchain", labels: Mut
<time class="text-sm text-neutral-500">{timeAgo(props.date)}</time>
</div>
<div class="">
<ActivityAmount amount={props.amount.toString()} price={30000} positive={props.positive} />
<ActivityAmount amount={props.amount.toString()} price={state.price} positive={props.positive} />
</div>
</div>
)

View File

@@ -0,0 +1,142 @@
import { Dialog } from "@kobalte/core";
import { For, JSX, ParentComponent, Show, createMemo } from "solid-js";
import { Hr, TinyButton, VStack } from "~/components/layout";
import { MutinyInvoice } from "@mutinywallet/mutiny-wasm";
import { OnChainTx } from "./Activity";
import eyeIcon from "~/assets/icons/eye.svg"
import close from "~/assets/icons/close.svg";
import bolt from "~/assets/icons/bolt-black.svg";
import { ActivityAmount } from "./ActivityItem";
import { CopyButton } from "./ShareCard";
import { prettyPrintTime } from "~/utils/prettyPrintTime";
import { useMegaStore } from "~/state/megaStore";
import { tagToMutinyTag } from "~/utils/tags";
const OVERLAY = "fixed inset-0 z-50 bg-black/50 backdrop-blur-sm"
const DIALOG_POSITIONER = "fixed inset-0 z-50 flex items-center justify-center"
const DIALOG_CONTENT = "max-w-[500px] w-[90vw] max-h-[100dvh] overflow-y-scroll disable-scrollbars mx-4 p-4 bg-neutral-800/80 backdrop-blur-md shadow-xl rounded-xl border border-white/10"
function LightningHeader(props: { info: MutinyInvoice }) {
const [state, _actions] = useMegaStore();
const tags = createMemo(() => {
if (props.info.labels.length) {
let contact = state.mutiny_wallet?.get_contact(props.info.labels[0]);
if (contact) {
return [tagToMutinyTag(contact)]
} else {
return []
}
} else {
return []
}
})
return (
<div class="flex flex-col items-center gap-4">
<div class="p-4 bg-neutral-100 rounded-full">
<img src={bolt} alt="lightning bolt" class="w-8 h-8" />
</div>
<h1 class="uppercase font-semibold">{props.info.is_send ? "Lightning send" : "Lightning receive"}</h1>
<ActivityAmount center amount={props.info.amount_sats?.toString() ?? "0"} price={state.price} positive={!props.info.is_send} />
<For each={tags()}>
{(tag) => (
<TinyButton tag={tag} onClick={() => { }}>
{tag.name}
</TinyButton>
)}
</For>
</div>
)
}
const KeyValue: ParentComponent<{ key: string }> = (props) => {
return (
<li class="flex justify-between items-center gap-4">
<span class="uppercase font-semibold whitespace-nowrap">{props.key}</span>
{props.children}
</li>
)
}
function MiniStringShower(props: { text: string }) {
return (
<div class="w-full grid gap-1 grid-cols-[minmax(0,_1fr)_auto]">
<pre class="truncate text-neutral-300">{props.text}</pre>
<button class="w-[1.5rem]" onClick={() => { }}>
<img src={eyeIcon} alt="eye" />
</button>
</div>
)
}
function LightningDetails(props: { info: MutinyInvoice }) {
return (
<VStack>
<ul class="flex flex-col gap-4">
<KeyValue key="Status">
<span class="text-neutral-300">{props.info.paid ? "Paid" : "Unpaid"}</span>
</KeyValue>
<KeyValue key="When">
<span class="text-neutral-300">{prettyPrintTime(Number(props.info.last_updated))}</span>
</KeyValue>
<Show when={props.info.description}>
<KeyValue key="Description">
<span class="text-neutral-300 truncate">{props.info.description}</span>
</KeyValue>
</Show>
<KeyValue key="Fees">
<span class="text-neutral-300">{props.info.fees_paid?.toLocaleString() ?? 0}</span>
</KeyValue>
<KeyValue key="Bolt11">
<MiniStringShower text={props.info.bolt11 ?? ""} />
</KeyValue>
<KeyValue key="Payment Hash">
<MiniStringShower text={props.info.payment_hash ?? ""} />
</KeyValue>
<KeyValue key="Preimage">
<MiniStringShower text={props.info.preimage ?? ""} />
</KeyValue>
</ul>
</VStack>
)
}
export function DetailsModal(props: { title: string, open: boolean, data?: MutinyInvoice | OnChainTx, setOpen: (open: boolean) => void, children?: JSX.Element }) {
const json = createMemo(() => JSON.stringify(props.data, null, 2));
return (
<Dialog.Root open={props.open} onOpenChange={(isOpen) => props.setOpen(isOpen)}>
<Dialog.Portal>
<Dialog.Overlay class={OVERLAY} />
<div class={DIALOG_POSITIONER}>
<Dialog.Content class={DIALOG_CONTENT}>
<div class="flex justify-between mb-2">
<div />
<Dialog.CloseButton>
<button tabindex="-1" class="self-center hover:bg-white/10 rounded-lg active:bg-m-blue ">
<img src={close} alt="Close" class="w-8 h-8" />
</button>
</Dialog.CloseButton>
</div>
<Dialog.Title>
<LightningHeader info={props.data as MutinyInvoice} />
</Dialog.Title>
<Hr />
<Dialog.Description class="flex flex-col gap-4">
<LightningDetails info={props.data as MutinyInvoice} />
<div class="flex justify-center">
<CopyButton title="Copy Json" text={json()} />
</div>
</Dialog.Description>
</Dialog.Content>
</div>
</Dialog.Portal>
</Dialog.Root >
)
}

View File

@@ -8,7 +8,7 @@ import { JsonModal } from "./JsonModal";
const STYLE = "px-4 py-2 rounded-xl border-2 border-white flex gap-2 items-center font-semibold"
function ShareButton(props: { receiveString: string }) {
export function ShareButton(props: { receiveString: string }) {
async function share(receiveString: string) {
// If the browser doesn't support share we can just copy the address
if (!navigator.share) {
@@ -45,20 +45,25 @@ export function StringShower(props: { text: string }) {
)
}
export function ShareCard(props: { text?: string }) {
export function CopyButton(props: { text?: string, title?: string }) {
const [copy, copied] = useCopy({ copiedTimeout: 1000 });
function handleCopy() {
copy(props.text ?? "")
}
return (
<button class={STYLE} onClick={handleCopy}>{copied() ? "Copied" : props.title ?? "Copy"}<img src={copyIcon} alt="copy" /></button>
)
}
export function ShareCard(props: { text?: string }) {
return (
<Card>
<StringShower text={props.text ?? ""} />
<VStack>
<div class="flex gap-4 justify-center">
<button class={STYLE} onClick={handleCopy}>{copied() ? "Copied" : "Copy"}<img src={copyIcon} alt="copy" /></button>
<CopyButton text={props.text ?? ""} />
<Show when={navigator.share}>
<ShareButton receiveString={props.text ?? ""} />
</Show>

View File

@@ -39,8 +39,3 @@ a {
#video-container .scan-region-highlight-svg {
display: none;
}
/* Missing you sveltekit */
dd {
@apply mb-8 mt-2;
}

View File

@@ -1,6 +1,5 @@
export function prettyPrintTime(ts: number) {
const options: Intl.DateTimeFormatOptions = {
weekday: 'long',
year: 'numeric',
month: 'short',
day: 'numeric',