mirror of
https://github.com/luminous-devs/lume.git
synced 2024-09-29 16:30:55 +00:00
feat: add bell
This commit is contained in:
parent
c843626bca
commit
afb7c87fa3
@ -5,7 +5,7 @@ import type { EventColumns, LumeColumn } from "@lume/types";
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
import { listen } from "@tauri-apps/api/event";
|
||||
import { resolveResource } from "@tauri-apps/api/path";
|
||||
import { getCurrent } from "@tauri-apps/api/webviewWindow";
|
||||
import { getCurrent } from "@tauri-apps/api/window";
|
||||
import { readTextFile } from "@tauri-apps/plugin-fs";
|
||||
import { nanoid } from "nanoid";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
@ -14,8 +14,11 @@ import { VList, type VListHandle } from "virtua";
|
||||
|
||||
export const Route = createFileRoute("/$account/home")({
|
||||
beforeLoad: async ({ context }) => {
|
||||
try {
|
||||
const ark = context.ark;
|
||||
const resourcePath = await resolveResource("resources/system_columns.json");
|
||||
const resourcePath = await resolveResource(
|
||||
"resources/system_columns.json",
|
||||
);
|
||||
const systemColumns: LumeColumn[] = JSON.parse(
|
||||
await readTextFile(resourcePath),
|
||||
);
|
||||
@ -24,6 +27,9 @@ export const Route = createFileRoute("/$account/home")({
|
||||
return {
|
||||
storedColumns: !userColumns.length ? systemColumns : userColumns,
|
||||
};
|
||||
} catch (e) {
|
||||
console.error(String(e));
|
||||
}
|
||||
},
|
||||
component: Screen,
|
||||
});
|
||||
|
@ -1,17 +1,29 @@
|
||||
import { BellIcon, ComposeFilledIcon, PlusIcon, SearchIcon } from "@lume/icons";
|
||||
import { Event, Kind } from "@lume/types";
|
||||
import { User } from "@lume/ui";
|
||||
import { cn } from "@lume/utils";
|
||||
import {
|
||||
cn,
|
||||
decodeZapInvoice,
|
||||
displayNpub,
|
||||
sendNativeNotification,
|
||||
} from "@lume/utils";
|
||||
import { Outlet, createFileRoute } from "@tanstack/react-router";
|
||||
import { UnlistenFn } from "@tauri-apps/api/event";
|
||||
import { getCurrent } from "@tauri-apps/api/window";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export const Route = createFileRoute("/$account")({
|
||||
beforeLoad: async ({ context }) => {
|
||||
const ark = context.ark;
|
||||
const accounts = await ark.get_all_accounts();
|
||||
|
||||
return { accounts };
|
||||
},
|
||||
component: Screen,
|
||||
});
|
||||
|
||||
function Screen() {
|
||||
const { account } = Route.useParams();
|
||||
const { ark, platform } = Route.useRouteContext();
|
||||
|
||||
const navigate = Route.useNavigate();
|
||||
|
||||
return (
|
||||
@ -42,14 +54,7 @@ function Screen() {
|
||||
<ComposeFilledIcon className="size-4" />
|
||||
New Post
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => ark.open_activity(account)}
|
||||
className="relative inline-flex size-8 items-center justify-center rounded-full text-neutral-800 hover:bg-black/10 dark:text-neutral-200 dark:hover:bg-white/10"
|
||||
>
|
||||
<BellIcon className="size-5" />
|
||||
{/* <span className="absolute right-0 top-0 block size-2 rounded-full bg-teal-500 ring-1 ring-black/5"></span> */}
|
||||
</button>
|
||||
<Bell />
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => ark.open_search()}
|
||||
@ -67,28 +72,20 @@ function Screen() {
|
||||
);
|
||||
}
|
||||
|
||||
export function Accounts() {
|
||||
function Accounts() {
|
||||
const navigate = Route.useNavigate();
|
||||
const { ark } = Route.useRouteContext();
|
||||
const { ark, accounts } = Route.useRouteContext();
|
||||
const { account } = Route.useParams();
|
||||
|
||||
const [accounts, setAccounts] = useState<string[]>([]);
|
||||
|
||||
const changeAccount = async (npub: string) => {
|
||||
if (npub === account) return;
|
||||
|
||||
const select = await ark.load_selected_account(npub);
|
||||
if (select)
|
||||
|
||||
if (select) {
|
||||
return navigate({ to: "/$account/home", params: { account: npub } });
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
async function getAllAccounts() {
|
||||
const data = await ark.get_all_accounts();
|
||||
if (data) setAccounts(data);
|
||||
}
|
||||
|
||||
getAllAccounts();
|
||||
}, []);
|
||||
};
|
||||
|
||||
return (
|
||||
<div data-tauri-drag-region className="flex items-center gap-3">
|
||||
@ -116,3 +113,71 @@ export function Accounts() {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Bell() {
|
||||
const { ark } = Route.useRouteContext();
|
||||
const { account } = Route.useParams();
|
||||
|
||||
const [isRing, setIsRing] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
let unlisten: UnlistenFn = undefined;
|
||||
|
||||
async function listenNotify() {
|
||||
unlisten = await getCurrent().listen<string>(
|
||||
"activity",
|
||||
async (payload) => {
|
||||
setIsRing(true);
|
||||
|
||||
const event: Event = JSON.parse(payload.payload);
|
||||
const user = await ark.get_profile(event.pubkey);
|
||||
const userName =
|
||||
user.display_name || user.name || displayNpub(event.pubkey, 16);
|
||||
|
||||
switch (event.kind) {
|
||||
case Kind.Text: {
|
||||
sendNativeNotification("Mentioned you in a note", userName);
|
||||
break;
|
||||
}
|
||||
case Kind.Repost: {
|
||||
sendNativeNotification("Reposted your note", userName);
|
||||
break;
|
||||
}
|
||||
case Kind.ZapReceipt: {
|
||||
const amount = decodeZapInvoice(event.tags);
|
||||
sendNativeNotification(
|
||||
`Zapped ₿ ${amount.bitcoinFormatted}`,
|
||||
userName,
|
||||
);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
if (!unlisten) listenNotify();
|
||||
|
||||
return () => {
|
||||
if (unlisten) unlisten();
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setIsRing(false);
|
||||
ark.open_activity(account);
|
||||
}}
|
||||
className="relative inline-flex size-8 items-center justify-center rounded-full text-neutral-800 hover:bg-black/10 dark:text-neutral-200 dark:hover:bg-white/10"
|
||||
>
|
||||
<BellIcon className="size-5" />
|
||||
{isRing ? (
|
||||
<span className="absolute right-0 top-0 block size-2 rounded-full bg-teal-500 ring-1 ring-black/5" />
|
||||
) : null}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ interface RouterContext {
|
||||
settings?: Settings;
|
||||
interests?: Interests;
|
||||
// Profile
|
||||
accounts?: Account[];
|
||||
accounts?: string[];
|
||||
profile?: Metadata;
|
||||
// Editor
|
||||
initialValue?: EditorElement[];
|
||||
|
@ -4,59 +4,39 @@ import { Link } from "@tanstack/react-router";
|
||||
import { createFileRoute, redirect } from "@tanstack/react-router";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
|
||||
export const Route = createFileRoute("/")({
|
||||
beforeLoad: async ({ context }) => {
|
||||
const ark = context.ark;
|
||||
const accounts = await ark.get_all_accounts();
|
||||
|
||||
// Run notification service
|
||||
if (accounts.length > 0) {
|
||||
await invoke("run_notification", { accounts });
|
||||
}
|
||||
|
||||
switch (accounts.length) {
|
||||
// Guest account
|
||||
case 0:
|
||||
if (!accounts.length) {
|
||||
throw redirect({
|
||||
to: "/landing",
|
||||
replace: true,
|
||||
});
|
||||
// Only 1 account, skip account selection screen
|
||||
case 1: {
|
||||
const account = accounts[0];
|
||||
const loadedAccount = await ark.load_selected_account(account);
|
||||
|
||||
if (loadedAccount) {
|
||||
throw redirect({
|
||||
to: "/$account/home",
|
||||
params: { account },
|
||||
replace: true,
|
||||
});
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
// Account selection
|
||||
default:
|
||||
// Run notification service
|
||||
await invoke("run_notification", { accounts });
|
||||
|
||||
return { accounts };
|
||||
}
|
||||
},
|
||||
component: Screen,
|
||||
});
|
||||
|
||||
function Screen() {
|
||||
const navigate = Route.useNavigate();
|
||||
const context = Route.useRouteContext();
|
||||
const { ark, accounts } = Route.useRouteContext();
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const select = async (npub: string) => {
|
||||
try {
|
||||
setLoading(true);
|
||||
|
||||
const ark = context.ark;
|
||||
const loadAccount = await ark.load_selected_account(npub);
|
||||
|
||||
if (loadAccount) {
|
||||
return navigate({
|
||||
to: "/$account/home",
|
||||
@ -64,6 +44,10 @@ function Screen() {
|
||||
replace: true,
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
setLoading(false);
|
||||
toast.error(String(e));
|
||||
}
|
||||
};
|
||||
|
||||
const currentDate = new Date().toLocaleString("default", {
|
||||
@ -86,7 +70,7 @@ function Screen() {
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{context.accounts.map((account) => (
|
||||
{accounts.map((account) => (
|
||||
<button
|
||||
type="button"
|
||||
key={account}
|
||||
|
@ -24,6 +24,7 @@ enum NSTORE_KEYS {
|
||||
export class Ark {
|
||||
public windows: WebviewWindow[];
|
||||
public settings: Settings;
|
||||
public accounts: string[];
|
||||
|
||||
constructor() {
|
||||
this.windows = [];
|
||||
@ -35,6 +36,8 @@ export class Ark {
|
||||
const cmd: string[] = await invoke("get_accounts");
|
||||
const accounts: string[] = cmd.map((item) => item.replace(".npub", ""));
|
||||
|
||||
if (!this.accounts) this.accounts = accounts;
|
||||
|
||||
return accounts;
|
||||
} catch (e) {
|
||||
throw new Error(String(e));
|
||||
|
1
packages/types/index.d.ts
vendored
1
packages/types/index.d.ts
vendored
@ -19,6 +19,7 @@ export enum Kind {
|
||||
Contacts = 3,
|
||||
Repost = 6,
|
||||
Reaction = 7,
|
||||
ZapReceipt = 9735,
|
||||
// NIP-89: App Metadata
|
||||
AppRecommendation = 31989,
|
||||
AppHandler = 31990,
|
||||
|
@ -23,12 +23,7 @@ pub fn run_notification(accounts: Vec<String>, app: tauri::AppHandle) -> Result<
|
||||
.collect();
|
||||
let subscription = Filter::new()
|
||||
.pubkeys(pubkeys)
|
||||
.kinds(vec![
|
||||
Kind::TextNote,
|
||||
Kind::Repost,
|
||||
Kind::ZapReceipt,
|
||||
Kind::EncryptedDirectMessage,
|
||||
])
|
||||
.kinds(vec![Kind::TextNote, Kind::Repost, Kind::ZapReceipt])
|
||||
.since(Timestamp::now());
|
||||
let activity_id = SubscriptionId::new("activity");
|
||||
|
||||
@ -47,7 +42,8 @@ pub fn run_notification(accounts: Vec<String>, app: tauri::AppHandle) -> Result<
|
||||
} = notification
|
||||
{
|
||||
if subscription_id == activity_id {
|
||||
let _ = app.emit_to("main", "activity", event.as_json());
|
||||
println!("new notification: {}", event.as_json());
|
||||
let _ = app.emit("activity", event.as_json());
|
||||
}
|
||||
}
|
||||
Ok(false)
|
||||
|
Loading…
Reference in New Issue
Block a user