redshift optimistic ui

This commit is contained in:
Paul Miller
2023-04-29 23:29:03 -05:00
parent c62686da11
commit 2c6b94835c
2 changed files with 216 additions and 9 deletions

View File

@@ -1,17 +1,19 @@
import send from '~/assets/icons/send.svg';
import receive from '~/assets/icons/receive.svg';
import { Card, LoadingSpinner, SmallAmount, SmallHeader, VStack } from './layout';
import { ButtonLink, Card, LoadingSpinner, SmallAmount, SmallHeader, VStack } from './layout';
import { For, Match, ParentComponent, Suspense, Switch, createMemo, createResource, createSignal } from 'solid-js';
import { useMegaStore } from '~/state/megaStore';
import { MutinyInvoice } from '@mutinywallet/mutiny-wasm';
import { prettyPrintTime } from '~/utils/prettyPrintTime';
import { JsonModal } from '~/components/JsonModal';
import mempoolTxUrl from '~/utils/mempoolTxUrl';
import wave from "~/assets/wave.gif"
const THREE_COLUMNS = 'grid grid-cols-[auto,1fr,auto] gap-4 py-2 px-2 border-b border-neutral-800 last:border-b-0'
const CENTER_COLUMN = 'min-w-0 overflow-hidden max-w-full'
const MISSING_LABEL = 'py-1 px-2 bg-white/10 rounded inline-block text-sm'
const RIGHT_COLUMN = 'flex flex-col items-right text-right max-w-[8rem]'
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'
export const MISSING_LABEL = 'py-1 px-2 bg-white/10 rounded inline-block text-sm'
export const REDSHIFT_LABEL = 'py-1 px-2 bg-white text-m-red rounded inline-block text-sm'
export const RIGHT_COLUMN = 'flex flex-col items-right text-right max-w-[8rem]'
type OnChainTx = {
txid: string
@@ -26,14 +28,15 @@ type OnChainTx = {
}
}
type Utxo = {
export type UtxoItem = {
outpoint: string
txout: {
value: number
script_pubkey: string
}
keychain: string
is_spent: boolean
is_spent: boolean,
redshifted?: boolean
}
const SubtleText: ParentComponent = (props) => {
@@ -95,7 +98,7 @@ function InvoiceItem(props: { item: MutinyInvoice }) {
)
}
function Utxo(props: { item: Utxo }) {
function Utxo(props: { item: UtxoItem }) {
const spent = createMemo(() => props.item.is_spent);
const [open, setOpen] = createSignal(false)
@@ -136,7 +139,7 @@ export function Activity() {
const getUtXos = async () => {
console.log("Getting utxos");
const utxos = await state.node_manager?.list_utxos() as Utxo[];
const utxos = await state.node_manager?.list_utxos() as UtxoItem[];
return utxos;
}
@@ -197,6 +200,7 @@ export function Activity() {
</For>
</Match>
</Switch>
<ButtonLink href="/redshift" layout="small" class="flex items-center gap-2 self-center hover:text-m-red">Redshift <img src={wave} class="h-4" alt="redshift"></img></ButtonLink>
</Card>
</Suspense>
</VStack>

203
src/routes/Redshift.tsx Normal file
View File

@@ -0,0 +1,203 @@
import { createEffect, createMemo, createResource, createSignal, For, Match, onMount, Suspense, Switch } from "solid-js";
import { CENTER_COLUMN, MISSING_LABEL, REDSHIFT_LABEL, RIGHT_COLUMN, THREE_COLUMNS, UtxoItem } from "~/components/Activity";
import { Card, DefaultMain, LargeHeader, LoadingSpinner, NiceP, NodeManagerGuard, SafeArea, SmallAmount, SmallHeader, VStack } from "~/components/layout";
import { BackLink } from "~/components/layout/BackLink";
import { StyledRadioGroup } from "~/components/layout/Radio";
import NavBar from "~/components/NavBar";
import { useMegaStore } from "~/state/megaStore";
import wave from "~/assets/wave.gif"
type ShiftOption = "utxo" | "lightning"
type ShiftStage = "choose" | "observe" | "success" | "failure"
const SHIFT_OPTIONS = [{ value: "utxo", label: "UTXO", caption: "Trade your UTXO for a fresh UTXO" }, { value: "lightning", label: "Lightning", caption: "Convert your UTXO into Lightning" }]
import receive from '~/assets/icons/receive.svg';
import { Button } from "~/components/layout/Button";
import { ProgressBar } from "~/components/layout/ProgressBar";
export function Utxo(props: { item: UtxoItem, onClick?: () => void, redshifted?: boolean }) {
const spent = createMemo(() => props.item.is_spent);
return (
<>
<div class={`${THREE_COLUMNS} ${props.redshifted && "bg-gradient-to-t from-m-red/10 to-transparent rounded-lg"}`} onClick={props.onClick}>
<img src={receive} alt="receive arrow" />
<div class={CENTER_COLUMN}>
<div class="flex gap-2">
{props.redshifted && <h2 class={REDSHIFT_LABEL}>RS</h2>}
{!props.item.redshifted && <h2 class={MISSING_LABEL}>Unknown</h2>}
</div>
<SmallAmount amount={props.item.txout.value} />
</div>
<div class={RIGHT_COLUMN}>
<SmallHeader class={props.item?.is_spent ? "text-m-red" : "text-m-green"}>
{props.item?.is_spent ? "SPENT" : "UNSPENT"}
</SmallHeader>
</div>
</div>
</>
)
}
const FAKE_STATES = ["Creating a new node", "Opening a channel", "Sending funds through", "Closing the channel", "Redshift complete"]
function ShiftObserver(props: { setShiftStage: (stage: ShiftStage) => void }, utxo: UtxoItem) {
const [fakeStage, setFakeStage] = createSignal(2);
// onMount(() => {
// const interval = setInterval(() => {
// console.log("intervaling")
// if (fakeStage() === FAKE_STATES.length - 1) {
// clearInterval(interval)
// props.setShiftStage("success");
// } else {
// setFakeStage((fakeStage() + 1))
// }
// // cont()
// }, 1000)
// // return () => clearInterval(interval);
// })
const [sentAmount, setSentAmount] = createSignal(0);
onMount(() => {
const interval = setInterval(() => {
if (sentAmount() === 200000) {
// clearInterval(interval)
// props.setShiftStage("success");
setSentAmount((0))
} else {
setSentAmount((sentAmount() + 50000))
}
}, 1000)
})
return (
<>
<NiceP>Watch it go!</NiceP>
<Card>
<VStack>
<pre class="self-center">{FAKE_STATES[fakeStage()]}</pre>
<ProgressBar value={sentAmount()} max={200000} />
<img src={wave} class="h-4 self-center" alt="sine wave" />
</VStack>
</Card>
</>
)
}
export default function Redshift() {
const [state, _actions] = useMegaStore();
const [shiftStage, setShiftStage] = createSignal<ShiftStage>("observe");
const [shiftType, setShiftType] = createSignal<ShiftOption>("utxo");
const [chosenUtxo, setChosenUtxo] = createSignal<UtxoItem>();
const getUtXos = async () => {
console.log("Getting utxos");
const utxos = await state.node_manager?.list_utxos() as UtxoItem[];
return utxos;
}
const [utxos, { refetch: _refetchUtxos }] = createResource(getUtXos);
createEffect(() => {
if (chosenUtxo()) {
setShiftStage("observe");
}
})
function resetState() {
setShiftStage("choose");
setShiftType("utxo");
setChosenUtxo(undefined);
}
return (
<NodeManagerGuard>
<SafeArea>
<DefaultMain>
<BackLink />
<LargeHeader>Redshift</LargeHeader>
<VStack biggap>
<Switch>
<Match when={shiftStage() === "choose"}>
<VStack>
<NiceP>Where is this going?</NiceP>
<StyledRadioGroup red value={shiftType()} onValueChange={(newValue) => setShiftType(newValue as ShiftOption)} choices={SHIFT_OPTIONS} />
</VStack>
<VStack>
<NiceP>Choose your <span class="inline-block"><img class="h-4" src={wave} alt="sine wave" /></span> UTXO to begin</NiceP>
<Suspense>
<Card title="Unshifted UTXOs">
<Switch>
<Match when={utxos.loading}>
<LoadingSpinner wide />
</Match>
<Match when={utxos.state === "ready" && utxos().length === 0}>
<code>No utxos (empty state)</code>
</Match>
<Match when={utxos.state === "ready" && utxos().length >= 0}>
<For each={utxos()}>
{(utxo) =>
<Utxo item={utxo} onClick={() => setChosenUtxo(utxo)} />
}
</For>
</Match>
</Switch>
</Card>
</Suspense>
<Suspense>
<Card titleElement={<SmallHeader><span class="text-m-red">Redshifted </span>UTXOs</SmallHeader>}>
<Switch>
<Match when={utxos.loading}>
<LoadingSpinner wide />
</Match>
<Match when={utxos.state === "ready" && utxos().length === 0}>
<code>No utxos (empty state)</code>
</Match>
<Match when={utxos.state === "ready" && utxos().length >= 0}>
<For each={utxos()}>
{(utxo) =>
<Utxo item={utxo} />
}
</For>
</Match>
</Switch>
</Card>
</Suspense>
</VStack>
</Match>
<Match when={shiftStage() === "observe"}>
<ShiftObserver setShiftStage={setShiftStage} />
</Match>
<Match when={shiftStage() === "success"}>
<VStack>
<NiceP>We did it. Here's your new UTXO:</NiceP>
<Card>
<Utxo item={chosenUtxo() ?? chosenUtxo()!} redshifted />
</Card>
<Button intent="red" onClick={resetState}>Nice</Button>
</VStack>
</Match>
<Match when={shiftStage() === "failure"}>
<NiceP>Oh dear</NiceP>
<NiceP>Here's what happened:</NiceP>
<Button intent="red" onClick={resetState}>Dangit</Button>
</Match>
</Switch>
</VStack>
</DefaultMain>
<NavBar activeTab="redshift" />
</SafeArea>
</NodeManagerGuard>
)
}