feat: Add support for NIP-51 (#236)
* feat: and follow and interest sets * feat: improve query * feat: improve
This commit is contained in:
@@ -13,8 +13,8 @@ import { createStore } from "@tauri-apps/plugin-store";
|
||||
import { routeTree } from "./routes.gen"; // auto generated file
|
||||
|
||||
const platform = type();
|
||||
// @ts-ignore, https://github.com/tauri-apps/plugins-workspace/pull/1860
|
||||
const store = await createStore(".cache", { autoSave: 100 });
|
||||
// @ts-expect-error, required: https://github.com/tauri-apps/plugins-workspace/pull/1860
|
||||
const store = await createStore(".cache", { autoSave: 250 });
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
|
||||
@@ -163,17 +163,49 @@ async getMentionList() : Promise<Result<Mention[], string>> {
|
||||
else return { status: "error", error: e as any };
|
||||
}
|
||||
},
|
||||
async getLumeStore(key: string) : Promise<Result<string, string>> {
|
||||
async setGroup(title: string, description: string | null, image: string | null, users: string[]) : Promise<Result<string, string>> {
|
||||
try {
|
||||
return { status: "ok", data: await TAURI_INVOKE("get_lume_store", { key }) };
|
||||
return { status: "ok", data: await TAURI_INVOKE("set_group", { title, description, image, users }) };
|
||||
} catch (e) {
|
||||
if(e instanceof Error) throw e;
|
||||
else return { status: "error", error: e as any };
|
||||
}
|
||||
},
|
||||
async setLumeStore(key: string, content: string) : Promise<Result<string, string>> {
|
||||
async getGroup(id: string) : Promise<Result<string, string>> {
|
||||
try {
|
||||
return { status: "ok", data: await TAURI_INVOKE("set_lume_store", { key, content }) };
|
||||
return { status: "ok", data: await TAURI_INVOKE("get_group", { id }) };
|
||||
} catch (e) {
|
||||
if(e instanceof Error) throw e;
|
||||
else return { status: "error", error: e as any };
|
||||
}
|
||||
},
|
||||
async getAllGroups() : Promise<Result<string[], string>> {
|
||||
try {
|
||||
return { status: "ok", data: await TAURI_INVOKE("get_all_groups") };
|
||||
} catch (e) {
|
||||
if(e instanceof Error) throw e;
|
||||
else return { status: "error", error: e as any };
|
||||
}
|
||||
},
|
||||
async setInterest(title: string, description: string | null, image: string | null, hashtags: string[]) : Promise<Result<string, string>> {
|
||||
try {
|
||||
return { status: "ok", data: await TAURI_INVOKE("set_interest", { title, description, image, hashtags }) };
|
||||
} catch (e) {
|
||||
if(e instanceof Error) throw e;
|
||||
else return { status: "error", error: e as any };
|
||||
}
|
||||
},
|
||||
async getInterest(id: string) : Promise<Result<string, string>> {
|
||||
try {
|
||||
return { status: "ok", data: await TAURI_INVOKE("get_interest", { id }) };
|
||||
} catch (e) {
|
||||
if(e instanceof Error) throw e;
|
||||
else return { status: "error", error: e as any };
|
||||
}
|
||||
},
|
||||
async getAllInterests() : Promise<Result<string[], string>> {
|
||||
try {
|
||||
return { status: "ok", data: await TAURI_INVOKE("get_all_interests") };
|
||||
} catch (e) {
|
||||
if(e instanceof Error) throw e;
|
||||
else return { status: "error", error: e as any };
|
||||
@@ -307,9 +339,25 @@ async subscribeTo(id: string) : Promise<Result<null, string>> {
|
||||
else return { status: "error", error: e as any };
|
||||
}
|
||||
},
|
||||
async getEventsBy(publicKey: string, limit: number) : Promise<Result<RichEvent[], string>> {
|
||||
async getAllEventsByAuthor(publicKey: string, limit: number) : Promise<Result<RichEvent[], string>> {
|
||||
try {
|
||||
return { status: "ok", data: await TAURI_INVOKE("get_events_by", { publicKey, limit }) };
|
||||
return { status: "ok", data: await TAURI_INVOKE("get_all_events_by_author", { publicKey, limit }) };
|
||||
} catch (e) {
|
||||
if(e instanceof Error) throw e;
|
||||
else return { status: "error", error: e as any };
|
||||
}
|
||||
},
|
||||
async getAllEventsByAuthors(publicKeys: string[], until: string | null) : Promise<Result<RichEvent[], string>> {
|
||||
try {
|
||||
return { status: "ok", data: await TAURI_INVOKE("get_all_events_by_authors", { publicKeys, until }) };
|
||||
} catch (e) {
|
||||
if(e instanceof Error) throw e;
|
||||
else return { status: "error", error: e as any };
|
||||
}
|
||||
},
|
||||
async getAllEventsByHashtags(hashtags: string[], until: string | null) : Promise<Result<RichEvent[], string>> {
|
||||
try {
|
||||
return { status: "ok", data: await TAURI_INVOKE("get_all_events_by_hashtags", { hashtags, until }) };
|
||||
} catch (e) {
|
||||
if(e instanceof Error) throw e;
|
||||
else return { status: "error", error: e as any };
|
||||
@@ -323,14 +371,6 @@ async getLocalEvents(until: string | null) : Promise<Result<RichEvent[], string>
|
||||
else return { status: "error", error: e as any };
|
||||
}
|
||||
},
|
||||
async getGroupEvents(publicKeys: string[], until: string | null) : Promise<Result<RichEvent[], string>> {
|
||||
try {
|
||||
return { status: "ok", data: await TAURI_INVOKE("get_group_events", { publicKeys, until }) };
|
||||
} catch (e) {
|
||||
if(e instanceof Error) throw e;
|
||||
else return { status: "error", error: e as any };
|
||||
}
|
||||
},
|
||||
async getGlobalEvents(until: string | null) : Promise<Result<RichEvent[], string>> {
|
||||
try {
|
||||
return { status: "ok", data: await TAURI_INVOKE("get_global_events", { until }) };
|
||||
@@ -339,9 +379,9 @@ async getGlobalEvents(until: string | null) : Promise<Result<RichEvent[], string
|
||||
else return { status: "error", error: e as any };
|
||||
}
|
||||
},
|
||||
async getHashtagEvents(hashtags: string[], until: string | null) : Promise<Result<RichEvent[], string>> {
|
||||
async isDeletedEvent(id: string) : Promise<Result<boolean, string>> {
|
||||
try {
|
||||
return { status: "ok", data: await TAURI_INVOKE("get_hashtag_events", { hashtags, until }) };
|
||||
return { status: "ok", data: await TAURI_INVOKE("is_deleted_event", { id }) };
|
||||
} catch (e) {
|
||||
if(e instanceof Error) throw e;
|
||||
else return { status: "error", error: e as any };
|
||||
|
||||
@@ -18,7 +18,7 @@ import { decode } from "light-bolt11-decoder";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import type { RichEvent, Settings } from "./commands.gen";
|
||||
import { LumeEvent } from "./system";
|
||||
import type { NostrEvent } from "./types";
|
||||
import type { LumeColumn, NostrEvent } from "./types";
|
||||
|
||||
dayjs.extend(relativeTime);
|
||||
dayjs.extend(updateLocale);
|
||||
@@ -154,6 +154,7 @@ export function decodeZapInvoice(tags?: string[][]) {
|
||||
(s: { name: string }) => s.name === "amount",
|
||||
);
|
||||
|
||||
// @ts-ignore, its fine.
|
||||
const amount = Number.parseInt(amountSection.value);
|
||||
const displayValue = getBitcoinDisplayValues(amount);
|
||||
|
||||
@@ -289,3 +290,5 @@ export const appSettings = new Store<Settings>({
|
||||
display_media: true,
|
||||
transparent: true,
|
||||
});
|
||||
|
||||
export const appColumns = new Store<LumeColumn[]>([]);
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { commands } from "@/commands.gen";
|
||||
import { appColumns } from "@/commons";
|
||||
import type { LumeColumn } from "@/types";
|
||||
import { CaretDown, Check } from "@phosphor-icons/react";
|
||||
import { useParams } from "@tanstack/react-router";
|
||||
import { useStore } from "@tanstack/react-store";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { listen } from "@tauri-apps/api/event";
|
||||
import { Menu, MenuItem, PredefinedMenuItem } from "@tauri-apps/api/menu";
|
||||
@@ -88,7 +90,7 @@ export const Column = memo(function Column({ column }: { column: LumeColumn }) {
|
||||
return (
|
||||
<div className="h-full w-[440px] shrink-0 border-r border-black/5 dark:border-white/5">
|
||||
<div className="flex flex-col gap-px size-full">
|
||||
<Header label={column.label} name={column.name} />
|
||||
<Header label={column.label} />
|
||||
<div ref={container} className="flex-1 size-full">
|
||||
{!isCreated ? (
|
||||
<div className="size-full flex items-center justify-center">
|
||||
@@ -101,10 +103,14 @@ export const Column = memo(function Column({ column }: { column: LumeColumn }) {
|
||||
);
|
||||
});
|
||||
|
||||
function Header({ label, name }: { label: string; name: string }) {
|
||||
function Header({ label }: { label: string }) {
|
||||
const [title, setTitle] = useState("");
|
||||
const [isChanged, setIsChanged] = useState(false);
|
||||
|
||||
const column = useStore(appColumns, (state) =>
|
||||
state.find((col) => col.label === label),
|
||||
);
|
||||
|
||||
const saveNewTitle = async () => {
|
||||
const mainWindow = getCurrentWindow();
|
||||
await mainWindow.emit("columns", { type: "set_title", label, title });
|
||||
@@ -186,7 +192,7 @@ function Header({ label, name }: { label: string; name: string }) {
|
||||
onBlur={(e) => setTitle(e.currentTarget.textContent)}
|
||||
className="text-[12px] font-semibold focus:outline-none"
|
||||
>
|
||||
{name}
|
||||
{column.name}
|
||||
</div>
|
||||
{isChanged ? (
|
||||
<button
|
||||
|
||||
@@ -1,13 +1,7 @@
|
||||
import { LumeWindow } from "@/system";
|
||||
|
||||
export function Hashtag({ tag }: { tag: string }) {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => LumeWindow.openHashtag(tag)}
|
||||
className="leading-normal cursor-default text-blue-500 hover:text-blue-600 font-normal"
|
||||
>
|
||||
<span className="leading-normal cursor-default text-blue-500 hover:text-blue-600 font-normal">
|
||||
{tag}
|
||||
</button>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -13,6 +13,8 @@ import { createFileRoute } from '@tanstack/react-router'
|
||||
// Import Routes
|
||||
|
||||
import { Route as rootRoute } from './routes/__root'
|
||||
import { Route as SetInterestImport } from './routes/set-interest'
|
||||
import { Route as SetGroupImport } from './routes/set-group'
|
||||
import { Route as LoadingImport } from './routes/loading'
|
||||
import { Route as BootstrapRelaysImport } from './routes/bootstrap-relays'
|
||||
import { Route as IndexImport } from './routes/index'
|
||||
@@ -22,17 +24,17 @@ import { Route as ColumnsLayoutImport } from './routes/columns/_layout'
|
||||
import { Route as AccountBackupImport } from './routes/$account/backup'
|
||||
import { Route as AccountAppImport } from './routes/$account/_app'
|
||||
import { Route as ColumnsLayoutStoriesImport } from './routes/columns/_layout/stories'
|
||||
import { Route as ColumnsLayoutGroupImport } from './routes/columns/_layout/group'
|
||||
import { Route as ColumnsLayoutNewsfeedImport } from './routes/columns/_layout/newsfeed'
|
||||
import { Route as ColumnsLayoutGlobalImport } from './routes/columns/_layout/global'
|
||||
import { Route as ColumnsLayoutGalleryImport } from './routes/columns/_layout/gallery'
|
||||
import { Route as ColumnsLayoutCreateNewsfeedImport } from './routes/columns/_layout/create-newsfeed'
|
||||
import { Route as ColumnsLayoutCreateGroupImport } from './routes/columns/_layout/create-group'
|
||||
import { Route as AccountSettingsWalletImport } from './routes/$account/_settings/wallet'
|
||||
import { Route as AccountSettingsRelayImport } from './routes/$account/_settings/relay'
|
||||
import { Route as AccountSettingsProfileImport } from './routes/$account/_settings/profile'
|
||||
import { Route as AccountSettingsGeneralImport } from './routes/$account/_settings/general'
|
||||
import { Route as AccountSettingsBitcoinConnectImport } from './routes/$account/_settings/bitcoin-connect'
|
||||
import { Route as AccountAppHomeImport } from './routes/$account/_app/home'
|
||||
import { Route as ColumnsLayoutInterestsIdImport } from './routes/columns/_layout/interests.$id'
|
||||
import { Route as ColumnsLayoutGroupsIdImport } from './routes/columns/_layout/groups.$id'
|
||||
import { Route as ColumnsLayoutCreateNewsfeedUsersImport } from './routes/columns/_layout/create-newsfeed.users'
|
||||
import { Route as ColumnsLayoutCreateNewsfeedF2fImport } from './routes/columns/_layout/create-newsfeed.f2f'
|
||||
|
||||
@@ -58,8 +60,8 @@ const ColumnsLayoutOnboardingLazyImport = createFileRoute(
|
||||
const ColumnsLayoutNotificationLazyImport = createFileRoute(
|
||||
'/columns/_layout/notification',
|
||||
)()
|
||||
const ColumnsLayoutNewsfeedLazyImport = createFileRoute(
|
||||
'/columns/_layout/newsfeed',
|
||||
const ColumnsLayoutGalleryLazyImport = createFileRoute(
|
||||
'/columns/_layout/gallery',
|
||||
)()
|
||||
const ColumnsLayoutUsersIdLazyImport = createFileRoute(
|
||||
'/columns/_layout/users/$id',
|
||||
@@ -67,9 +69,6 @@ const ColumnsLayoutUsersIdLazyImport = createFileRoute(
|
||||
const ColumnsLayoutRepliesIdLazyImport = createFileRoute(
|
||||
'/columns/_layout/replies/$id',
|
||||
)()
|
||||
const ColumnsLayoutHashtagsContentLazyImport = createFileRoute(
|
||||
'/columns/_layout/hashtags/$content',
|
||||
)()
|
||||
const ColumnsLayoutEventsIdLazyImport = createFileRoute(
|
||||
'/columns/_layout/events/$id',
|
||||
)()
|
||||
@@ -96,6 +95,16 @@ const NewLazyRoute = NewLazyImport.update({
|
||||
getParentRoute: () => rootRoute,
|
||||
} as any).lazy(() => import('./routes/new.lazy').then((d) => d.Route))
|
||||
|
||||
const SetInterestRoute = SetInterestImport.update({
|
||||
path: '/set-interest',
|
||||
getParentRoute: () => rootRoute,
|
||||
} as any).lazy(() => import('./routes/set-interest.lazy').then((d) => d.Route))
|
||||
|
||||
const SetGroupRoute = SetGroupImport.update({
|
||||
path: '/set-group',
|
||||
getParentRoute: () => rootRoute,
|
||||
} as any).lazy(() => import('./routes/set-group.lazy').then((d) => d.Route))
|
||||
|
||||
const LoadingRoute = LoadingImport.update({
|
||||
path: '/loading',
|
||||
getParentRoute: () => rootRoute,
|
||||
@@ -190,11 +199,11 @@ const ColumnsLayoutNotificationLazyRoute =
|
||||
import('./routes/columns/_layout/notification.lazy').then((d) => d.Route),
|
||||
)
|
||||
|
||||
const ColumnsLayoutNewsfeedLazyRoute = ColumnsLayoutNewsfeedLazyImport.update({
|
||||
path: '/newsfeed',
|
||||
const ColumnsLayoutGalleryLazyRoute = ColumnsLayoutGalleryLazyImport.update({
|
||||
path: '/gallery',
|
||||
getParentRoute: () => ColumnsLayoutRoute,
|
||||
} as any).lazy(() =>
|
||||
import('./routes/columns/_layout/newsfeed.lazy').then((d) => d.Route),
|
||||
import('./routes/columns/_layout/gallery.lazy').then((d) => d.Route),
|
||||
)
|
||||
|
||||
const ColumnsLayoutStoriesRoute = ColumnsLayoutStoriesImport.update({
|
||||
@@ -204,11 +213,11 @@ const ColumnsLayoutStoriesRoute = ColumnsLayoutStoriesImport.update({
|
||||
import('./routes/columns/_layout/stories.lazy').then((d) => d.Route),
|
||||
)
|
||||
|
||||
const ColumnsLayoutGroupRoute = ColumnsLayoutGroupImport.update({
|
||||
path: '/group',
|
||||
const ColumnsLayoutNewsfeedRoute = ColumnsLayoutNewsfeedImport.update({
|
||||
path: '/newsfeed',
|
||||
getParentRoute: () => ColumnsLayoutRoute,
|
||||
} as any).lazy(() =>
|
||||
import('./routes/columns/_layout/group.lazy').then((d) => d.Route),
|
||||
import('./routes/columns/_layout/newsfeed.lazy').then((d) => d.Route),
|
||||
)
|
||||
|
||||
const ColumnsLayoutGlobalRoute = ColumnsLayoutGlobalImport.update({
|
||||
@@ -216,26 +225,12 @@ const ColumnsLayoutGlobalRoute = ColumnsLayoutGlobalImport.update({
|
||||
getParentRoute: () => ColumnsLayoutRoute,
|
||||
} as any)
|
||||
|
||||
const ColumnsLayoutGalleryRoute = ColumnsLayoutGalleryImport.update({
|
||||
path: '/gallery',
|
||||
getParentRoute: () => ColumnsLayoutRoute,
|
||||
} as any).lazy(() =>
|
||||
import('./routes/columns/_layout/gallery.lazy').then((d) => d.Route),
|
||||
)
|
||||
|
||||
const ColumnsLayoutCreateNewsfeedRoute =
|
||||
ColumnsLayoutCreateNewsfeedImport.update({
|
||||
path: '/create-newsfeed',
|
||||
getParentRoute: () => ColumnsLayoutRoute,
|
||||
} as any)
|
||||
|
||||
const ColumnsLayoutCreateGroupRoute = ColumnsLayoutCreateGroupImport.update({
|
||||
path: '/create-group',
|
||||
getParentRoute: () => ColumnsLayoutRoute,
|
||||
} as any).lazy(() =>
|
||||
import('./routes/columns/_layout/create-group.lazy').then((d) => d.Route),
|
||||
)
|
||||
|
||||
const AccountSettingsWalletRoute = AccountSettingsWalletImport.update({
|
||||
path: '/wallet',
|
||||
getParentRoute: () => AccountSettingsLazyRoute,
|
||||
@@ -297,16 +292,6 @@ const ColumnsLayoutRepliesIdLazyRoute = ColumnsLayoutRepliesIdLazyImport.update(
|
||||
import('./routes/columns/_layout/replies.$id.lazy').then((d) => d.Route),
|
||||
)
|
||||
|
||||
const ColumnsLayoutHashtagsContentLazyRoute =
|
||||
ColumnsLayoutHashtagsContentLazyImport.update({
|
||||
path: '/hashtags/$content',
|
||||
getParentRoute: () => ColumnsLayoutRoute,
|
||||
} as any).lazy(() =>
|
||||
import('./routes/columns/_layout/hashtags.$content.lazy').then(
|
||||
(d) => d.Route,
|
||||
),
|
||||
)
|
||||
|
||||
const ColumnsLayoutEventsIdLazyRoute = ColumnsLayoutEventsIdLazyImport.update({
|
||||
path: '/events/$id',
|
||||
getParentRoute: () => ColumnsLayoutRoute,
|
||||
@@ -314,6 +299,20 @@ const ColumnsLayoutEventsIdLazyRoute = ColumnsLayoutEventsIdLazyImport.update({
|
||||
import('./routes/columns/_layout/events.$id.lazy').then((d) => d.Route),
|
||||
)
|
||||
|
||||
const ColumnsLayoutInterestsIdRoute = ColumnsLayoutInterestsIdImport.update({
|
||||
path: '/interests/$id',
|
||||
getParentRoute: () => ColumnsLayoutRoute,
|
||||
} as any).lazy(() =>
|
||||
import('./routes/columns/_layout/interests.$id.lazy').then((d) => d.Route),
|
||||
)
|
||||
|
||||
const ColumnsLayoutGroupsIdRoute = ColumnsLayoutGroupsIdImport.update({
|
||||
path: '/groups/$id',
|
||||
getParentRoute: () => ColumnsLayoutRoute,
|
||||
} as any).lazy(() =>
|
||||
import('./routes/columns/_layout/groups.$id.lazy').then((d) => d.Route),
|
||||
)
|
||||
|
||||
const ColumnsLayoutCreateNewsfeedUsersRoute =
|
||||
ColumnsLayoutCreateNewsfeedUsersImport.update({
|
||||
path: '/users',
|
||||
@@ -351,6 +350,20 @@ declare module '@tanstack/react-router' {
|
||||
preLoaderRoute: typeof LoadingImport
|
||||
parentRoute: typeof rootRoute
|
||||
}
|
||||
'/set-group': {
|
||||
id: '/set-group'
|
||||
path: '/set-group'
|
||||
fullPath: '/set-group'
|
||||
preLoaderRoute: typeof SetGroupImport
|
||||
parentRoute: typeof rootRoute
|
||||
}
|
||||
'/set-interest': {
|
||||
id: '/set-interest'
|
||||
path: '/set-interest'
|
||||
fullPath: '/set-interest'
|
||||
preLoaderRoute: typeof SetInterestImport
|
||||
parentRoute: typeof rootRoute
|
||||
}
|
||||
'/new': {
|
||||
id: '/new'
|
||||
path: '/new'
|
||||
@@ -484,13 +497,6 @@ declare module '@tanstack/react-router' {
|
||||
preLoaderRoute: typeof AccountSettingsWalletImport
|
||||
parentRoute: typeof AccountSettingsLazyImport
|
||||
}
|
||||
'/columns/_layout/create-group': {
|
||||
id: '/columns/_layout/create-group'
|
||||
path: '/create-group'
|
||||
fullPath: '/columns/create-group'
|
||||
preLoaderRoute: typeof ColumnsLayoutCreateGroupImport
|
||||
parentRoute: typeof ColumnsLayoutImport
|
||||
}
|
||||
'/columns/_layout/create-newsfeed': {
|
||||
id: '/columns/_layout/create-newsfeed'
|
||||
path: '/create-newsfeed'
|
||||
@@ -498,13 +504,6 @@ declare module '@tanstack/react-router' {
|
||||
preLoaderRoute: typeof ColumnsLayoutCreateNewsfeedImport
|
||||
parentRoute: typeof ColumnsLayoutImport
|
||||
}
|
||||
'/columns/_layout/gallery': {
|
||||
id: '/columns/_layout/gallery'
|
||||
path: '/gallery'
|
||||
fullPath: '/columns/gallery'
|
||||
preLoaderRoute: typeof ColumnsLayoutGalleryImport
|
||||
parentRoute: typeof ColumnsLayoutImport
|
||||
}
|
||||
'/columns/_layout/global': {
|
||||
id: '/columns/_layout/global'
|
||||
path: '/global'
|
||||
@@ -512,11 +511,11 @@ declare module '@tanstack/react-router' {
|
||||
preLoaderRoute: typeof ColumnsLayoutGlobalImport
|
||||
parentRoute: typeof ColumnsLayoutImport
|
||||
}
|
||||
'/columns/_layout/group': {
|
||||
id: '/columns/_layout/group'
|
||||
path: '/group'
|
||||
fullPath: '/columns/group'
|
||||
preLoaderRoute: typeof ColumnsLayoutGroupImport
|
||||
'/columns/_layout/newsfeed': {
|
||||
id: '/columns/_layout/newsfeed'
|
||||
path: '/newsfeed'
|
||||
fullPath: '/columns/newsfeed'
|
||||
preLoaderRoute: typeof ColumnsLayoutNewsfeedImport
|
||||
parentRoute: typeof ColumnsLayoutImport
|
||||
}
|
||||
'/columns/_layout/stories': {
|
||||
@@ -526,11 +525,11 @@ declare module '@tanstack/react-router' {
|
||||
preLoaderRoute: typeof ColumnsLayoutStoriesImport
|
||||
parentRoute: typeof ColumnsLayoutImport
|
||||
}
|
||||
'/columns/_layout/newsfeed': {
|
||||
id: '/columns/_layout/newsfeed'
|
||||
path: '/newsfeed'
|
||||
fullPath: '/columns/newsfeed'
|
||||
preLoaderRoute: typeof ColumnsLayoutNewsfeedLazyImport
|
||||
'/columns/_layout/gallery': {
|
||||
id: '/columns/_layout/gallery'
|
||||
path: '/gallery'
|
||||
fullPath: '/columns/gallery'
|
||||
preLoaderRoute: typeof ColumnsLayoutGalleryLazyImport
|
||||
parentRoute: typeof ColumnsLayoutImport
|
||||
}
|
||||
'/columns/_layout/notification': {
|
||||
@@ -575,6 +574,20 @@ declare module '@tanstack/react-router' {
|
||||
preLoaderRoute: typeof ColumnsLayoutCreateNewsfeedUsersImport
|
||||
parentRoute: typeof ColumnsLayoutCreateNewsfeedImport
|
||||
}
|
||||
'/columns/_layout/groups/$id': {
|
||||
id: '/columns/_layout/groups/$id'
|
||||
path: '/groups/$id'
|
||||
fullPath: '/columns/groups/$id'
|
||||
preLoaderRoute: typeof ColumnsLayoutGroupsIdImport
|
||||
parentRoute: typeof ColumnsLayoutImport
|
||||
}
|
||||
'/columns/_layout/interests/$id': {
|
||||
id: '/columns/_layout/interests/$id'
|
||||
path: '/interests/$id'
|
||||
fullPath: '/columns/interests/$id'
|
||||
preLoaderRoute: typeof ColumnsLayoutInterestsIdImport
|
||||
parentRoute: typeof ColumnsLayoutImport
|
||||
}
|
||||
'/columns/_layout/events/$id': {
|
||||
id: '/columns/_layout/events/$id'
|
||||
path: '/events/$id'
|
||||
@@ -582,13 +595,6 @@ declare module '@tanstack/react-router' {
|
||||
preLoaderRoute: typeof ColumnsLayoutEventsIdLazyImport
|
||||
parentRoute: typeof ColumnsLayoutImport
|
||||
}
|
||||
'/columns/_layout/hashtags/$content': {
|
||||
id: '/columns/_layout/hashtags/$content'
|
||||
path: '/hashtags/$content'
|
||||
fullPath: '/columns/hashtags/$content'
|
||||
preLoaderRoute: typeof ColumnsLayoutHashtagsContentLazyImport
|
||||
parentRoute: typeof ColumnsLayoutImport
|
||||
}
|
||||
'/columns/_layout/replies/$id': {
|
||||
id: '/columns/_layout/replies/$id'
|
||||
path: '/replies/$id'
|
||||
@@ -672,38 +678,36 @@ const ColumnsLayoutCreateNewsfeedRouteWithChildren =
|
||||
)
|
||||
|
||||
interface ColumnsLayoutRouteChildren {
|
||||
ColumnsLayoutCreateGroupRoute: typeof ColumnsLayoutCreateGroupRoute
|
||||
ColumnsLayoutCreateNewsfeedRoute: typeof ColumnsLayoutCreateNewsfeedRouteWithChildren
|
||||
ColumnsLayoutGalleryRoute: typeof ColumnsLayoutGalleryRoute
|
||||
ColumnsLayoutGlobalRoute: typeof ColumnsLayoutGlobalRoute
|
||||
ColumnsLayoutGroupRoute: typeof ColumnsLayoutGroupRoute
|
||||
ColumnsLayoutNewsfeedRoute: typeof ColumnsLayoutNewsfeedRoute
|
||||
ColumnsLayoutStoriesRoute: typeof ColumnsLayoutStoriesRoute
|
||||
ColumnsLayoutNewsfeedLazyRoute: typeof ColumnsLayoutNewsfeedLazyRoute
|
||||
ColumnsLayoutGalleryLazyRoute: typeof ColumnsLayoutGalleryLazyRoute
|
||||
ColumnsLayoutNotificationLazyRoute: typeof ColumnsLayoutNotificationLazyRoute
|
||||
ColumnsLayoutOnboardingLazyRoute: typeof ColumnsLayoutOnboardingLazyRoute
|
||||
ColumnsLayoutSearchLazyRoute: typeof ColumnsLayoutSearchLazyRoute
|
||||
ColumnsLayoutTrendingLazyRoute: typeof ColumnsLayoutTrendingLazyRoute
|
||||
ColumnsLayoutGroupsIdRoute: typeof ColumnsLayoutGroupsIdRoute
|
||||
ColumnsLayoutInterestsIdRoute: typeof ColumnsLayoutInterestsIdRoute
|
||||
ColumnsLayoutEventsIdLazyRoute: typeof ColumnsLayoutEventsIdLazyRoute
|
||||
ColumnsLayoutHashtagsContentLazyRoute: typeof ColumnsLayoutHashtagsContentLazyRoute
|
||||
ColumnsLayoutRepliesIdLazyRoute: typeof ColumnsLayoutRepliesIdLazyRoute
|
||||
ColumnsLayoutUsersIdLazyRoute: typeof ColumnsLayoutUsersIdLazyRoute
|
||||
}
|
||||
|
||||
const ColumnsLayoutRouteChildren: ColumnsLayoutRouteChildren = {
|
||||
ColumnsLayoutCreateGroupRoute: ColumnsLayoutCreateGroupRoute,
|
||||
ColumnsLayoutCreateNewsfeedRoute:
|
||||
ColumnsLayoutCreateNewsfeedRouteWithChildren,
|
||||
ColumnsLayoutGalleryRoute: ColumnsLayoutGalleryRoute,
|
||||
ColumnsLayoutGlobalRoute: ColumnsLayoutGlobalRoute,
|
||||
ColumnsLayoutGroupRoute: ColumnsLayoutGroupRoute,
|
||||
ColumnsLayoutNewsfeedRoute: ColumnsLayoutNewsfeedRoute,
|
||||
ColumnsLayoutStoriesRoute: ColumnsLayoutStoriesRoute,
|
||||
ColumnsLayoutNewsfeedLazyRoute: ColumnsLayoutNewsfeedLazyRoute,
|
||||
ColumnsLayoutGalleryLazyRoute: ColumnsLayoutGalleryLazyRoute,
|
||||
ColumnsLayoutNotificationLazyRoute: ColumnsLayoutNotificationLazyRoute,
|
||||
ColumnsLayoutOnboardingLazyRoute: ColumnsLayoutOnboardingLazyRoute,
|
||||
ColumnsLayoutSearchLazyRoute: ColumnsLayoutSearchLazyRoute,
|
||||
ColumnsLayoutTrendingLazyRoute: ColumnsLayoutTrendingLazyRoute,
|
||||
ColumnsLayoutGroupsIdRoute: ColumnsLayoutGroupsIdRoute,
|
||||
ColumnsLayoutInterestsIdRoute: ColumnsLayoutInterestsIdRoute,
|
||||
ColumnsLayoutEventsIdLazyRoute: ColumnsLayoutEventsIdLazyRoute,
|
||||
ColumnsLayoutHashtagsContentLazyRoute: ColumnsLayoutHashtagsContentLazyRoute,
|
||||
ColumnsLayoutRepliesIdLazyRoute: ColumnsLayoutRepliesIdLazyRoute,
|
||||
ColumnsLayoutUsersIdLazyRoute: ColumnsLayoutUsersIdLazyRoute,
|
||||
}
|
||||
@@ -727,6 +731,8 @@ export interface FileRoutesByFullPath {
|
||||
'/': typeof IndexRoute
|
||||
'/bootstrap-relays': typeof BootstrapRelaysRoute
|
||||
'/loading': typeof LoadingRoute
|
||||
'/set-group': typeof SetGroupRoute
|
||||
'/set-interest': typeof SetInterestRoute
|
||||
'/new': typeof NewLazyRoute
|
||||
'/reset': typeof ResetLazyRoute
|
||||
'/$account': typeof AccountSettingsLazyRouteWithChildren
|
||||
@@ -743,21 +749,20 @@ export interface FileRoutesByFullPath {
|
||||
'/$account/profile': typeof AccountSettingsProfileRoute
|
||||
'/$account/relay': typeof AccountSettingsRelayRoute
|
||||
'/$account/wallet': typeof AccountSettingsWalletRoute
|
||||
'/columns/create-group': typeof ColumnsLayoutCreateGroupRoute
|
||||
'/columns/create-newsfeed': typeof ColumnsLayoutCreateNewsfeedRouteWithChildren
|
||||
'/columns/gallery': typeof ColumnsLayoutGalleryRoute
|
||||
'/columns/global': typeof ColumnsLayoutGlobalRoute
|
||||
'/columns/group': typeof ColumnsLayoutGroupRoute
|
||||
'/columns/newsfeed': typeof ColumnsLayoutNewsfeedRoute
|
||||
'/columns/stories': typeof ColumnsLayoutStoriesRoute
|
||||
'/columns/newsfeed': typeof ColumnsLayoutNewsfeedLazyRoute
|
||||
'/columns/gallery': typeof ColumnsLayoutGalleryLazyRoute
|
||||
'/columns/notification': typeof ColumnsLayoutNotificationLazyRoute
|
||||
'/columns/onboarding': typeof ColumnsLayoutOnboardingLazyRoute
|
||||
'/columns/search': typeof ColumnsLayoutSearchLazyRoute
|
||||
'/columns/trending': typeof ColumnsLayoutTrendingLazyRoute
|
||||
'/columns/create-newsfeed/f2f': typeof ColumnsLayoutCreateNewsfeedF2fRoute
|
||||
'/columns/create-newsfeed/users': typeof ColumnsLayoutCreateNewsfeedUsersRoute
|
||||
'/columns/groups/$id': typeof ColumnsLayoutGroupsIdRoute
|
||||
'/columns/interests/$id': typeof ColumnsLayoutInterestsIdRoute
|
||||
'/columns/events/$id': typeof ColumnsLayoutEventsIdLazyRoute
|
||||
'/columns/hashtags/$content': typeof ColumnsLayoutHashtagsContentLazyRoute
|
||||
'/columns/replies/$id': typeof ColumnsLayoutRepliesIdLazyRoute
|
||||
'/columns/users/$id': typeof ColumnsLayoutUsersIdLazyRoute
|
||||
}
|
||||
@@ -766,6 +771,8 @@ export interface FileRoutesByTo {
|
||||
'/': typeof IndexRoute
|
||||
'/bootstrap-relays': typeof BootstrapRelaysRoute
|
||||
'/loading': typeof LoadingRoute
|
||||
'/set-group': typeof SetGroupRoute
|
||||
'/set-interest': typeof SetInterestRoute
|
||||
'/new': typeof NewLazyRoute
|
||||
'/reset': typeof ResetLazyRoute
|
||||
'/$account': typeof AccountSettingsLazyRouteWithChildren
|
||||
@@ -782,21 +789,20 @@ export interface FileRoutesByTo {
|
||||
'/$account/profile': typeof AccountSettingsProfileRoute
|
||||
'/$account/relay': typeof AccountSettingsRelayRoute
|
||||
'/$account/wallet': typeof AccountSettingsWalletRoute
|
||||
'/columns/create-group': typeof ColumnsLayoutCreateGroupRoute
|
||||
'/columns/create-newsfeed': typeof ColumnsLayoutCreateNewsfeedRouteWithChildren
|
||||
'/columns/gallery': typeof ColumnsLayoutGalleryRoute
|
||||
'/columns/global': typeof ColumnsLayoutGlobalRoute
|
||||
'/columns/group': typeof ColumnsLayoutGroupRoute
|
||||
'/columns/newsfeed': typeof ColumnsLayoutNewsfeedRoute
|
||||
'/columns/stories': typeof ColumnsLayoutStoriesRoute
|
||||
'/columns/newsfeed': typeof ColumnsLayoutNewsfeedLazyRoute
|
||||
'/columns/gallery': typeof ColumnsLayoutGalleryLazyRoute
|
||||
'/columns/notification': typeof ColumnsLayoutNotificationLazyRoute
|
||||
'/columns/onboarding': typeof ColumnsLayoutOnboardingLazyRoute
|
||||
'/columns/search': typeof ColumnsLayoutSearchLazyRoute
|
||||
'/columns/trending': typeof ColumnsLayoutTrendingLazyRoute
|
||||
'/columns/create-newsfeed/f2f': typeof ColumnsLayoutCreateNewsfeedF2fRoute
|
||||
'/columns/create-newsfeed/users': typeof ColumnsLayoutCreateNewsfeedUsersRoute
|
||||
'/columns/groups/$id': typeof ColumnsLayoutGroupsIdRoute
|
||||
'/columns/interests/$id': typeof ColumnsLayoutInterestsIdRoute
|
||||
'/columns/events/$id': typeof ColumnsLayoutEventsIdLazyRoute
|
||||
'/columns/hashtags/$content': typeof ColumnsLayoutHashtagsContentLazyRoute
|
||||
'/columns/replies/$id': typeof ColumnsLayoutRepliesIdLazyRoute
|
||||
'/columns/users/$id': typeof ColumnsLayoutUsersIdLazyRoute
|
||||
}
|
||||
@@ -806,6 +812,8 @@ export interface FileRoutesById {
|
||||
'/': typeof IndexRoute
|
||||
'/bootstrap-relays': typeof BootstrapRelaysRoute
|
||||
'/loading': typeof LoadingRoute
|
||||
'/set-group': typeof SetGroupRoute
|
||||
'/set-interest': typeof SetInterestRoute
|
||||
'/new': typeof NewLazyRoute
|
||||
'/reset': typeof ResetLazyRoute
|
||||
'/$account': typeof AccountRouteWithChildren
|
||||
@@ -825,21 +833,20 @@ export interface FileRoutesById {
|
||||
'/$account/_settings/profile': typeof AccountSettingsProfileRoute
|
||||
'/$account/_settings/relay': typeof AccountSettingsRelayRoute
|
||||
'/$account/_settings/wallet': typeof AccountSettingsWalletRoute
|
||||
'/columns/_layout/create-group': typeof ColumnsLayoutCreateGroupRoute
|
||||
'/columns/_layout/create-newsfeed': typeof ColumnsLayoutCreateNewsfeedRouteWithChildren
|
||||
'/columns/_layout/gallery': typeof ColumnsLayoutGalleryRoute
|
||||
'/columns/_layout/global': typeof ColumnsLayoutGlobalRoute
|
||||
'/columns/_layout/group': typeof ColumnsLayoutGroupRoute
|
||||
'/columns/_layout/newsfeed': typeof ColumnsLayoutNewsfeedRoute
|
||||
'/columns/_layout/stories': typeof ColumnsLayoutStoriesRoute
|
||||
'/columns/_layout/newsfeed': typeof ColumnsLayoutNewsfeedLazyRoute
|
||||
'/columns/_layout/gallery': typeof ColumnsLayoutGalleryLazyRoute
|
||||
'/columns/_layout/notification': typeof ColumnsLayoutNotificationLazyRoute
|
||||
'/columns/_layout/onboarding': typeof ColumnsLayoutOnboardingLazyRoute
|
||||
'/columns/_layout/search': typeof ColumnsLayoutSearchLazyRoute
|
||||
'/columns/_layout/trending': typeof ColumnsLayoutTrendingLazyRoute
|
||||
'/columns/_layout/create-newsfeed/f2f': typeof ColumnsLayoutCreateNewsfeedF2fRoute
|
||||
'/columns/_layout/create-newsfeed/users': typeof ColumnsLayoutCreateNewsfeedUsersRoute
|
||||
'/columns/_layout/groups/$id': typeof ColumnsLayoutGroupsIdRoute
|
||||
'/columns/_layout/interests/$id': typeof ColumnsLayoutInterestsIdRoute
|
||||
'/columns/_layout/events/$id': typeof ColumnsLayoutEventsIdLazyRoute
|
||||
'/columns/_layout/hashtags/$content': typeof ColumnsLayoutHashtagsContentLazyRoute
|
||||
'/columns/_layout/replies/$id': typeof ColumnsLayoutRepliesIdLazyRoute
|
||||
'/columns/_layout/users/$id': typeof ColumnsLayoutUsersIdLazyRoute
|
||||
}
|
||||
@@ -850,6 +857,8 @@ export interface FileRouteTypes {
|
||||
| '/'
|
||||
| '/bootstrap-relays'
|
||||
| '/loading'
|
||||
| '/set-group'
|
||||
| '/set-interest'
|
||||
| '/new'
|
||||
| '/reset'
|
||||
| '/$account'
|
||||
@@ -866,21 +875,20 @@ export interface FileRouteTypes {
|
||||
| '/$account/profile'
|
||||
| '/$account/relay'
|
||||
| '/$account/wallet'
|
||||
| '/columns/create-group'
|
||||
| '/columns/create-newsfeed'
|
||||
| '/columns/gallery'
|
||||
| '/columns/global'
|
||||
| '/columns/group'
|
||||
| '/columns/stories'
|
||||
| '/columns/newsfeed'
|
||||
| '/columns/stories'
|
||||
| '/columns/gallery'
|
||||
| '/columns/notification'
|
||||
| '/columns/onboarding'
|
||||
| '/columns/search'
|
||||
| '/columns/trending'
|
||||
| '/columns/create-newsfeed/f2f'
|
||||
| '/columns/create-newsfeed/users'
|
||||
| '/columns/groups/$id'
|
||||
| '/columns/interests/$id'
|
||||
| '/columns/events/$id'
|
||||
| '/columns/hashtags/$content'
|
||||
| '/columns/replies/$id'
|
||||
| '/columns/users/$id'
|
||||
fileRoutesByTo: FileRoutesByTo
|
||||
@@ -888,6 +896,8 @@ export interface FileRouteTypes {
|
||||
| '/'
|
||||
| '/bootstrap-relays'
|
||||
| '/loading'
|
||||
| '/set-group'
|
||||
| '/set-interest'
|
||||
| '/new'
|
||||
| '/reset'
|
||||
| '/$account'
|
||||
@@ -904,21 +914,20 @@ export interface FileRouteTypes {
|
||||
| '/$account/profile'
|
||||
| '/$account/relay'
|
||||
| '/$account/wallet'
|
||||
| '/columns/create-group'
|
||||
| '/columns/create-newsfeed'
|
||||
| '/columns/gallery'
|
||||
| '/columns/global'
|
||||
| '/columns/group'
|
||||
| '/columns/stories'
|
||||
| '/columns/newsfeed'
|
||||
| '/columns/stories'
|
||||
| '/columns/gallery'
|
||||
| '/columns/notification'
|
||||
| '/columns/onboarding'
|
||||
| '/columns/search'
|
||||
| '/columns/trending'
|
||||
| '/columns/create-newsfeed/f2f'
|
||||
| '/columns/create-newsfeed/users'
|
||||
| '/columns/groups/$id'
|
||||
| '/columns/interests/$id'
|
||||
| '/columns/events/$id'
|
||||
| '/columns/hashtags/$content'
|
||||
| '/columns/replies/$id'
|
||||
| '/columns/users/$id'
|
||||
id:
|
||||
@@ -926,6 +935,8 @@ export interface FileRouteTypes {
|
||||
| '/'
|
||||
| '/bootstrap-relays'
|
||||
| '/loading'
|
||||
| '/set-group'
|
||||
| '/set-interest'
|
||||
| '/new'
|
||||
| '/reset'
|
||||
| '/$account'
|
||||
@@ -945,21 +956,20 @@ export interface FileRouteTypes {
|
||||
| '/$account/_settings/profile'
|
||||
| '/$account/_settings/relay'
|
||||
| '/$account/_settings/wallet'
|
||||
| '/columns/_layout/create-group'
|
||||
| '/columns/_layout/create-newsfeed'
|
||||
| '/columns/_layout/gallery'
|
||||
| '/columns/_layout/global'
|
||||
| '/columns/_layout/group'
|
||||
| '/columns/_layout/stories'
|
||||
| '/columns/_layout/newsfeed'
|
||||
| '/columns/_layout/stories'
|
||||
| '/columns/_layout/gallery'
|
||||
| '/columns/_layout/notification'
|
||||
| '/columns/_layout/onboarding'
|
||||
| '/columns/_layout/search'
|
||||
| '/columns/_layout/trending'
|
||||
| '/columns/_layout/create-newsfeed/f2f'
|
||||
| '/columns/_layout/create-newsfeed/users'
|
||||
| '/columns/_layout/groups/$id'
|
||||
| '/columns/_layout/interests/$id'
|
||||
| '/columns/_layout/events/$id'
|
||||
| '/columns/_layout/hashtags/$content'
|
||||
| '/columns/_layout/replies/$id'
|
||||
| '/columns/_layout/users/$id'
|
||||
fileRoutesById: FileRoutesById
|
||||
@@ -969,6 +979,8 @@ export interface RootRouteChildren {
|
||||
IndexRoute: typeof IndexRoute
|
||||
BootstrapRelaysRoute: typeof BootstrapRelaysRoute
|
||||
LoadingRoute: typeof LoadingRoute
|
||||
SetGroupRoute: typeof SetGroupRoute
|
||||
SetInterestRoute: typeof SetInterestRoute
|
||||
NewLazyRoute: typeof NewLazyRoute
|
||||
ResetLazyRoute: typeof ResetLazyRoute
|
||||
AccountRoute: typeof AccountRouteWithChildren
|
||||
@@ -984,6 +996,8 @@ const rootRouteChildren: RootRouteChildren = {
|
||||
IndexRoute: IndexRoute,
|
||||
BootstrapRelaysRoute: BootstrapRelaysRoute,
|
||||
LoadingRoute: LoadingRoute,
|
||||
SetGroupRoute: SetGroupRoute,
|
||||
SetInterestRoute: SetInterestRoute,
|
||||
NewLazyRoute: NewLazyRoute,
|
||||
ResetLazyRoute: ResetLazyRoute,
|
||||
AccountRoute: AccountRouteWithChildren,
|
||||
@@ -1010,6 +1024,8 @@ export const routeTree = rootRoute
|
||||
"/",
|
||||
"/bootstrap-relays",
|
||||
"/loading",
|
||||
"/set-group",
|
||||
"/set-interest",
|
||||
"/new",
|
||||
"/reset",
|
||||
"/$account",
|
||||
@@ -1030,6 +1046,12 @@ export const routeTree = rootRoute
|
||||
"/loading": {
|
||||
"filePath": "loading.tsx"
|
||||
},
|
||||
"/set-group": {
|
||||
"filePath": "set-group.tsx"
|
||||
},
|
||||
"/set-interest": {
|
||||
"filePath": "set-interest.tsx"
|
||||
},
|
||||
"/new": {
|
||||
"filePath": "new.lazy.tsx"
|
||||
},
|
||||
@@ -1065,19 +1087,18 @@ export const routeTree = rootRoute
|
||||
"filePath": "columns/_layout.tsx",
|
||||
"parent": "/columns",
|
||||
"children": [
|
||||
"/columns/_layout/create-group",
|
||||
"/columns/_layout/create-newsfeed",
|
||||
"/columns/_layout/gallery",
|
||||
"/columns/_layout/global",
|
||||
"/columns/_layout/group",
|
||||
"/columns/_layout/stories",
|
||||
"/columns/_layout/newsfeed",
|
||||
"/columns/_layout/stories",
|
||||
"/columns/_layout/gallery",
|
||||
"/columns/_layout/notification",
|
||||
"/columns/_layout/onboarding",
|
||||
"/columns/_layout/search",
|
||||
"/columns/_layout/trending",
|
||||
"/columns/_layout/groups/$id",
|
||||
"/columns/_layout/interests/$id",
|
||||
"/columns/_layout/events/$id",
|
||||
"/columns/_layout/hashtags/$content",
|
||||
"/columns/_layout/replies/$id",
|
||||
"/columns/_layout/users/$id"
|
||||
]
|
||||
@@ -1132,10 +1153,6 @@ export const routeTree = rootRoute
|
||||
"filePath": "$account/_settings/wallet.tsx",
|
||||
"parent": "/$account/_settings"
|
||||
},
|
||||
"/columns/_layout/create-group": {
|
||||
"filePath": "columns/_layout/create-group.tsx",
|
||||
"parent": "/columns/_layout"
|
||||
},
|
||||
"/columns/_layout/create-newsfeed": {
|
||||
"filePath": "columns/_layout/create-newsfeed.tsx",
|
||||
"parent": "/columns/_layout",
|
||||
@@ -1144,24 +1161,20 @@ export const routeTree = rootRoute
|
||||
"/columns/_layout/create-newsfeed/users"
|
||||
]
|
||||
},
|
||||
"/columns/_layout/gallery": {
|
||||
"filePath": "columns/_layout/gallery.tsx",
|
||||
"parent": "/columns/_layout"
|
||||
},
|
||||
"/columns/_layout/global": {
|
||||
"filePath": "columns/_layout/global.tsx",
|
||||
"parent": "/columns/_layout"
|
||||
},
|
||||
"/columns/_layout/group": {
|
||||
"filePath": "columns/_layout/group.tsx",
|
||||
"/columns/_layout/newsfeed": {
|
||||
"filePath": "columns/_layout/newsfeed.tsx",
|
||||
"parent": "/columns/_layout"
|
||||
},
|
||||
"/columns/_layout/stories": {
|
||||
"filePath": "columns/_layout/stories.tsx",
|
||||
"parent": "/columns/_layout"
|
||||
},
|
||||
"/columns/_layout/newsfeed": {
|
||||
"filePath": "columns/_layout/newsfeed.lazy.tsx",
|
||||
"/columns/_layout/gallery": {
|
||||
"filePath": "columns/_layout/gallery.lazy.tsx",
|
||||
"parent": "/columns/_layout"
|
||||
},
|
||||
"/columns/_layout/notification": {
|
||||
@@ -1188,12 +1201,16 @@ export const routeTree = rootRoute
|
||||
"filePath": "columns/_layout/create-newsfeed.users.tsx",
|
||||
"parent": "/columns/_layout/create-newsfeed"
|
||||
},
|
||||
"/columns/_layout/events/$id": {
|
||||
"filePath": "columns/_layout/events.$id.lazy.tsx",
|
||||
"/columns/_layout/groups/$id": {
|
||||
"filePath": "columns/_layout/groups.$id.tsx",
|
||||
"parent": "/columns/_layout"
|
||||
},
|
||||
"/columns/_layout/hashtags/$content": {
|
||||
"filePath": "columns/_layout/hashtags.$content.lazy.tsx",
|
||||
"/columns/_layout/interests/$id": {
|
||||
"filePath": "columns/_layout/interests.$id.tsx",
|
||||
"parent": "/columns/_layout"
|
||||
},
|
||||
"/columns/_layout/events/$id": {
|
||||
"filePath": "columns/_layout/events.$id.lazy.tsx",
|
||||
"parent": "/columns/_layout"
|
||||
},
|
||||
"/columns/_layout/replies/$id": {
|
||||
|
||||
@@ -1,15 +1,3 @@
|
||||
import type { LumeColumn } from "@/types";
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
import { resolveResource } from "@tauri-apps/api/path";
|
||||
import { readTextFile } from "@tauri-apps/plugin-fs";
|
||||
|
||||
export const Route = createFileRoute("/$account/_app")({
|
||||
beforeLoad: async () => {
|
||||
const systemPath = "resources/columns.json";
|
||||
const resourcePath = await resolveResource(systemPath);
|
||||
const resourceFile = await readTextFile(resourcePath);
|
||||
const systemColumns: LumeColumn[] = JSON.parse(resourceFile);
|
||||
|
||||
return { systemColumns };
|
||||
},
|
||||
});
|
||||
export const Route = createFileRoute("/$account/_app")();
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
import { commands } from "@/commands.gen";
|
||||
import { appColumns } from "@/commons";
|
||||
import { Spinner } from "@/components";
|
||||
import { Column } from "@/components/column";
|
||||
import { LumeWindow } from "@/system";
|
||||
import type { ColumnEvent, LumeColumn } from "@/types";
|
||||
import { ArrowLeft, ArrowRight, Plus, StackPlus } from "@phosphor-icons/react";
|
||||
import { createLazyFileRoute } from "@tanstack/react-router";
|
||||
import { useStore } from "@tanstack/react-store";
|
||||
import { listen } from "@tauri-apps/api/event";
|
||||
import { Menu, MenuItem, PredefinedMenuItem } from "@tauri-apps/api/menu";
|
||||
import { resolveResource } from "@tauri-apps/api/path";
|
||||
import { getCurrentWindow } from "@tauri-apps/api/window";
|
||||
import { readTextFile } from "@tauri-apps/plugin-fs";
|
||||
import useEmblaCarousel from "embla-carousel-react";
|
||||
import { nanoid } from "nanoid";
|
||||
import {
|
||||
@@ -25,9 +28,8 @@ export const Route = createLazyFileRoute("/$account/_app/home")({
|
||||
});
|
||||
|
||||
function Screen() {
|
||||
const { initialColumns } = Route.useRouteContext();
|
||||
const columns = useStore(appColumns, (state) => state);
|
||||
|
||||
const [columns, setColumns] = useState<LumeColumn[]>([]);
|
||||
const [emblaRef, emblaApi] = useEmblaCarousel({
|
||||
watchDrag: false,
|
||||
loop: false,
|
||||
@@ -51,11 +53,11 @@ function Screen() {
|
||||
|
||||
const add = useDebouncedCallback((column: LumeColumn) => {
|
||||
column.label = `${column.label}-${nanoid()}`; // update col label
|
||||
setColumns((prev) => [column, ...prev]);
|
||||
appColumns.setState((prev) => [column, ...prev]);
|
||||
}, 150);
|
||||
|
||||
const remove = useDebouncedCallback((label: string) => {
|
||||
setColumns((prev) => prev.filter((t) => t.label !== label));
|
||||
appColumns.setState((prev) => prev.filter((t) => t.label !== label));
|
||||
}, 150);
|
||||
|
||||
const move = useDebouncedCallback(
|
||||
@@ -70,12 +72,12 @@ function Screen() {
|
||||
if (direction === "left") newCols.splice(colIndex - 1, 0, col);
|
||||
if (direction === "right") newCols.splice(colIndex + 1, 0, col);
|
||||
|
||||
setColumns(newCols);
|
||||
appColumns.setState(() => newCols);
|
||||
},
|
||||
150,
|
||||
);
|
||||
|
||||
const updateName = useDebouncedCallback((label: string, title: string) => {
|
||||
const update = useDebouncedCallback((label: string, title: string) => {
|
||||
const currentColIndex = columns.findIndex((col) => col.label === label);
|
||||
|
||||
const updatedCol = Object.assign({}, columns[currentColIndex]);
|
||||
@@ -84,10 +86,10 @@ function Screen() {
|
||||
const newCols = columns.slice();
|
||||
newCols[currentColIndex] = updatedCol;
|
||||
|
||||
setColumns(newCols);
|
||||
appColumns.setState(() => newCols);
|
||||
}, 150);
|
||||
|
||||
const reset = useDebouncedCallback(() => setColumns([]), 150);
|
||||
const reset = useDebouncedCallback(() => appColumns.setState(() => []), 150);
|
||||
|
||||
const handleKeyDown = useDebouncedCallback((event) => {
|
||||
if (event.defaultPrevented) return;
|
||||
@@ -106,18 +108,6 @@ function Screen() {
|
||||
event.preventDefault();
|
||||
}, 150);
|
||||
|
||||
const saveAllColumns = useDebouncedCallback(async () => {
|
||||
const key = "lume_v4:columns";
|
||||
const content = JSON.stringify(columns);
|
||||
const res = await commands.setLumeStore(key, content);
|
||||
|
||||
if (res.status === "ok") {
|
||||
return res.data;
|
||||
} else {
|
||||
console.log(res.error);
|
||||
}
|
||||
}, 200);
|
||||
|
||||
useEffect(() => {
|
||||
if (emblaApi) {
|
||||
emblaApi.on("scroll", emitScrollEvent);
|
||||
@@ -132,14 +122,6 @@ function Screen() {
|
||||
};
|
||||
}, [emblaApi, emitScrollEvent, emitResizeEvent]);
|
||||
|
||||
useEffect(() => {
|
||||
if (columns) saveAllColumns();
|
||||
}, [columns]);
|
||||
|
||||
useEffect(() => {
|
||||
setColumns(initialColumns);
|
||||
}, [initialColumns]);
|
||||
|
||||
// Listen for keyboard event
|
||||
useEffect(() => {
|
||||
window.addEventListener("keydown", handleKeyDown);
|
||||
@@ -158,7 +140,7 @@ function Screen() {
|
||||
if (data.payload.type === "move")
|
||||
move(data.payload.label, data.payload.direction);
|
||||
if (data.payload.type === "set_title")
|
||||
updateName(data.payload.label, data.payload.title);
|
||||
update(data.payload.label, data.payload.title);
|
||||
});
|
||||
|
||||
return () => {
|
||||
@@ -166,6 +148,21 @@ function Screen() {
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
async function getSystemColumns() {
|
||||
const systemPath = "resources/columns.json";
|
||||
const resourcePath = await resolveResource(systemPath);
|
||||
const resourceFile = await readTextFile(resourcePath);
|
||||
const cols: LumeColumn[] = JSON.parse(resourceFile);
|
||||
|
||||
appColumns.setState(() => cols.filter((col) => col.default));
|
||||
}
|
||||
|
||||
if (!columns.length) {
|
||||
getSystemColumns();
|
||||
}
|
||||
}, [columns.length]);
|
||||
|
||||
return (
|
||||
<div className="size-full">
|
||||
<div ref={emblaRef} className="overflow-hidden size-full">
|
||||
|
||||
@@ -1,20 +1,3 @@
|
||||
import { commands } from "@/commands.gen";
|
||||
import type { LumeColumn } from "@/types";
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
|
||||
export const Route = createFileRoute("/$account/_app/home")({
|
||||
beforeLoad: async ({ context }) => {
|
||||
const key = "lume_v4:columns";
|
||||
const defaultColumns = context.systemColumns.filter((col) => col.default);
|
||||
const query = await commands.getLumeStore(key);
|
||||
|
||||
let initialColumns: LumeColumn[] = defaultColumns;
|
||||
|
||||
if (query.status === "ok") {
|
||||
initialColumns = JSON.parse(query.data);
|
||||
return { initialColumns };
|
||||
}
|
||||
|
||||
return { initialColumns };
|
||||
},
|
||||
});
|
||||
export const Route = createFileRoute("/$account/_app/home")();
|
||||
|
||||
@@ -1,186 +0,0 @@
|
||||
import { commands } from "@/commands.gen";
|
||||
import { Spinner } from "@/components";
|
||||
import { User } from "@/components/user";
|
||||
import { Plus, X } from "@phosphor-icons/react";
|
||||
import { createLazyFileRoute } from "@tanstack/react-router";
|
||||
import { message } from "@tauri-apps/plugin-dialog";
|
||||
import { useState, useTransition } from "react";
|
||||
|
||||
export const Route = createLazyFileRoute("/columns/_layout/create-group")({
|
||||
component: Screen,
|
||||
});
|
||||
|
||||
const REYA_NPUB =
|
||||
"npub1zfss807aer0j26mwp2la0ume0jqde3823rmu97ra6sgyyg956e0s6xw445";
|
||||
|
||||
function Screen() {
|
||||
const contacts = Route.useLoaderData();
|
||||
const search = Route.useSearch();
|
||||
const navigate = Route.useNavigate();
|
||||
const { queryClient } = Route.useRouteContext();
|
||||
|
||||
const [title, setTitle] = useState("");
|
||||
const [npub, setNpub] = useState("");
|
||||
const [users, setUsers] = useState<string[]>([REYA_NPUB]);
|
||||
const [isPending, startTransition] = useTransition();
|
||||
|
||||
const toggleUser = (pubkey: string) => {
|
||||
setUsers((prev) =>
|
||||
prev.includes(pubkey)
|
||||
? prev.filter((i) => i !== pubkey)
|
||||
: [...prev, pubkey],
|
||||
);
|
||||
};
|
||||
|
||||
const addUser = () => {
|
||||
if (!npub.startsWith("npub1")) return;
|
||||
if (users.includes(npub)) return;
|
||||
|
||||
setUsers((prev) => [...prev, npub]);
|
||||
setNpub("");
|
||||
};
|
||||
|
||||
const submit = () => {
|
||||
startTransition(async () => {
|
||||
const key = `lume_v4:group:${search.label}`;
|
||||
const res = await commands.setLumeStore(key, JSON.stringify(users));
|
||||
|
||||
if (res.status === "ok") {
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: [search.label, search.account],
|
||||
});
|
||||
// @ts-ignore, tanstack router bug.
|
||||
navigate({ to: search.redirect, search: { ...search, name: title } });
|
||||
} else {
|
||||
await message(res.error, {
|
||||
title: "Create Group",
|
||||
kind: "error",
|
||||
});
|
||||
return;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center w-full h-full gap-4">
|
||||
<div className="flex flex-col items-center justify-center text-center">
|
||||
<h1 className="font-serif text-2xl font-medium">Create a group</h1>
|
||||
<p className="leading-tight text-neutral-700 dark:text-neutral-300">
|
||||
For the people that you want to keep up.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-col w-4/5 max-w-full gap-3">
|
||||
<div className="flex items-center w-full rounded-lg h-9 shrink-0 bg-neutral-200 dark:bg-neutral-800">
|
||||
<label
|
||||
htmlFor="name"
|
||||
className="w-16 text-sm font-semibold text-center border-r border-neutral-300 dark:border-neutral-700 shrink-0"
|
||||
>
|
||||
Name
|
||||
</label>
|
||||
<input
|
||||
name="name"
|
||||
value={title}
|
||||
onChange={(e) => setTitle(e.target.value)}
|
||||
placeholder="Enter a name for this group"
|
||||
className="h-full px-3 text-sm bg-transparent border-none placeholder:text-neutral-600 focus:border-neutral-500 focus:ring-0 dark:placeholder:text-neutral-400"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col items-center w-full gap-3">
|
||||
<div className="overflow-y-auto scrollbar-none p-2 w-full h-[450px] flex flex-col gap-3 bg-neutral-200 dark:bg-neutral-900 rounded-xl">
|
||||
<div className="flex gap-2">
|
||||
<input
|
||||
name="npub"
|
||||
value={npub}
|
||||
onChange={(e) => setNpub(e.target.value)}
|
||||
placeholder="npub1..."
|
||||
className="w-full px-3 text-sm border-none rounded-lg h-9 bg-neutral-300 dark:bg-neutral-700 placeholder:text-neutral-600 focus:border-neutral-500 focus:ring-0 dark:placeholder:text-neutral-400"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => addUser()}
|
||||
className="inline-flex items-center justify-center text-neutral-500 rounded-lg size-9 bg-neutral-300 dark:bg-neutral-700 shrink-0 hover:bg-blue-500 hover:text-white"
|
||||
>
|
||||
<Plus className="size-5" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<span className="text-sm font-semibold">Added</span>
|
||||
<div className="flex flex-col gap-2">
|
||||
{users.length ? (
|
||||
users.map((item: string) => (
|
||||
<button
|
||||
key={item}
|
||||
type="button"
|
||||
onClick={() => toggleUser(item)}
|
||||
className="inline-flex items-center justify-between px-3 py-2 bg-white rounded-lg dark:bg-black/20 shadow-primary dark:ring-1 ring-neutral-800/50"
|
||||
>
|
||||
<User.Provider pubkey={item}>
|
||||
<User.Root className="flex items-center gap-2.5">
|
||||
<User.Avatar className="rounded-full size-8" />
|
||||
<div className="flex items-center gap-1">
|
||||
<User.Name className="text-sm font-medium" />
|
||||
</div>
|
||||
</User.Root>
|
||||
</User.Provider>
|
||||
<X className="size-4" />
|
||||
</button>
|
||||
))
|
||||
) : (
|
||||
<div className="flex items-center justify-center text-sm rounded-lg bg-neutral-300 dark:bg-neutral-700 h-14">
|
||||
Empty.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<span className="text-sm font-semibold">Contacts</span>
|
||||
<div className="flex flex-col gap-2">
|
||||
{contacts.length ? (
|
||||
contacts.map((item: string) => (
|
||||
<button
|
||||
key={item}
|
||||
type="button"
|
||||
onClick={() => toggleUser(item)}
|
||||
className="inline-flex items-center justify-between px-3 py-2 bg-white rounded-lg dark:bg-black/20 shadow-primary dark:ring-1 ring-neutral-800/50"
|
||||
>
|
||||
<User.Provider pubkey={item}>
|
||||
<User.Root className="flex items-center gap-2.5">
|
||||
<User.Avatar className="rounded-full size-8" />
|
||||
<div className="flex items-center gap-1">
|
||||
<User.Name className="text-sm font-medium" />
|
||||
</div>
|
||||
</User.Root>
|
||||
</User.Provider>
|
||||
</button>
|
||||
))
|
||||
) : (
|
||||
<div className="flex items-center justify-center text-sm rounded-lg bg-black/5 dark:bg-white/5 h-14">
|
||||
<p>
|
||||
Find more user at{" "}
|
||||
<a
|
||||
href="https://www.nostr.directory/"
|
||||
target="_blank"
|
||||
className="text-blue-600 after:content-['_↗']"
|
||||
rel="noreferrer"
|
||||
>
|
||||
Nostr Directory
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => submit()}
|
||||
disabled={isPending || users.length < 1}
|
||||
className="inline-flex items-center justify-center text-sm font-medium text-white bg-blue-500 rounded-full w-36 h-9 hover:bg-blue-600 disabled:opacity-50"
|
||||
>
|
||||
{isPending ? <Spinner /> : "Confirm"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,49 +1,30 @@
|
||||
import type { LumeColumn } from "@/types";
|
||||
import { commands } from "@/commands.gen";
|
||||
import { Spinner, User } from "@/components";
|
||||
import { LumeWindow } from "@/system";
|
||||
import type { LumeColumn, NostrEvent } from "@/types";
|
||||
import { ArrowClockwise, Plus } from "@phosphor-icons/react";
|
||||
import * as ScrollArea from "@radix-ui/react-scroll-area";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { createLazyFileRoute } from "@tanstack/react-router";
|
||||
import { getCurrentWindow } from "@tauri-apps/api/window";
|
||||
import { resolveResource } from "@tauri-apps/api/path";
|
||||
import { readTextFile } from "@tauri-apps/plugin-fs";
|
||||
import { useCallback } from "react";
|
||||
|
||||
export const Route = createLazyFileRoute("/columns/_layout/gallery")({
|
||||
component: Screen,
|
||||
});
|
||||
|
||||
function Screen() {
|
||||
const { columns } = Route.useRouteContext();
|
||||
|
||||
const install = async (column: LumeColumn) => {
|
||||
const mainWindow = getCurrentWindow();
|
||||
await mainWindow.emit("columns", { type: "add", column });
|
||||
};
|
||||
|
||||
return (
|
||||
<ScrollArea.Root
|
||||
type={"scroll"}
|
||||
scrollHideDelay={300}
|
||||
className="overflow-hidden size-full"
|
||||
>
|
||||
<ScrollArea.Viewport className="relative h-full px-3">
|
||||
{columns.map((column) => (
|
||||
<div
|
||||
key={column.label}
|
||||
className="mb-3 group flex px-4 items-center justify-between h-16 rounded-xl bg-white dark:bg-black border-[.5px] border-neutral-300 dark:border-neutral-700"
|
||||
>
|
||||
<div className="text-sm">
|
||||
<div className="mb-px leading-tight font-semibold">
|
||||
{column.name}
|
||||
</div>
|
||||
<div className="leading-tight text-neutral-500 dark:text-neutral-400">
|
||||
{column.description}
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => install(column)}
|
||||
className="text-xs uppercase font-semibold w-16 h-7 hidden group-hover:inline-flex items-center justify-center rounded-full bg-neutral-200 hover:bg-blue-500 hover:text-white dark:bg-black/10"
|
||||
>
|
||||
Open
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
<ScrollArea.Viewport className="relative h-full px-3 pb-3">
|
||||
<MyGroups />
|
||||
<MyInterests />
|
||||
<Core />
|
||||
</ScrollArea.Viewport>
|
||||
<ScrollArea.Scrollbar
|
||||
className="flex select-none touch-none p-0.5 duration-[160ms] ease-out data-[orientation=vertical]:w-2"
|
||||
@@ -55,3 +36,276 @@ function Screen() {
|
||||
</ScrollArea.Root>
|
||||
);
|
||||
}
|
||||
|
||||
function Core() {
|
||||
const { isLoading, data } = useQuery({
|
||||
queryKey: ["core"],
|
||||
queryFn: async () => {
|
||||
const systemPath = "resources/columns.json";
|
||||
const resourcePath = await resolveResource(systemPath);
|
||||
const resourceFile = await readTextFile(resourcePath);
|
||||
|
||||
const systemColumns: LumeColumn[] = JSON.parse(resourceFile);
|
||||
const columns = systemColumns.filter((col) => !col.default);
|
||||
|
||||
return columns;
|
||||
},
|
||||
refetchOnWindowFocus: false,
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-3">
|
||||
<div className="flex items-center justify-between px-2">
|
||||
<h3 className="font-semibold">Core</h3>
|
||||
</div>
|
||||
<div className="flex flex-col gap-3">
|
||||
{isLoading ? (
|
||||
<div className="inline-flex items-center gap-1.5">
|
||||
<Spinner className="size-4" />
|
||||
Loading...
|
||||
</div>
|
||||
) : (
|
||||
data.map((column) => (
|
||||
<div
|
||||
key={column.label}
|
||||
className="group flex px-4 items-center justify-between h-16 rounded-xl bg-white dark:bg-black border-[.5px] border-neutral-300 dark:border-neutral-700"
|
||||
>
|
||||
<div className="text-sm">
|
||||
<div className="mb-px leading-tight font-semibold">
|
||||
{column.name}
|
||||
</div>
|
||||
<div className="leading-tight text-neutral-500 dark:text-neutral-400">
|
||||
{column.description}
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => LumeWindow.openColumn(column)}
|
||||
className="text-xs uppercase font-semibold w-16 h-7 hidden group-hover:inline-flex items-center justify-center rounded-full bg-neutral-200 hover:bg-blue-500 hover:text-white dark:bg-black/10"
|
||||
>
|
||||
Open
|
||||
</button>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function MyGroups() {
|
||||
const { account } = Route.useSearch();
|
||||
const { isLoading, data, refetch } = useQuery({
|
||||
queryKey: ["mygroups", account],
|
||||
queryFn: async () => {
|
||||
const res = await commands.getAllGroups();
|
||||
|
||||
if (res.status === "ok") {
|
||||
const data = res.data.map((item) => JSON.parse(item) as NostrEvent);
|
||||
return data;
|
||||
} else {
|
||||
throw new Error(res.error);
|
||||
}
|
||||
},
|
||||
select: (data) =>
|
||||
data.filter(
|
||||
(item) => item.tags.filter((tag) => tag[0] === "p")?.length > 0,
|
||||
),
|
||||
refetchOnWindowFocus: false,
|
||||
});
|
||||
|
||||
const renderItem = useCallback(
|
||||
(item: NostrEvent) => {
|
||||
const name = item.tags.filter((tag) => tag[0] === "d")[0][1] ?? "unnamed";
|
||||
|
||||
return (
|
||||
<div
|
||||
key={item.id}
|
||||
className="group flex flex-col rounded-xl overflow-hidden bg-neutral-200/50 dark:bg-neutral-800/50 border-[.5px] border-neutral-300 dark:border-neutral-700"
|
||||
>
|
||||
<div className="p-3 h-16 flex flex-wrap items-center justify-center gap-2 overflow-y-auto">
|
||||
{item.tags
|
||||
.filter((tag) => tag[0] === "p")
|
||||
.map((tag) => (
|
||||
<div key={tag[1]}>
|
||||
<User.Provider pubkey={tag[1]}>
|
||||
<User.Root>
|
||||
<User.Avatar className="size-8 rounded-full" />
|
||||
</User.Root>
|
||||
</User.Provider>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="p-3 flex items-center justify-between">
|
||||
<div className="text-sm font-medium">{name}</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() =>
|
||||
LumeWindow.openColumn({
|
||||
label: name,
|
||||
name,
|
||||
url: `/columns/groups/${item.id}`,
|
||||
})
|
||||
}
|
||||
className="h-6 w-16 inline-flex items-center justify-center gap-1 text-xs font-semibold rounded-full bg-blue-600 hover:bg-blue-500 text-white"
|
||||
>
|
||||
Add
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
[data],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="mb-12 flex flex-col gap-3">
|
||||
<div className="flex items-center justify-between px-2">
|
||||
<h3 className="font-semibold">My groups</h3>
|
||||
<div className="inline-flex items-center justify-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => refetch()}
|
||||
className="size-7 inline-flex items-center justify-center rounded-full"
|
||||
>
|
||||
<ArrowClockwise className="size-4" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() =>
|
||||
LumeWindow.openPopup("New group", `/set-group?account=${account}`)
|
||||
}
|
||||
className="h-7 w-max px-2 inline-flex items-center justify-center gap-1 text-sm font-medium rounded-full bg-neutral-300 dark:bg-neutral-700 hover:bg-blue-500 hover:text-white"
|
||||
>
|
||||
<Plus className="size-3" weight="bold" />
|
||||
New
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-3">
|
||||
{isLoading ? (
|
||||
<div className="inline-flex items-center gap-1.5">
|
||||
<Spinner className="size-4" />
|
||||
Loading...
|
||||
</div>
|
||||
) : !data.length ? (
|
||||
<div className="flex flex-col items-center justify-center h-16 w-full rounded-xl overflow-hidden bg-neutral-200/50 dark:bg-neutral-800/50">
|
||||
<p className="text-center">You don't have any groups yet.</p>
|
||||
</div>
|
||||
) : (
|
||||
data.map((item) => renderItem(item))
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function MyInterests() {
|
||||
const { account } = Route.useSearch();
|
||||
const { isLoading, data, refetch } = useQuery({
|
||||
queryKey: ["myinterests", account],
|
||||
queryFn: async () => {
|
||||
const res = await commands.getAllInterests();
|
||||
|
||||
if (res.status === "ok") {
|
||||
const data = res.data.map((item) => JSON.parse(item) as NostrEvent);
|
||||
return data;
|
||||
} else {
|
||||
throw new Error(res.error);
|
||||
}
|
||||
},
|
||||
select: (data) =>
|
||||
data.filter(
|
||||
(item) => item.tags.filter((tag) => tag[0] === "t")?.length > 0,
|
||||
),
|
||||
refetchOnWindowFocus: false,
|
||||
});
|
||||
|
||||
const renderItem = useCallback(
|
||||
(item: NostrEvent) => {
|
||||
const name = item.tags.filter((tag) => tag[0] === "d")[0][1] ?? "unnamed";
|
||||
|
||||
return (
|
||||
<div
|
||||
key={item.id}
|
||||
className="group flex flex-col rounded-xl overflow-hidden bg-neutral-200/50 dark:bg-neutral-800/50 border-[.5px] border-neutral-300 dark:border-neutral-700"
|
||||
>
|
||||
<div className="p-3 h-16 flex flex-wrap items-center justify-center gap-2 overflow-y-auto">
|
||||
{item.tags
|
||||
.filter((tag) => tag[0] === "t")
|
||||
.map((tag) => (
|
||||
<div key={tag[1]} className="text-sm font-medium">
|
||||
{tag[1]}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="p-3 flex items-center justify-between">
|
||||
<div className="text-sm font-medium">{name}</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() =>
|
||||
LumeWindow.openColumn({
|
||||
label: name,
|
||||
name,
|
||||
url: `/columns/interests/${item.id}`,
|
||||
})
|
||||
}
|
||||
className="h-6 w-16 inline-flex items-center justify-center gap-1 text-xs font-semibold rounded-full bg-blue-600 hover:bg-blue-500 text-white"
|
||||
>
|
||||
Add
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
[data],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="mb-12 flex flex-col gap-3">
|
||||
<div className="flex items-center justify-between px-2">
|
||||
<h3 className="font-semibold">My interests</h3>
|
||||
<div className="inline-flex items-center justify-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => refetch()}
|
||||
className="size-7 inline-flex items-center justify-center rounded-full"
|
||||
>
|
||||
<ArrowClockwise className="size-4" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() =>
|
||||
LumeWindow.openPopup(
|
||||
"New interest",
|
||||
`/set-interest?account=${account}`,
|
||||
)
|
||||
}
|
||||
className="h-7 w-max px-2 inline-flex items-center justify-center gap-1 text-sm font-medium rounded-full bg-neutral-300 dark:bg-neutral-700 hover:bg-blue-500 hover:text-white"
|
||||
>
|
||||
<Plus className="size-3" weight="bold" />
|
||||
New
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-3">
|
||||
{isLoading ? (
|
||||
<div className="inline-flex items-center gap-1.5">
|
||||
<Spinner className="size-4" />
|
||||
Loading...
|
||||
</div>
|
||||
) : !data.length ? (
|
||||
<div className="flex flex-col items-center justify-center h-16 w-full rounded-xl overflow-hidden bg-neutral-200/50 dark:bg-neutral-800/50">
|
||||
<p className="text-center">You don't have any interests yet.</p>
|
||||
</div>
|
||||
) : (
|
||||
data.map((item) => renderItem(item))
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
import type { LumeColumn } from "@/types";
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
import { resolveResource } from "@tauri-apps/api/path";
|
||||
import { readTextFile } from "@tauri-apps/plugin-fs";
|
||||
|
||||
export const Route = createFileRoute("/columns/_layout/gallery")({
|
||||
beforeLoad: async () => {
|
||||
const systemPath = "resources/columns.json";
|
||||
const resourcePath = await resolveResource(systemPath);
|
||||
const resourceFile = await readTextFile(resourcePath);
|
||||
|
||||
const systemColumns: LumeColumn[] = JSON.parse(resourceFile);
|
||||
const columns = systemColumns.filter((col) => !col.default);
|
||||
|
||||
return {
|
||||
columns,
|
||||
};
|
||||
},
|
||||
});
|
||||
@@ -1,25 +0,0 @@
|
||||
import { commands } from "@/commands.gen";
|
||||
import { createFileRoute, redirect } from "@tanstack/react-router";
|
||||
|
||||
export const Route = createFileRoute("/columns/_layout/group")({
|
||||
beforeLoad: async ({ search }) => {
|
||||
const key = `lume_v4:group:${search.label}`;
|
||||
const res = await commands.getLumeStore(key);
|
||||
|
||||
if (res.status === "ok") {
|
||||
const groups: string[] = JSON.parse(res.data);
|
||||
|
||||
if (groups.length) {
|
||||
return { groups };
|
||||
}
|
||||
}
|
||||
|
||||
throw redirect({
|
||||
to: "/columns/create-group",
|
||||
search: {
|
||||
...search,
|
||||
redirect: "/columns/group",
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -7,16 +7,19 @@ import { ArrowDown } from "@phosphor-icons/react";
|
||||
import * as ScrollArea from "@radix-ui/react-scroll-area";
|
||||
import { useInfiniteQuery } from "@tanstack/react-query";
|
||||
import { createLazyFileRoute } from "@tanstack/react-router";
|
||||
import { useCallback, useRef } from "react";
|
||||
import { listen } from "@tauri-apps/api/event";
|
||||
import { useCallback, useEffect, useRef } from "react";
|
||||
import { Virtualizer } from "virtua";
|
||||
|
||||
export const Route = createLazyFileRoute("/columns/_layout/group")({
|
||||
export const Route = createLazyFileRoute("/columns/_layout/groups/$id")({
|
||||
component: Screen,
|
||||
});
|
||||
|
||||
export function Screen() {
|
||||
const { label, account } = Route.useSearch();
|
||||
const { groups } = Route.useRouteContext();
|
||||
const group = Route.useLoaderData();
|
||||
const params = Route.useParams();
|
||||
const { queryClient } = Route.useRouteContext();
|
||||
|
||||
const {
|
||||
data,
|
||||
isLoading,
|
||||
@@ -25,11 +28,11 @@ export function Screen() {
|
||||
hasNextPage,
|
||||
fetchNextPage,
|
||||
} = useInfiniteQuery({
|
||||
queryKey: [label, account],
|
||||
queryKey: ["groups", params.id],
|
||||
initialPageParam: 0,
|
||||
queryFn: async ({ pageParam }: { pageParam: number }) => {
|
||||
const until = pageParam > 0 ? pageParam.toString() : undefined;
|
||||
const res = await commands.getGroupEvents(groups, until);
|
||||
const res = await commands.getAllEventsByAuthors(group, until);
|
||||
|
||||
if (res.status === "error") {
|
||||
throw new Error(res.error);
|
||||
@@ -39,6 +42,7 @@ export function Screen() {
|
||||
},
|
||||
getNextPageParam: (lastPage) => lastPage?.at(-1)?.created_at - 1,
|
||||
select: (data) => data?.pages.flat(),
|
||||
enabled: group?.length > 0,
|
||||
refetchOnWindowFocus: false,
|
||||
});
|
||||
|
||||
@@ -80,6 +84,16 @@ export function Screen() {
|
||||
[data],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const unlisten = listen("synchronized", async () => {
|
||||
await queryClient.invalidateQueries({ queryKey: ["groups", params.id] });
|
||||
});
|
||||
|
||||
return () => {
|
||||
unlisten.then((f) => f());
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<ScrollArea.Root
|
||||
type={"scroll"}
|
||||
20
src/routes/columns/_layout/groups.$id.tsx
Normal file
20
src/routes/columns/_layout/groups.$id.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import { commands } from "@/commands.gen";
|
||||
import type { NostrEvent } from "@/types";
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
|
||||
export const Route = createFileRoute("/columns/_layout/groups/$id")({
|
||||
loader: async ({ params }) => {
|
||||
const res = await commands.getGroup(params.id);
|
||||
|
||||
if (res.status === "ok") {
|
||||
const event: NostrEvent = JSON.parse(res.data);
|
||||
const tag = event.tags
|
||||
.filter((tag) => tag[0] === "p")
|
||||
.map((tag) => tag[1]);
|
||||
|
||||
return tag;
|
||||
} else {
|
||||
throw new Error(res.error);
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -7,16 +7,19 @@ import { ArrowDown } from "@phosphor-icons/react";
|
||||
import * as ScrollArea from "@radix-ui/react-scroll-area";
|
||||
import { useInfiniteQuery } from "@tanstack/react-query";
|
||||
import { createLazyFileRoute } from "@tanstack/react-router";
|
||||
import { useCallback, useRef } from "react";
|
||||
import { listen } from "@tauri-apps/api/event";
|
||||
import { useCallback, useEffect, useRef } from "react";
|
||||
import { Virtualizer } from "virtua";
|
||||
|
||||
export const Route = createLazyFileRoute("/columns/_layout/hashtags/$content")({
|
||||
export const Route = createLazyFileRoute("/columns/_layout/interests/$id")({
|
||||
component: Screen,
|
||||
});
|
||||
|
||||
export function Screen() {
|
||||
const { label, account } = Route.useSearch();
|
||||
const { content } = Route.useParams();
|
||||
const hashtags = Route.useLoaderData();
|
||||
const params = Route.useParams();
|
||||
const { queryClient } = Route.useRouteContext();
|
||||
|
||||
const {
|
||||
data,
|
||||
isLoading,
|
||||
@@ -25,12 +28,12 @@ export function Screen() {
|
||||
hasNextPage,
|
||||
fetchNextPage,
|
||||
} = useInfiniteQuery({
|
||||
queryKey: [label, account],
|
||||
queryKey: ["hashtags", params.id],
|
||||
initialPageParam: 0,
|
||||
queryFn: async ({ pageParam }: { pageParam: number }) => {
|
||||
const hashtags = content.split("_");
|
||||
const tags = hashtags.map((tag) => tag.toLowerCase().replace("#", ""));
|
||||
const until = pageParam > 0 ? pageParam.toString() : undefined;
|
||||
const res = await commands.getHashtagEvents(hashtags, until);
|
||||
const res = await commands.getAllEventsByHashtags(tags, until);
|
||||
|
||||
if (res.status === "error") {
|
||||
throw new Error(res.error);
|
||||
@@ -81,6 +84,18 @@ export function Screen() {
|
||||
[data],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const unlisten = listen("synchronized", async () => {
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: ["hashtags", params.id],
|
||||
});
|
||||
});
|
||||
|
||||
return () => {
|
||||
unlisten.then((f) => f());
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<ScrollArea.Root
|
||||
type={"scroll"}
|
||||
20
src/routes/columns/_layout/interests.$id.tsx
Normal file
20
src/routes/columns/_layout/interests.$id.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import { commands } from "@/commands.gen";
|
||||
import type { NostrEvent } from "@/types";
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
|
||||
export const Route = createFileRoute("/columns/_layout/interests/$id")({
|
||||
loader: async ({ params }) => {
|
||||
const res = await commands.getInterest(params.id);
|
||||
|
||||
if (res.status === "ok") {
|
||||
const event: NostrEvent = JSON.parse(res.data);
|
||||
const tag = event.tags
|
||||
.filter((tag) => tag[0] === "t")
|
||||
.map((tag) => tag[1]);
|
||||
|
||||
return tag;
|
||||
} else {
|
||||
throw new Error(res.error);
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -6,11 +6,7 @@ import { Kind, type Meta } from "@/types";
|
||||
import { ArrowDown, ArrowUp } from "@phosphor-icons/react";
|
||||
import * as ScrollArea from "@radix-ui/react-scroll-area";
|
||||
import { type InfiniteData, useInfiniteQuery } from "@tanstack/react-query";
|
||||
import {
|
||||
Navigate,
|
||||
createLazyFileRoute,
|
||||
useLocation,
|
||||
} from "@tanstack/react-router";
|
||||
import { createLazyFileRoute } from "@tanstack/react-router";
|
||||
import { listen } from "@tauri-apps/api/event";
|
||||
import { getCurrentWindow } from "@tauri-apps/api/window";
|
||||
import {
|
||||
@@ -33,12 +29,12 @@ export const Route = createLazyFileRoute("/columns/_layout/newsfeed")({
|
||||
});
|
||||
|
||||
export function Screen() {
|
||||
const contacts = Route.useLoaderData();
|
||||
const { queryClient } = Route.useRouteContext();
|
||||
const { label, account } = Route.useSearch();
|
||||
const {
|
||||
data,
|
||||
isLoading,
|
||||
isError,
|
||||
isFetching,
|
||||
isFetchingNextPage,
|
||||
hasNextPage,
|
||||
@@ -48,7 +44,7 @@ export function Screen() {
|
||||
initialPageParam: 0,
|
||||
queryFn: async ({ pageParam }: { pageParam: number }) => {
|
||||
const until = pageParam > 0 ? pageParam.toString() : undefined;
|
||||
const res = await commands.getLocalEvents(until);
|
||||
const res = await commands.getAllEventsByAuthors(contacts, until);
|
||||
|
||||
if (res.status === "error") {
|
||||
throw new Error(res.error);
|
||||
@@ -58,9 +54,9 @@ export function Screen() {
|
||||
},
|
||||
getNextPageParam: (lastPage) => lastPage?.at?.(-1)?.created_at - 1,
|
||||
select: (data) => data?.pages.flat(),
|
||||
enabled: contacts?.length > 0,
|
||||
});
|
||||
|
||||
const location = useLocation();
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
|
||||
const renderItem = useCallback(
|
||||
@@ -109,16 +105,6 @@ export function Screen() {
|
||||
};
|
||||
}, []);
|
||||
|
||||
if (isError) {
|
||||
return (
|
||||
<Navigate
|
||||
to="/columns/create-newsfeed/users"
|
||||
// @ts-ignore, tanstack router bug.
|
||||
search={{ label, account, redirect: location.href }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ScrollArea.Root
|
||||
type={"scroll"}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { commands } from "@/commands.gen";
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
|
||||
export const Route = createFileRoute("/columns/_layout/create-group")({
|
||||
export const Route = createFileRoute("/columns/_layout/newsfeed")({
|
||||
loader: async () => {
|
||||
const res = await commands.getContactList();
|
||||
|
||||
@@ -10,7 +10,7 @@ import { useQuery } from "@tanstack/react-query";
|
||||
import { createLazyFileRoute } from "@tanstack/react-router";
|
||||
import { getCurrentWindow } from "@tauri-apps/api/window";
|
||||
import { nip19 } from "nostr-tools";
|
||||
import { type ReactNode, useEffect, useRef } from "react";
|
||||
import { type ReactNode, useEffect, useMemo, useRef } from "react";
|
||||
import { Virtualizer } from "virtua";
|
||||
|
||||
export const Route = createLazyFileRoute("/columns/_layout/notification")({
|
||||
@@ -180,17 +180,7 @@ function Screen() {
|
||||
</div>
|
||||
<div className="flex flex-wrap items-center gap-3">
|
||||
{events.map((event) => (
|
||||
<User.Provider
|
||||
key={event.id}
|
||||
pubkey={event.tags.find((tag) => tag[0] === "P")[1]}
|
||||
>
|
||||
<User.Root className="shrink-0 flex gap-1.5 rounded-full h-7 bg-black/10 dark:bg-white/10 p-[2px]">
|
||||
<User.Avatar className="rounded-full size-6" />
|
||||
<div className="flex-1 h-6 w-max pr-1.5 rounded-full inline-flex items-center justify-center text-xs font-semibold truncate">
|
||||
₿ {decodeZapInvoice(event.tags).bitcoinFormatted}
|
||||
</div>
|
||||
</User.Root>
|
||||
</User.Provider>
|
||||
<ZapReceipt key={event.id} event={event} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
@@ -311,3 +301,36 @@ function TextNote({ event }: { event: LumeEvent }) {
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
function ZapReceipt({ event }: { event: LumeEvent }) {
|
||||
const amount = useMemo(
|
||||
() => decodeZapInvoice(event.tags).bitcoinFormatted ?? "0",
|
||||
[event.id],
|
||||
);
|
||||
const sender = useMemo(
|
||||
() => event.tags.find((tag) => tag[0] === "P")?.[1],
|
||||
[event.id],
|
||||
);
|
||||
|
||||
if (!sender) {
|
||||
return (
|
||||
<div className="shrink-0 flex gap-1.5 rounded-full h-7 bg-black/10 dark:bg-white/10 p-[2px]">
|
||||
<div className="rounded-full size-6 bg-blue-500" />
|
||||
<div className="flex-1 h-6 w-max pr-1.5 rounded-full inline-flex items-center justify-center text-xs font-semibold truncate">
|
||||
₿ {amount}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<User.Provider pubkey={sender}>
|
||||
<User.Root className="shrink-0 flex gap-1.5 rounded-full h-7 bg-black/10 dark:bg-white/10 p-[2px]">
|
||||
<User.Avatar className="rounded-full size-6" />
|
||||
<div className="flex-1 h-6 w-max pr-1.5 rounded-full inline-flex items-center justify-center text-xs font-semibold truncate">
|
||||
₿ {amount}
|
||||
</div>
|
||||
</User.Root>
|
||||
</User.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ function StoryItem({ contact }: { contact: string }) {
|
||||
} = useQuery({
|
||||
queryKey: ["stories", contact],
|
||||
queryFn: async () => {
|
||||
const res = await commands.getEventsBy(contact, 10);
|
||||
const res = await commands.getAllEventsByAuthor(contact, 10);
|
||||
|
||||
if (res.status === "ok") {
|
||||
const data = toLumeEvents(res.data);
|
||||
|
||||
@@ -22,7 +22,7 @@ function Screen() {
|
||||
const { isLoading, data: events } = useQuery({
|
||||
queryKey: ["stories", id],
|
||||
queryFn: async () => {
|
||||
const res = await commands.getEventsBy(id, 100);
|
||||
const res = await commands.getAllEventsByAuthor(id, 100);
|
||||
|
||||
if (res.status === "ok") {
|
||||
const data = toLumeEvents(res.data);
|
||||
|
||||
@@ -21,25 +21,21 @@ function Screen() {
|
||||
href="/auth/connect"
|
||||
className="w-full p-4 rounded-xl hover:shadow-lg hover:ring-0 hover:bg-white dark:hover:bg-neutral-900 ring-1 ring-black/5 dark:ring-white/5"
|
||||
>
|
||||
<h3 className="mb-1.5 font-medium">Continue with Nostr Connect</h3>
|
||||
<div className="text-sm">
|
||||
<p className="text-neutral-500 dark:text-neutral-600">
|
||||
Your account will be handled by a remote signer. Lume will not
|
||||
store your account keys.
|
||||
</p>
|
||||
</div>
|
||||
<h3 className="mb-1 font-medium">Continue with Nostr Connect</h3>
|
||||
<p className="text-xs text-neutral-500 dark:text-neutral-600">
|
||||
Your account will be handled by a remote signer. Lume will not
|
||||
store your account keys.
|
||||
</p>
|
||||
</a>
|
||||
<a
|
||||
href="/auth/import"
|
||||
className="w-full p-4 rounded-xl hover:shadow-lg hover:ring-0 hover:bg-white dark:hover:bg-neutral-900 ring-1 ring-black/5 dark:ring-white/5"
|
||||
>
|
||||
<h3 className="mb-1.5 font-medium">Continue with Secret Key</h3>
|
||||
<div className="text-sm">
|
||||
<p className="text-neutral-500 dark:text-neutral-600">
|
||||
Lume will store your keys in secure storage. You can provide a
|
||||
password to add extra security.
|
||||
</p>
|
||||
</div>
|
||||
<h3 className="mb-1 font-medium">Continue with Secret Key</h3>
|
||||
<p className="text-xs text-neutral-500 dark:text-neutral-600">
|
||||
Lume will store your keys in secure storage. You can provide a
|
||||
password to add extra security.
|
||||
</p>
|
||||
</a>
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<div className="flex-1 h-px bg-black/5 dark:bg-white/5" />
|
||||
|
||||
205
src/routes/set-group.lazy.tsx
Normal file
205
src/routes/set-group.lazy.tsx
Normal file
@@ -0,0 +1,205 @@
|
||||
import { commands } from "@/commands.gen";
|
||||
import { Spinner } from "@/components";
|
||||
import { User } from "@/components/user";
|
||||
import { Plus, X } from "@phosphor-icons/react";
|
||||
import * as ScrollArea from "@radix-ui/react-scroll-area";
|
||||
import { createLazyFileRoute } from "@tanstack/react-router";
|
||||
import { getCurrentWindow } from "@tauri-apps/api/window";
|
||||
import { message } from "@tauri-apps/plugin-dialog";
|
||||
import { useState, useTransition } from "react";
|
||||
|
||||
export const Route = createLazyFileRoute("/set-group")({
|
||||
component: Screen,
|
||||
});
|
||||
|
||||
function Screen() {
|
||||
const contacts = Route.useLoaderData();
|
||||
const { account } = Route.useSearch();
|
||||
const { queryClient } = Route.useRouteContext();
|
||||
|
||||
const [title, setTitle] = useState("");
|
||||
const [npub, setNpub] = useState("");
|
||||
const [users, setUsers] = useState<string[]>([]);
|
||||
const [isPending, startTransition] = useTransition();
|
||||
|
||||
const toggleUser = (pubkey: string) => {
|
||||
setUsers((prev) =>
|
||||
prev.includes(pubkey)
|
||||
? prev.filter((i) => i !== pubkey)
|
||||
: [...prev, pubkey],
|
||||
);
|
||||
};
|
||||
|
||||
const addUser = () => {
|
||||
if (!npub.startsWith("npub1")) return;
|
||||
if (users.includes(npub)) return;
|
||||
|
||||
setUsers((prev) => [...prev, npub]);
|
||||
setNpub("");
|
||||
};
|
||||
|
||||
const submit = () => {
|
||||
startTransition(async () => {
|
||||
const res = await commands.setGroup(title, null, null, users);
|
||||
|
||||
if (res.status === "ok") {
|
||||
const window = getCurrentWindow();
|
||||
|
||||
// Invalidate cache
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: ["mygroups", account],
|
||||
});
|
||||
|
||||
// Create column in the main window
|
||||
await window.emitTo("main", "columns", {
|
||||
type: "add",
|
||||
column: {
|
||||
label: res.data,
|
||||
name: title,
|
||||
url: `/columns/groups/${res.data}`,
|
||||
},
|
||||
});
|
||||
|
||||
// Close current popup
|
||||
await window.close();
|
||||
} else {
|
||||
await message(res.error, { kind: "error" });
|
||||
return;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col size-full">
|
||||
<div data-tauri-drag-region className="shrink-0 h-11" />
|
||||
<div className="shrink-0 h-14 px-3 flex items-center gap-2 justify-between border-b border-black/5 dark:border-white/5">
|
||||
<div className="flex items-center flex-1 rounded-lg h-9 shrink-0 bg-black/10 dark:bg-white/10">
|
||||
<label
|
||||
htmlFor="name"
|
||||
className="w-16 text-sm font-semibold text-center border-r border-neutral-300 dark:border-neutral-700 shrink-0"
|
||||
>
|
||||
Name
|
||||
</label>
|
||||
<input
|
||||
name="name"
|
||||
value={title}
|
||||
onChange={(e) => setTitle(e.target.value)}
|
||||
placeholder="family, bff, devs,..."
|
||||
className="h-full px-3 text-sm bg-transparent border-none placeholder:text-neutral-600 focus:border-neutral-500 focus:ring-0 dark:placeholder:text-neutral-400"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => submit()}
|
||||
disabled={isPending || users.length < 1}
|
||||
className="shrink-0 inline-flex items-center justify-center text-sm font-medium text-white bg-blue-500 rounded-lg w-20 h-9 hover:bg-blue-600 disabled:opacity-50"
|
||||
>
|
||||
{isPending ? <Spinner /> : "Create"}
|
||||
</button>
|
||||
</div>
|
||||
<ScrollArea.Root
|
||||
type={"scroll"}
|
||||
scrollHideDelay={300}
|
||||
className="flex-1 overflow-hidden"
|
||||
>
|
||||
<ScrollArea.Viewport className="bg-white dark:bg-black h-full p-3">
|
||||
<div className="mb-3 flex flex-col gap-2">
|
||||
<h3 className="text-sm font-semibold">Added</h3>
|
||||
<div className="flex gap-2">
|
||||
<input
|
||||
name="npub"
|
||||
value={npub}
|
||||
onChange={(e) => setNpub(e.target.value)}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === "Enter") addUser();
|
||||
}}
|
||||
placeholder="npub1..."
|
||||
className="w-full px-3 text-sm border-none rounded-lg h-9 bg-neutral-100 dark:bg-neutral-900 placeholder:text-neutral-600 focus:border-neutral-500 focus:ring-0 dark:placeholder:text-neutral-400"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => addUser()}
|
||||
className="inline-flex items-center justify-center text-neutral-500 rounded-lg size-9 bg-neutral-200 dark:bg-neutral-800 shrink-0 hover:bg-blue-500 hover:text-white"
|
||||
>
|
||||
<Plus className="size-5" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
{users.length ? (
|
||||
users.map((item: string) => (
|
||||
<button
|
||||
key={item}
|
||||
type="button"
|
||||
onClick={() => toggleUser(item)}
|
||||
className="inline-flex items-center justify-between px-3 py-2 bg-white rounded-lg dark:bg-black/20 shadow-primary dark:ring-1 ring-neutral-800/50"
|
||||
>
|
||||
<User.Provider pubkey={item}>
|
||||
<User.Root className="flex items-center gap-2.5">
|
||||
<User.Avatar className="rounded-full size-8" />
|
||||
<div className="flex items-center gap-1">
|
||||
<User.Name className="text-sm font-medium" />
|
||||
</div>
|
||||
</User.Root>
|
||||
</User.Provider>
|
||||
<X className="size-4" />
|
||||
</button>
|
||||
))
|
||||
) : (
|
||||
<div className="flex items-center justify-center text-sm rounded-lg bg-neutral-100 dark:bg-neutral-900 h-14">
|
||||
Please add some user to your group.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<h3 className="text-sm font-semibold">Contacts</h3>
|
||||
<div className="flex flex-col gap-2">
|
||||
{contacts.length ? (
|
||||
contacts
|
||||
.filter((c) => !users.includes(c))
|
||||
.map((item: string) => (
|
||||
<button
|
||||
key={item}
|
||||
type="button"
|
||||
onClick={() => toggleUser(item)}
|
||||
className="inline-flex items-center justify-between px-3 py-2 rounded-lg border-[.5px] border-neutral-300 dark:border-neutral-700 hover:border-blue-500"
|
||||
>
|
||||
<User.Provider pubkey={item}>
|
||||
<User.Root className="flex items-center gap-2.5">
|
||||
<User.Avatar className="rounded-full size-8" />
|
||||
<div className="flex items-center gap-1">
|
||||
<User.Name className="text-sm font-medium" />
|
||||
</div>
|
||||
</User.Root>
|
||||
</User.Provider>
|
||||
</button>
|
||||
))
|
||||
) : (
|
||||
<div className="flex items-center justify-center text-sm rounded-lg bg-black/5 dark:bg-white/5 h-14">
|
||||
<p>
|
||||
Find more user at{" "}
|
||||
<a
|
||||
href="https://www.nostr.directory/"
|
||||
target="_blank"
|
||||
className="text-blue-600 after:content-['_↗']"
|
||||
rel="noreferrer"
|
||||
>
|
||||
Nostr Directory
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</ScrollArea.Viewport>
|
||||
<ScrollArea.Scrollbar
|
||||
className="flex select-none touch-none p-0.5 duration-[160ms] ease-out data-[orientation=vertical]:w-2"
|
||||
orientation="vertical"
|
||||
>
|
||||
<ScrollArea.Thumb className="flex-1 bg-black/10 dark:bg-white/10 rounded-full relative before:content-[''] before:absolute before:top-1/2 before:left-1/2 before:-translate-x-1/2 before:-translate-y-1/2 before:w-full before:h-full before:min-w-[44px] before:min-h-[44px]" />
|
||||
</ScrollArea.Scrollbar>
|
||||
<ScrollArea.Corner className="bg-transparent" />
|
||||
</ScrollArea.Root>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
23
src/routes/set-group.tsx
Normal file
23
src/routes/set-group.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import { commands } from "@/commands.gen";
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
|
||||
type RouteSearch = {
|
||||
account: string;
|
||||
};
|
||||
|
||||
export const Route = createFileRoute("/set-group")({
|
||||
validateSearch: (search: Record<string, string>): RouteSearch => {
|
||||
return {
|
||||
account: search.account,
|
||||
};
|
||||
},
|
||||
loader: async () => {
|
||||
const res = await commands.getContactList();
|
||||
|
||||
if (res.status === "ok") {
|
||||
return res.data;
|
||||
} else {
|
||||
throw new Error(res.error);
|
||||
}
|
||||
},
|
||||
});
|
||||
198
src/routes/set-interest.lazy.tsx
Normal file
198
src/routes/set-interest.lazy.tsx
Normal file
@@ -0,0 +1,198 @@
|
||||
import { commands } from "@/commands.gen";
|
||||
import { cn } from "@/commons";
|
||||
import { Spinner } from "@/components";
|
||||
import { Plus, X } from "@phosphor-icons/react";
|
||||
import * as ScrollArea from "@radix-ui/react-scroll-area";
|
||||
import { createLazyFileRoute } from "@tanstack/react-router";
|
||||
import { getCurrentWindow } from "@tauri-apps/api/window";
|
||||
import { message } from "@tauri-apps/plugin-dialog";
|
||||
import { useState, useTransition } from "react";
|
||||
|
||||
const TOPICS = [
|
||||
{
|
||||
title: "Popular",
|
||||
content: [
|
||||
"#nostr",
|
||||
"#introductions",
|
||||
"#grownostr",
|
||||
"#zap",
|
||||
"#meme",
|
||||
"#asknostr",
|
||||
"#bitcoin",
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export const Route = createLazyFileRoute("/set-interest")({
|
||||
component: Screen,
|
||||
});
|
||||
|
||||
function Screen() {
|
||||
const [title, setTitle] = useState("");
|
||||
const [hashtag, setHashtag] = useState("");
|
||||
const [hashtags, setHashtags] = useState<string[]>([]);
|
||||
const [isPending, startTransition] = useTransition();
|
||||
|
||||
const { account } = Route.useSearch();
|
||||
const { queryClient } = Route.useRouteContext();
|
||||
|
||||
const toggleHashtag = (tag: string) => {
|
||||
setHashtags((prev) =>
|
||||
prev.includes(tag) ? prev.filter((i) => i !== tag) : [...prev, tag],
|
||||
);
|
||||
};
|
||||
|
||||
const addHashtag = () => {
|
||||
if (!hashtag.startsWith("#")) return;
|
||||
if (hashtags.includes(hashtag)) return;
|
||||
|
||||
setHashtags((prev) => [...prev, hashtag]);
|
||||
setHashtag("");
|
||||
};
|
||||
|
||||
const submit = () => {
|
||||
startTransition(async () => {
|
||||
const content = hashtags.map((tag) =>
|
||||
tag.toLowerCase().replace(" ", "-").replace("#", ""),
|
||||
);
|
||||
const res = await commands.setInterest(title, null, null, content);
|
||||
|
||||
if (res.status === "ok") {
|
||||
const window = getCurrentWindow();
|
||||
|
||||
// Invalidate cache
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: ["myinterests", account],
|
||||
});
|
||||
|
||||
// Create column in the main window
|
||||
await window.emitTo("main", "columns", {
|
||||
type: "add",
|
||||
column: {
|
||||
label: res.data,
|
||||
name: title,
|
||||
url: `/columns/interests/${res.data}`,
|
||||
},
|
||||
});
|
||||
|
||||
// Close current popup
|
||||
await window.close();
|
||||
} else {
|
||||
await message(res.error, { kind: "error" });
|
||||
return;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col size-full">
|
||||
<div data-tauri-drag-region className="shrink-0 h-11" />
|
||||
<div className="shrink-0 h-14 px-3 flex items-center gap-2 justify-between border-b border-black/5 dark:border-white/5">
|
||||
<div className="flex items-center flex-1 rounded-lg h-9 shrink-0 bg-black/10 dark:bg-white/10">
|
||||
<label
|
||||
htmlFor="name"
|
||||
className="w-16 text-sm font-semibold text-center border-r border-neutral-300 dark:border-neutral-700 shrink-0"
|
||||
>
|
||||
Name
|
||||
</label>
|
||||
<input
|
||||
name="name"
|
||||
value={title}
|
||||
onChange={(e) => setTitle(e.target.value)}
|
||||
placeholder="anime, sport, art,..."
|
||||
className="h-full px-3 text-sm bg-transparent border-none placeholder:text-neutral-600 focus:border-neutral-500 focus:ring-0 dark:placeholder:text-neutral-400"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => submit()}
|
||||
className="shrink-0 inline-flex items-center justify-center text-sm font-medium text-white bg-blue-500 rounded-lg w-20 h-9 hover:bg-blue-600 disabled:opacity-50"
|
||||
>
|
||||
{isPending ? <Spinner /> : "Create"}
|
||||
</button>
|
||||
</div>
|
||||
<ScrollArea.Root
|
||||
type={"scroll"}
|
||||
scrollHideDelay={300}
|
||||
className="flex-1 overflow-hidden"
|
||||
>
|
||||
<ScrollArea.Viewport className="bg-white dark:bg-black h-full p-3">
|
||||
<div className="mb-3 flex flex-col gap-2">
|
||||
<span className="text-sm font-semibold">Added</span>
|
||||
<div className="flex flex-col gap-2">
|
||||
{hashtags.length ? (
|
||||
hashtags.map((item: string) => (
|
||||
<button
|
||||
key={item}
|
||||
type="button"
|
||||
onClick={() => toggleHashtag(item)}
|
||||
className="inline-flex items-center justify-between p-3 bg-white rounded-lg dark:bg-black/20 shadow-primary dark:ring-1 ring-neutral-800/50"
|
||||
>
|
||||
<p>{item}</p>
|
||||
<X className="size-4" />
|
||||
</button>
|
||||
))
|
||||
) : (
|
||||
<div className="flex items-center justify-center text-sm rounded-lg bg-neutral-100 dark:bg-neutral-900 h-14">
|
||||
Please add some hashtag.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<span className="text-sm font-semibold">Hashtags</span>
|
||||
<div className="flex gap-2">
|
||||
<input
|
||||
name="hashtag"
|
||||
placeholder="#nostr"
|
||||
onChange={(e) => setHashtag(e.target.value)}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === "Enter") addHashtag();
|
||||
}}
|
||||
className="w-full px-3 text-sm border-none rounded-lg h-9 bg-neutral-100 dark:bg-neutral-900 placeholder:text-neutral-600 focus:border-neutral-500 focus:ring-0 dark:placeholder:text-neutral-400"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => addHashtag()}
|
||||
className="inline-flex items-center justify-center text-neutral-500 rounded-lg size-9 bg-neutral-200 dark:bg-neutral-800 shrink-0 hover:bg-blue-500 hover:text-white"
|
||||
>
|
||||
<Plus className="size-5" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="mt-2 flex flex-col gap-4">
|
||||
{TOPICS.map((topic) => (
|
||||
<div key={topic.title} className="flex flex-col gap-2">
|
||||
<div className="text-sm font-semibold">{topic.title}</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{topic.content.map((item) => (
|
||||
<button
|
||||
key={item}
|
||||
type="button"
|
||||
onClick={() => toggleHashtag(item)}
|
||||
className={cn(
|
||||
"text-sm p-2 rounded-full",
|
||||
hashtags.includes(item)
|
||||
? "bg-blue-500 text-white"
|
||||
: "bg-neutral-100 dark:bg-neutral-900",
|
||||
)}
|
||||
>
|
||||
{item}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</ScrollArea.Viewport>
|
||||
<ScrollArea.Scrollbar
|
||||
className="flex select-none touch-none p-0.5 duration-[160ms] ease-out data-[orientation=vertical]:w-2"
|
||||
orientation="vertical"
|
||||
>
|
||||
<ScrollArea.Thumb className="flex-1 bg-black/10 dark:bg-white/10 rounded-full relative before:content-[''] before:absolute before:top-1/2 before:left-1/2 before:-translate-x-1/2 before:-translate-y-1/2 before:w-full before:h-full before:min-w-[44px] before:min-h-[44px]" />
|
||||
</ScrollArea.Scrollbar>
|
||||
<ScrollArea.Corner className="bg-transparent" />
|
||||
</ScrollArea.Root>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
13
src/routes/set-interest.tsx
Normal file
13
src/routes/set-interest.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
|
||||
type RouteSearch = {
|
||||
account: string;
|
||||
};
|
||||
|
||||
export const Route = createFileRoute("/set-interest")({
|
||||
validateSearch: (search: Record<string, string>): RouteSearch => {
|
||||
return {
|
||||
account: search.account,
|
||||
};
|
||||
},
|
||||
});
|
||||
@@ -1,6 +1,7 @@
|
||||
import { commands } from "@/commands.gen";
|
||||
import type { LumeColumn, NostrEvent } from "@/types";
|
||||
import { getCurrentWindow } from "@tauri-apps/api/window";
|
||||
import { nanoid } from "nanoid";
|
||||
import type { LumeEvent } from "./event";
|
||||
|
||||
export const LumeWindow = {
|
||||
@@ -150,8 +151,22 @@ export const LumeWindow = {
|
||||
throw new Error(query.error);
|
||||
}
|
||||
},
|
||||
openMainWindow: async () => {
|
||||
const query = await commands.reopenLume();
|
||||
return query;
|
||||
openPopup: async (title: string, url: string) => {
|
||||
const query = await commands.openWindow({
|
||||
label: `popup-${nanoid()}`,
|
||||
url,
|
||||
title,
|
||||
width: 400,
|
||||
height: 500,
|
||||
maximizable: false,
|
||||
minimizable: false,
|
||||
hidden_title: false,
|
||||
});
|
||||
|
||||
if (query.status === "ok") {
|
||||
return query.data;
|
||||
} else {
|
||||
throw new Error(query.error);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user