diff --git a/src/components/Activity.tsx b/src/components/Activity.tsx index 8c36de2..856f07b 100644 --- a/src/components/Activity.tsx +++ b/src/components/Activity.tsx @@ -1,16 +1,13 @@ -import send from '~/assets/icons/send.svg'; -import receive from '~/assets/icons/receive.svg'; -import { ButtonLink, Card, LoadingSpinner, NiceP, SmallAmount, SmallHeader, VStack } from './layout'; -import { For, Match, ParentComponent, Show, Suspense, Switch, createMemo, createResource, createSignal } from 'solid-js'; +import { LoadingSpinner, NiceP, SmallAmount, SmallHeader } from './layout'; +import { For, Match, ParentComponent, Show, 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" import utxoIcon from '~/assets/icons/coin.svg'; import { getRedshifted } from '~/utils/fakeLabels'; import { ActivityItem } from './ActivityItem'; +import { MutinyTagItem } from '~/utils/tags'; 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' @@ -40,14 +37,14 @@ export type UtxoItem = { } keychain: string is_spent: boolean, - redshifted?: boolean + redshifted?: boolean, } const SubtleText: ParentComponent = (props) => { return

{props.children}

} -function OnChainItem(props: { item: OnChainTx, labels: string[] }) { +function OnChainItem(props: { item: OnChainTx, labels: MutinyTagItem[] }) { const [store, actions] = useMegaStore(); const isReceive = createMemo(() => props.item.received > 0); @@ -69,26 +66,11 @@ function OnChainItem(props: { item: OnChainTx, labels: string[] }) { positive={isReceive()} onClick={() => setOpen(!open())} /> - {/*
setOpen(!open())}> -
- {isReceive() ? receive arrow : send arrow} -
-
-

Unknown

- {isReceive() ? : } -
-
- - On-chain {isReceive() ? Receive : Send} - - {props.item.confirmation_time?.Confirmed ? prettyPrintTime(props.item.confirmation_time?.Confirmed?.time) : "Unconfirmed"} -
-
*/} ) } -function InvoiceItem(props: { item: MutinyInvoice, labels: string[] }) { +function InvoiceItem(props: { item: MutinyInvoice, labels: MutinyTagItem[] }) { const [store, actions] = useMegaStore(); const isSend = createMemo(() => props.item.is_send); @@ -98,21 +80,6 @@ function InvoiceItem(props: { item: MutinyInvoice, labels: string[] }) { <> setOpen(!open())} /> - {/*
setOpen(!open())}> -
- {isSend() ? send arrow : receive arrow} -
-
-

Unknown

- -
-
- - Lightning {!isSend() ? Receive : Send} - - {prettyPrintTime(Number(props.item.expire))} -
-
*/} ) } @@ -149,57 +116,49 @@ function Utxo(props: { item: UtxoItem }) { ) } -type ActivityItem = { type: "onchain" | "lightning", item: OnChainTx | MutinyInvoice, time: number } +type ActivityItem = { type: "onchain" | "lightning", item: OnChainTx | MutinyInvoice, time: number, labels: MutinyTagItem[] } function sortByTime(a: ActivityItem, b: ActivityItem) { return b.time - a.time; } export function CombinedActivity(props: { limit?: number }) { - - const [state, _] = useMegaStore(); + const [state, actions] = useMegaStore(); const getAllActivity = async () => { console.log("Getting all activity"); const txs = await state.mutiny_wallet?.list_onchain() as OnChainTx[]; const invoices = await state.mutiny_wallet?.list_invoices() as MutinyInvoice[]; + const tags = await actions.listTags(); - const activity: ActivityItem[] = []; + let activity: ActivityItem[] = []; - txs.forEach((tx) => { - activity.push({ type: "onchain", item: tx, time: tx.confirmation_time?.Confirmed?.time || Date.now() }) - }) + for (let i = 0; i < txs.length; i++) { + activity.push({ type: "onchain", item: txs[i], time: txs[i].confirmation_time?.Confirmed?.time || Date.now(), labels: [] }) + } - invoices.forEach((invoice) => { - if (invoice.paid) { - activity.push({ type: "lightning", item: invoice, time: Number(invoice.expire) }) + for (let i = 0; i < invoices.length; i++) { + if (invoices[i].paid) { + activity.push({ type: "lightning", item: invoices[i], time: Number(invoices[i].expire), labels: [] }) } - }) + } if (props.limit) { - return activity.sort(sortByTime).slice(0, props.limit); + activity = activity.sort(sortByTime).slice(0, props.limit); } else { - return activity.sort(sortByTime); + activity.sort(sortByTime); } + + for (let i = 0; i < activity.length; i++) { + // filter the tags to only include the ones that have an id matching one of the labels + activity[i].labels = tags.filter((tag) => activity[i].item.labels.includes(tag.id)); + } + + return activity; } const [activity] = createResource(getAllActivity); - // const addressLabels = createMemo(() => { - // const labels = state.mutiny_wallet?.get_address_labels(); - // console.log(labels); - // return labels || []; - // // return labels.filter((label) => label.address === props.item.txid) - // }) - - // const invoiceLabels = createMemo(() => { - // const labels = state.mutiny_wallet?.get_address_labels(); - // console.log(labels); - // if (!labels) return ["abcdefg"]; - // return labels; - // // return labels.filter((label) => label.address === props.item.txid) - // }) - return ( @@ -214,11 +173,11 @@ export function CombinedActivity(props: { limit?: number }) { {/* FIXME */} - + {/* FIXME */} - + } diff --git a/src/components/ActivityItem.tsx b/src/components/ActivityItem.tsx index 915d034..604bc68 100644 --- a/src/components/ActivityItem.tsx +++ b/src/components/ActivityItem.tsx @@ -1,9 +1,11 @@ -import { ParentComponent, createMemo } from "solid-js"; +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"; export const ActivityAmount: ParentComponent<{ amount: string, price: number, positive?: boolean }> = (props) => { const amountInUsd = createMemo(() => { @@ -35,17 +37,43 @@ export const ActivityAmount: ParentComponent<{ amount: string, price: number, po ) } -function LabelCircle(props: { name: string }) { +function LabelCircle(props: { name?: string, contact: boolean }) { + + // TODO: don't need to run this if it's not a contact + const [gradient] = createResource(props.name, async (name: string) => { + return generateGradient(name || "?") + }) + + const text = () => (props.contact && props.name && props.name.length) ? props.name[0] : (props.name && props.name.length) ? "≡" : "?" + const bg = () => (props.name && props.contact) ? gradient() : "gray" + return (
- {props.name[0] || "?"} + {text()}
) } -export function ActivityItem(props: { kind: "lightning" | "onchain", labels: string[], amount: number | bigint, date?: number | bigint, positive?: boolean, onClick?: () => void }) { +// function that takes a list of MutinyTagItems and returns bool if one of those items is of kind Contact +function includesContact(labels: MutinyTagItem[]) { + return labels.some((label) => label.kind === "Contact") +} + +// sort the labels so that the contact is always first +function sortLabels(labels: MutinyTagItem[]) { + const contact = labels.find(label => label.kind === "Contact"); + return contact ? [contact, ...labels.filter(label => label !== contact)] : labels; +} + +// return a string of each label name separated by a comma and a space. if the array is empty return "Unknown" +function labelString(labels: MutinyTagItem[]) { + return labels.length ? labels.map(label => label.name).join(", ") : "Unknown" +} + +export function ActivityItem(props: { kind: "lightning" | "onchain", labels: MutinyTagItem[], amount: number | bigint, date?: number | bigint, positive?: boolean, onClick?: () => void }) { + const labels = () => sortLabels(props.labels) return (
props.onClick && props.onClick()} @@ -54,14 +82,14 @@ export function ActivityItem(props: { kind: "lightning" | "onchain", labels: str >
- {props.kind === "lightning" ? lightning : onchain} + {props.kind === "lightning" ? lightning : onchain}
- +
- {props.labels.length ? props.labels[0] : "Unknown"} + {labelString(labels())}
diff --git a/src/components/App.tsx b/src/components/App.tsx index 282f33f..53ca269 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -1,6 +1,6 @@ import logo from '~/assets/icons/mutiny-logo.svg'; -import { DefaultMain, SafeArea, VStack, Card, FullscreenLoader } from "~/components/layout"; -import BalanceBox from "~/components/BalanceBox"; +import { DefaultMain, SafeArea, VStack, Card, LoadingSpinner } from "~/components/layout"; +import BalanceBox, { LoadingShimmer } from "~/components/BalanceBox"; import NavBar from "~/components/NavBar"; import ReloadPrompt from "~/components/Reload"; import { A } from 'solid-start'; @@ -28,7 +28,7 @@ export default function App() {
- }> + }> {/* View All */} diff --git a/src/components/BalanceBox.tsx b/src/components/BalanceBox.tsx index 708df39..f0f6c15 100644 --- a/src/components/BalanceBox.tsx +++ b/src/components/BalanceBox.tsx @@ -11,7 +11,7 @@ function prettyPrintAmount(n?: number | bigint): string { return n.toLocaleString() } -function LoadingShimmer() { +export function LoadingShimmer() { return (

diff --git a/src/components/ColorRadioGroup.tsx b/src/components/ColorRadioGroup.tsx deleted file mode 100644 index d32e701..0000000 --- a/src/components/ColorRadioGroup.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import { RadioGroup as Kobalte } from '@kobalte/core'; -import { type JSX, Show, splitProps, For } from 'solid-js'; - -type RadioGroupProps = { - name: string; - label?: string | undefined; - options: { label: string; value: string }[]; - value: string | undefined; - error: string; - required?: boolean | undefined; - disabled?: boolean | undefined; - ref: (element: HTMLInputElement | HTMLTextAreaElement) => void; - onInput: JSX.EventHandler; - onChange: JSX.EventHandler; - onBlur: JSX.EventHandler; -}; -type Color = "blue" | "green" | "red" | "gray" - -export const colorVariants = { - blue: "bg-m-blue", - green: "bg-m-green", - red: "bg-m-red", - gray: "bg-[#898989]", -} - -export function ColorRadioGroup(props: RadioGroupProps) { - const [rootProps, inputProps] = splitProps( - props, - ['name', 'value', 'required', 'disabled'], - ['ref', 'onInput', 'onChange', 'onBlur'] - ); - return ( - - - - {props.label} - - -
- - {(option) => ( - - - - - - {/* {option.label} */} - - )} - -
- {props.error} -
- ); -} \ No newline at end of file diff --git a/src/components/ContactEditor.tsx b/src/components/ContactEditor.tsx index 9cf4e0f..e724f94 100644 --- a/src/components/ContactEditor.tsx +++ b/src/components/ContactEditor.tsx @@ -1,24 +1,17 @@ -import { Match, Switch, createSignal, createUniqueId } from 'solid-js'; +import { Match, Switch, createSignal } from 'solid-js'; import { SmallHeader, TinyButton } from '~/components/layout'; import { Dialog } from '@kobalte/core'; import close from "~/assets/icons/close.svg"; import { SubmitHandler } from '@modular-forms/solid'; -import { ContactItem } from '~/state/contacts'; import { ContactForm } from './ContactForm'; +import { ContactFormValues } from './ContactViewer'; -const INITIAL: ContactItem = { id: createUniqueId(), kind: "contact", name: "", color: "gray" } - -export function ContactEditor(props: { createContact: (contact: ContactItem) => void, list?: boolean }) { +export function ContactEditor(props: { createContact: (contact: ContactFormValues) => void, list?: boolean }) { const [isOpen, setIsOpen] = createSignal(false); // What we're all here for in the first place: returning a value - const handleSubmit: SubmitHandler = (c: ContactItem) => { - // TODO: why do the id and color disappear? - - const odd = { id: createUniqueId(), kind: "contact" } - - props.createContact({ ...odd, ...c }) - + const handleSubmit: SubmitHandler = (c: ContactFormValues) => { + props.createContact(c) setIsOpen(false); } @@ -50,7 +43,7 @@ export function ContactEditor(props: { createContact: (contact: ContactItem) => Close

- +
diff --git a/src/components/ContactForm.tsx b/src/components/ContactForm.tsx index e94c139..b90330b 100644 --- a/src/components/ContactForm.tsx +++ b/src/components/ContactForm.tsx @@ -1,13 +1,10 @@ import { SubmitHandler, createForm, required } from "@modular-forms/solid"; -import { ContactItem } from "~/state/contacts"; import { Button, LargeHeader, VStack } from "~/components/layout"; import { TextField } from "~/components/layout/TextField"; -import { ColorRadioGroup } from "~/components/ColorRadioGroup"; +import { ContactFormValues } from "./ContactViewer"; -const colorOptions = [{ label: "blue", value: "blue" }, { label: "green", value: "green" }, { label: "red", value: "red" }, { label: "gray", value: "gray" }] - -export function ContactForm(props: { handleSubmit: SubmitHandler, initialValues?: ContactItem, title: string, cta: string }) { - const [_contactForm, { Form, Field }] = createForm({ initialValues: props.initialValues }); +export function ContactForm(props: { handleSubmit: SubmitHandler, initialValues?: ContactFormValues, title: string, cta: string }) { + const [_contactForm, { Form, Field }] = createForm({ initialValues: props.initialValues }); return (
@@ -24,11 +21,6 @@ export function ContactForm(props: { handleSubmit: SubmitHandler, i )} - - {(field, props) => ( - - )} -
) diff --git a/src/components/layout/index.tsx b/src/components/layout/index.tsx index 14ffe3a..168f056 100644 --- a/src/components/layout/index.tsx +++ b/src/components/layout/index.tsx @@ -1,9 +1,11 @@ -import { JSX, ParentComponent, Show, Suspense, createSignal } from "solid-js" +import { JSX, ParentComponent, Show, Suspense, createResource, createSignal } from "solid-js" import Linkify from "./Linkify" import { Button, ButtonLink } from "./Button" import { Checkbox as KCheckbox, Separator } from "@kobalte/core" import { useMegaStore } from "~/state/megaStore" import check from "~/assets/icons/check.svg" +import { MutinyTagItem } from "~/utils/tags" +import { generateGradient } from "~/utils/gradientHash" export { Button, @@ -122,9 +124,20 @@ export const NiceP: ParentComponent = (props) => { return (

{props.children}

) } -export const TinyButton: ParentComponent<{ onClick: () => void }> = (props) => { +export const TinyButton: ParentComponent<{ onClick: () => void, tag?: MutinyTagItem }> = (props) => { + // TODO: don't need to run this if it's not a contact + const [gradient] = createResource(props.tag?.name, async (name: string) => { + return generateGradient(name || "?") + }) + + const bg = () => (props.tag?.name && props.tag?.kind === "Contact") ? gradient() : "rgb(255 255 255 / 0.1)" + + console.log("tiny tag", props.tag?.name, gradient()) + return ( - ) diff --git a/src/routes/Activity.tsx b/src/routes/Activity.tsx index 76ce361..a2d1c8c 100644 --- a/src/routes/Activity.tsx +++ b/src/routes/Activity.tsx @@ -5,23 +5,40 @@ import { BackLink } from "~/components/layout/BackLink"; import { CombinedActivity } from "~/components/Activity"; import { A } from "solid-start"; import settings from '~/assets/icons/settings.svg'; -import { ContactItem, addContact, editContact, listContacts } from "~/state/contacts"; import { Tabs } from "@kobalte/core"; import { gradientsPerContact } from "~/utils/gradientHash"; import { ContactEditor } from "~/components/ContactEditor"; -import { ContactViewer } from "~/components/ContactViewer"; +import { ContactFormValues, ContactViewer } from "~/components/ContactViewer"; +import { useMegaStore } from "~/state/megaStore"; +import { Contact } from "@mutinywallet/mutiny-wasm"; +import { showToast } from "~/components/Toaster"; function ContactRow() { - const [contacts, { refetch }] = createResource(listContacts) + const [state, actions] = useMegaStore(); + const [contacts, { refetch }] = createResource(async () => { + const contacts = state.mutiny_wallet?.get_contacts(); + console.log(contacts) + + let c: Contact[] = [] + if (contacts) { + for (let contact in contacts) { + c.push(contacts[contact]) + } + } + return c || [] + }) const [gradients] = createResource(contacts, gradientsPerContact); - async function createContact(contact: ContactItem) { - await addContact(contact) + async function createContact(contact: ContactFormValues) { + const c = new Contact(contact.name, contact.npub ?? undefined, undefined, undefined); + await state.mutiny_wallet?.create_new_contact(c) refetch(); } - async function saveContact(contact: ContactItem) { - await editContact(contact) + // + async function saveContact(contact: ContactFormValues) { + showToast(new Error("Unimplemented")) + // await editContact(contact) refetch(); } @@ -31,7 +48,7 @@ function ContactRow() { {(contact) => ( - + )} @@ -49,6 +66,7 @@ export default function Activity() { Settings}>Activity + Mutiny diff --git a/src/routes/Receive.tsx b/src/routes/Receive.tsx index 0acbeac..f8c6270 100644 --- a/src/routes/Receive.tsx +++ b/src/routes/Receive.tsx @@ -14,10 +14,10 @@ import { StyledRadioGroup } from "~/components/layout/Radio"; import { showToast } from "~/components/Toaster"; import { useNavigate } from "solid-start"; import megacheck from "~/assets/icons/megacheck.png"; -import { TagItem, listTags } from "~/state/contacts"; import { AmountCard } from "~/components/AmountCard"; import { ShareCard } from "~/components/ShareCard"; import { BackButton } from "~/components/layout/BackButton"; +import { MutinyTagItem, UNKNOWN_TAG, sortByLastUsed, tagsToIds } from "~/utils/tags"; type OnChainTx = { transaction: { @@ -43,22 +43,14 @@ type OnChainTx = { } } -const createUniqueId = () => Math.random().toString(36).substr(2, 9); - const RECEIVE_FLAVORS = [{ value: "unified", label: "Unified", caption: "Sender decides" }, { value: "lightning", label: "Lightning", caption: "Fast and cool" }, { value: "onchain", label: "On-chain", caption: "Just like Satoshi did it" }] type ReceiveFlavor = "unified" | "lightning" | "onchain" type ReceiveState = "edit" | "show" | "paid" type PaidState = "lightning_paid" | "onchain_paid"; -function tagItemsToLabels(items: TagItem[]) { - const labels = items.map(item => item.kind === "contact" ? item.id : item.name) - console.log("Labels", labels) - return labels; -} - export default function Receive() { - const [state, _] = useMegaStore() + const [state, actions] = useMegaStore() const navigate = useNavigate(); const [amount, setAmount] = createSignal("") @@ -68,8 +60,8 @@ export default function Receive() { const [shouldShowAmountEditor, setShouldShowAmountEditor] = createSignal(true) // Tagging stuff - const [selectedValues, setSelectedValues] = createSignal([]); - const [values, setValues] = createSignal([{ id: createUniqueId(), name: "Unknown", kind: "text" }]); + const [selectedValues, setSelectedValues] = createSignal([]); + const [values, setValues] = createSignal([UNKNOWN_TAG]); // The data we get after a payment const [paymentTx, setPaymentTx] = createSignal(); @@ -92,8 +84,8 @@ export default function Receive() { }) onMount(() => { - listTags().then((tags) => { - setValues(prev => [...prev, ...tags || []]) + actions.listTags().then((tags) => { + setValues(prev => [...prev, ...tags.sort(sortByLastUsed) || []]) }); }) @@ -109,11 +101,8 @@ export default function Receive() { async function getUnifiedQr(amount: string) { const bigAmount = BigInt(amount); - console.log(selectedValues()); - console.log(tagItemsToLabels(selectedValues())) try { - // FIXME: actual labels - const raw = await state.mutiny_wallet?.create_bip21(bigAmount, tagItemsToLabels(selectedValues())); + const raw = await state.mutiny_wallet?.create_bip21(bigAmount, tagsToIds(selectedValues())); // Save the raw info so we can watch the address and invoice setBip21Raw(raw); diff --git a/src/routes/Send.tsx b/src/routes/Send.tsx index 880a685..c6a306d 100644 --- a/src/routes/Send.tsx +++ b/src/routes/Send.tsx @@ -17,9 +17,9 @@ import mempoolTxUrl from "~/utils/mempoolTxUrl"; import { BackLink } from "~/components/layout/BackLink"; import { useNavigate } from "solid-start"; import { TagEditor } from "~/components/TagEditor"; -import { TagItem, createUniqueId, listTags } from "~/state/contacts"; import { StringShower } from "~/components/ShareCard"; import { AmountCard } from "~/components/AmountCard"; +import { MutinyTagItem, UNKNOWN_TAG, sortByLastUsed, tagsToIds } from "~/utils/tags"; type SendSource = "lightning" | "onchain"; @@ -97,23 +97,6 @@ function DestinationShower(props: { ) } -function SendTags() { - // Tagging stuff - const [selectedValues, setSelectedValues] = createSignal([]); - const [values, setValues] = createSignal([{ id: createUniqueId(), name: "Unknown", kind: "text" }]); - - onMount(() => { - listTags().then((tags) => { - setValues(prev => [...prev, ...tags || []]) - }); - }) - - return ( - - ) -} - - export default function Send() { const [state, actions] = useMegaStore(); const navigate = useNavigate() @@ -136,6 +119,10 @@ export default function Send() { const [sending, setSending] = createSignal(false); const [sentDetails, setSentDetails] = createSignal(); + // Tagging stuff + const [selectedValues, setSelectedValues] = createSignal([]); + const [values, setValues] = createSignal([UNKNOWN_TAG]); + function clearAll() { setDestination(undefined); setAmountSats(0n); @@ -158,6 +145,10 @@ export default function Send() { setDestination(state.scan_result); actions.setScanResult(undefined); } + + actions.listTags().then((tags) => { + setValues(prev => [...prev, ...tags.sort(sortByLastUsed) || []]) + }); }) // Rerun every time the destination changes @@ -235,20 +226,16 @@ export default function Send() { sentDetails.destination = bolt11; // If the invoice has sats use that, otherwise we pass the user-defined amount if (invoice()?.amount_sats) { - // FIXME: labels - await state.mutiny_wallet?.pay_invoice(firstNode, bolt11, undefined, []); + await state.mutiny_wallet?.pay_invoice(firstNode, bolt11, undefined, tagsToIds(selectedValues())); sentDetails.amount = invoice()?.amount_sats; } else { - // FIXME: labels - await state.mutiny_wallet?.pay_invoice(firstNode, bolt11, amountSats(), []); + await state.mutiny_wallet?.pay_invoice(firstNode, bolt11, amountSats(), tagsToIds(selectedValues())); sentDetails.amount = amountSats(); } } else if (source() === "lightning" && nodePubkey()) { const nodes = await state.mutiny_wallet?.list_nodes(); const firstNode = nodes[0] as string || "" - // FIXME: labels - const payment = await state.mutiny_wallet?.keysend(firstNode, nodePubkey()!, amountSats(), []); - console.log(payment?.value) + const payment = await state.mutiny_wallet?.keysend(firstNode, nodePubkey()!, amountSats(), tagsToIds(selectedValues())); // TODO: handle timeouts if (!payment?.paid) { @@ -257,9 +244,8 @@ export default function Send() { sentDetails.amount = amountSats(); } } else if (source() === "onchain" && address()) { - // FIXME: actual labels // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const txid = await state.mutiny_wallet?.send_to_address(address()!, amountSats(), []); + const txid = await state.mutiny_wallet?.send_to_address(address()!, amountSats(), tagsToIds(selectedValues())); sentDetails.amount = amountSats(); sentDetails.destination = address(); // TODO: figure out if this is necessary, it takes forever @@ -323,7 +309,7 @@ export default function Send() { - + diff --git a/src/state/contacts.ts b/src/state/contacts.ts deleted file mode 100644 index 7637faa..0000000 --- a/src/state/contacts.ts +++ /dev/null @@ -1,58 +0,0 @@ -export type TagItem = TextItem | ContactItem; - -export type TextItem = { - id: string; - kind: "text"; - name: string; -} - -export type ContactItem = { - id: string; - kind: "contact"; - name: string; - npub?: string; - color: Color; -} - -export type Color = "blue" | "green" | "red" | "gray" - -export const createUniqueId = () => Math.random().toString(36).substr(2, 9); - -export async function listContacts(): Promise { - // get contacts from localstorage - const contacts: ContactItem[] = JSON.parse(localStorage.getItem("contacts") || "[]"); - return contacts; -} - -export async function listTexts(): Promise { - // get texts from localstorage - const texts: TextItem[] = JSON.parse(localStorage.getItem("texts") || "[]"); - return texts; -} - -export async function listTags(): Promise { - const contacts = await listContacts(); - const texts = await listTexts(); - return [...contacts, ...texts]; -} - -export async function addContact(contact: ContactItem): Promise { - const contacts = await listContacts(); - contacts.push(contact); - localStorage.setItem("contacts", JSON.stringify(contacts)); -} - -export async function editContact(contact: ContactItem): Promise { - const contacts = await listContacts(); - const index = contacts.findIndex(c => c.id === contact.id); - contacts[index] = contact; - localStorage.setItem("contacts", JSON.stringify(contacts)); -} - -export async function addTextTag(text: TextItem): Promise { - const texts = await listTexts(); - texts.push(text); - localStorage.setItem("texts", JSON.stringify(texts)); -} - - diff --git a/src/state/megaStore.tsx b/src/state/megaStore.tsx index 74a2870..7038444 100644 --- a/src/state/megaStore.tsx +++ b/src/state/megaStore.tsx @@ -6,6 +6,7 @@ import { createStore } from "solid-js/store"; import { MutinyWalletSettingStrings, setupMutinyWallet } from "~/logic/mutinyWalletSetup"; import { MutinyBalance, MutinyWallet } from "@mutinywallet/mutiny-wasm"; import { ParsedParams } from "~/routes/Scanner"; +import { MutinyTagItem } from "~/utils/tags"; const MegaStoreContext = createContext(); @@ -33,6 +34,7 @@ export type MegaStore = [{ sync(): Promise; dismissRestorePrompt(): void; setHasBackedUp(): void; + listTags(): Promise; }]; export const Provider: ParentComponent = (props) => { @@ -119,6 +121,9 @@ export const Provider: ParentComponent = (props) => { dismissRestorePrompt() { localStorage.setItem("dismissed_restore_prompt", "true") setState({ dismissed_restore_prompt: true }) + }, + async listTags(): Promise { + return state.mutiny_wallet?.get_tag_items() as MutinyTagItem[] } }; diff --git a/src/utils/gradientHash.ts b/src/utils/gradientHash.ts index 4d85234..22d65d8 100644 --- a/src/utils/gradientHash.ts +++ b/src/utils/gradientHash.ts @@ -1,6 +1,6 @@ -import { ContactItem } from "~/state/contacts"; +import { Contact } from "@mutinywallet/mutiny-wasm"; -async function generateGradientFromHashedString(str: string) { +export async function generateGradient(str: string) { const encoder = new TextEncoder(); const data = encoder.encode(str); const digestBuffer = await crypto.subtle.digest('SHA-256', data); @@ -13,11 +13,12 @@ async function generateGradientFromHashedString(str: string) { return gradient; } -export async function gradientsPerContact(contacts: ContactItem[]) { +export async function gradientsPerContact(contacts: Contact[]) { + console.log(contacts); const gradients = new Map(); for (const contact of contacts) { - const gradient = await generateGradientFromHashedString(contact.name); - gradients.set(contact.id, gradient); + const gradient = await generateGradient(contact.name); + gradients.set(contact.name, gradient); } return gradients; diff --git a/src/utils/tags.ts b/src/utils/tags.ts new file mode 100644 index 0000000..a76d835 --- /dev/null +++ b/src/utils/tags.ts @@ -0,0 +1,27 @@ +import { TagItem } from "@mutinywallet/mutiny-wasm" + +export type MutinyTagItem = { + id: string, + kind: "Label" | "Contact" + name: string, + last_used_time: bigint, + npub?: string, + ln_address?: string, + lnurl?: string, +} + +export const UNKNOWN_TAG: MutinyTagItem = { id: "Unknown", kind: "Label", name: "Unknown", last_used_time: 0n } + +export function tagsToIds(tags: MutinyTagItem[]): string[] { + return tags.filter((tag) => tag.id !== "Unknown").map((tag) => tag.id) +} + +export function tagToMutinyTag(tag: TagItem): MutinyTagItem { + // @ts-ignore + // FIXME: make typescript less mad about this + return tag as MutinyTagItem +} + +export function sortByLastUsed(a: MutinyTagItem, b: MutinyTagItem) { + return Number(b.last_used_time - a.last_used_time); +} \ No newline at end of file