mirror of
https://github.com/lumehq/lume.git
synced 2025-03-17 21:32:32 +01:00
wip: migrate frontend to new backend
This commit is contained in:
parent
ec78cf8bf7
commit
739ba63e6c
@ -19,11 +19,11 @@ export default function App() {
|
||||
<I18nextProvider i18n={i18n} defaultNS={"translation"}>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Toaster position="top-center" theme="system" closeButton />
|
||||
<StorageProvider>
|
||||
<ArkProvider>
|
||||
<ArkProvider>
|
||||
<StorageProvider>
|
||||
<Router />
|
||||
</ArkProvider>
|
||||
</StorageProvider>
|
||||
</StorageProvider>
|
||||
</ArkProvider>
|
||||
</QueryClientProvider>
|
||||
</I18nextProvider>
|
||||
);
|
||||
|
@ -3,7 +3,7 @@ import { Timeline } from "@columns/timeline";
|
||||
export function HomeScreen() {
|
||||
return (
|
||||
<div className="relative w-full h-full">
|
||||
<Timeline column={{ id: 1, kind: 1, title: "", content: "" }} />
|
||||
<Timeline column={{ id: 1, title: "", content: "" }} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -11,9 +11,11 @@ export class Ark {
|
||||
public async verify_signer() {
|
||||
try {
|
||||
const cmd: string = await invoke("verify_signer");
|
||||
if (!cmd) return false;
|
||||
this.account.pubkey = cmd;
|
||||
return true;
|
||||
if (cmd) {
|
||||
this.account.pubkey = cmd;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} catch (e) {
|
||||
console.error(String(e));
|
||||
}
|
||||
|
@ -5,48 +5,33 @@ import {
|
||||
RefreshIcon,
|
||||
TrashIcon,
|
||||
} from "@lume/icons";
|
||||
import { useColumn } from "@lume/storage";
|
||||
import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { InterestModal } from "./interestModal";
|
||||
import { useColumnContext } from "./provider";
|
||||
|
||||
export function ColumnHeader({
|
||||
id,
|
||||
title,
|
||||
queryKey,
|
||||
}: {
|
||||
id: number;
|
||||
title: string;
|
||||
queryKey?: string[];
|
||||
}) {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const { t } = useTranslation();
|
||||
const { moveColumn, removeColumn } = useColumnContext();
|
||||
const { move, remove } = useColumn();
|
||||
|
||||
const column = useColumnContext();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const refresh = async () => {
|
||||
if (queryKey) await queryClient.refetchQueries({ queryKey });
|
||||
};
|
||||
|
||||
const moveLeft = async () => {
|
||||
moveColumn(id, "left");
|
||||
};
|
||||
|
||||
const moveRight = async () => {
|
||||
moveColumn(id, "right");
|
||||
};
|
||||
|
||||
const deleteWidget = async () => {
|
||||
await removeColumn(id);
|
||||
};
|
||||
|
||||
return (
|
||||
<DropdownMenu.Root>
|
||||
<div className="flex items-center justify-center gap-2 px-3 w-full border-b h-11 shrink-0 border-neutral-100 dark:border-neutral-900">
|
||||
<DropdownMenu.Trigger asChild>
|
||||
<div className="inline-flex items-center gap-1.5">
|
||||
<div className="text-[13px] font-medium">{title}</div>
|
||||
<div className="text-[13px] font-medium">{column.title}</div>
|
||||
<ChevronDownIcon className="size-5" />
|
||||
</div>
|
||||
</DropdownMenu.Trigger>
|
||||
@ -65,18 +50,10 @@ export function ColumnHeader({
|
||||
{t("global.refresh")}
|
||||
</button>
|
||||
</DropdownMenu.Item>
|
||||
{queryKey?.[0] === "foryou-9998" ? (
|
||||
<DropdownMenu.Item asChild>
|
||||
<InterestModal
|
||||
queryKey={queryKey}
|
||||
className="inline-flex items-center gap-3 px-3 text-sm font-medium rounded-lg h-9 text-black/70 hover:bg-black/10 hover:text-black focus:outline-none dark:text-white/70 dark:hover:bg-white/10 dark:hover:text-white"
|
||||
/>
|
||||
</DropdownMenu.Item>
|
||||
) : null}
|
||||
<DropdownMenu.Item asChild>
|
||||
<button
|
||||
type="button"
|
||||
onClick={moveLeft}
|
||||
onClick={() => move(column.id, "left")}
|
||||
className="inline-flex items-center gap-3 px-3 text-sm font-medium rounded-lg h-9 text-black/70 hover:bg-black/10 hover:text-black focus:outline-none dark:text-white/70 dark:hover:bg-white/10 dark:hover:text-white"
|
||||
>
|
||||
<MoveLeftIcon className="size-4" />
|
||||
@ -86,7 +63,7 @@ export function ColumnHeader({
|
||||
<DropdownMenu.Item asChild>
|
||||
<button
|
||||
type="button"
|
||||
onClick={moveRight}
|
||||
onClick={() => move(column.id, "right")}
|
||||
className="inline-flex items-center gap-3 px-3 text-sm font-medium rounded-lg h-9 text-black/70 hover:bg-black/10 hover:text-black focus:outline-none dark:text-white/70 dark:hover:bg-white/10 dark:hover:text-white"
|
||||
>
|
||||
<MoveRightIcon className="size-4" />
|
||||
@ -97,7 +74,7 @@ export function ColumnHeader({
|
||||
<DropdownMenu.Item asChild>
|
||||
<button
|
||||
type="button"
|
||||
onClick={deleteWidget}
|
||||
onClick={() => remove(column.id)}
|
||||
className="inline-flex items-center gap-3 px-3 text-sm font-medium text-red-500 rounded-lg h-9 hover:bg-red-500 hover:text-red-50 focus:outline-none"
|
||||
>
|
||||
<TrashIcon className="size-4" />
|
||||
|
@ -2,9 +2,11 @@ import { Route } from "react-router-dom";
|
||||
import { ColumnContent } from "./content";
|
||||
import { ColumnHeader } from "./header";
|
||||
import { ColumnLiveWidget } from "./live";
|
||||
import { ColumnProvider } from "./provider";
|
||||
import { ColumnRoot } from "./root";
|
||||
|
||||
export const Column = {
|
||||
Provider: ColumnProvider,
|
||||
Root: ColumnRoot,
|
||||
Live: ColumnLiveWidget,
|
||||
Header: ColumnHeader,
|
||||
|
@ -1,7 +1,5 @@
|
||||
import { ArrowUpIcon } from "@lume/icons";
|
||||
import { NDKEvent, NDKFilter } from "@nostr-dev-kit/ndk";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useArk } from "../../hooks/useArk";
|
||||
|
||||
export function ColumnLiveWidget({
|
||||
filter,
|
||||
|
@ -1,136 +1,14 @@
|
||||
import { useStorage } from "@lume/storage";
|
||||
import { IColumn } from "@lume/types";
|
||||
import { COL_TYPES } from "@lume/utils";
|
||||
import {
|
||||
type MutableRefObject,
|
||||
type ReactNode,
|
||||
createContext,
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import { toast } from "sonner";
|
||||
import { type VListHandle } from "virtua";
|
||||
import { LumeColumn } from "@lume/types";
|
||||
import { ReactNode, createContext, useContext } from "react";
|
||||
|
||||
type ColumnContext = {
|
||||
columns: IColumn[];
|
||||
vlistRef: MutableRefObject<VListHandle>;
|
||||
addColumn: (column: IColumn) => Promise<void>;
|
||||
removeColumn: (id: number) => Promise<void>;
|
||||
moveColumn: (id: number, position: "left" | "right") => void;
|
||||
updateColumn: (id: number, title: string, content: string) => Promise<void>;
|
||||
loadAllColumns: () => Promise<IColumn[]>;
|
||||
};
|
||||
|
||||
const ColumnContext = createContext<ColumnContext>(null);
|
||||
|
||||
export function ColumnProvider({ children }: { children: ReactNode }) {
|
||||
const storage = useStorage();
|
||||
const vlistRef = useRef<VListHandle>(null);
|
||||
|
||||
const [columns, setColumns] = useState<IColumn[]>([
|
||||
{
|
||||
id: 9999,
|
||||
title: "Newsfeed",
|
||||
content: "",
|
||||
kind: COL_TYPES.newsfeed,
|
||||
},
|
||||
{
|
||||
id: 9998,
|
||||
title: "For You",
|
||||
content: "",
|
||||
kind: COL_TYPES.foryou,
|
||||
},
|
||||
]);
|
||||
|
||||
const loadAllColumns = useCallback(async () => {
|
||||
return await storage.getColumns();
|
||||
}, []);
|
||||
|
||||
const addColumn = useCallback(async (column: IColumn) => {
|
||||
const result = await storage.createColumn(
|
||||
column.kind,
|
||||
column.title,
|
||||
column.content,
|
||||
);
|
||||
if (result) {
|
||||
setColumns((prev) => [...prev, result]);
|
||||
vlistRef?.current.scrollToIndex(columns.length);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const removeColumn = useCallback(async (id: number) => {
|
||||
if (id === 9998 || id === 9999) {
|
||||
toast.info("You cannot remove default column");
|
||||
return;
|
||||
}
|
||||
|
||||
await storage.removeColumn(id);
|
||||
setColumns((prev) => prev.filter((t) => t.id !== id));
|
||||
}, []);
|
||||
|
||||
const updateColumn = useCallback(
|
||||
async (id: number, title: string, content: string) => {
|
||||
const res = await storage.updateColumn(id, title, content);
|
||||
if (res) {
|
||||
const newCols = columns.map((col) => {
|
||||
if (col.id === id) {
|
||||
return { ...col, title, content };
|
||||
}
|
||||
return col;
|
||||
});
|
||||
|
||||
setColumns(newCols);
|
||||
}
|
||||
},
|
||||
[columns],
|
||||
);
|
||||
|
||||
const moveColumn = useCallback(
|
||||
(id: number, position: "left" | "right") => {
|
||||
const newCols = [...columns];
|
||||
|
||||
const col = newCols.find((el) => el.id === id);
|
||||
const colIndex = newCols.findIndex((el) => el.id === id);
|
||||
|
||||
newCols.splice(colIndex, 1);
|
||||
|
||||
if (position === "left") newCols.splice(colIndex - 1, 0, col);
|
||||
if (position === "right") newCols.splice(colIndex + 1, 0, col);
|
||||
|
||||
setColumns(newCols);
|
||||
},
|
||||
[columns],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
let isMounted = true;
|
||||
|
||||
loadAllColumns().then((data) => {
|
||||
if (isMounted) setColumns((prev) => [...prev, ...data]);
|
||||
});
|
||||
|
||||
return () => {
|
||||
isMounted = false;
|
||||
};
|
||||
}, []);
|
||||
const ColumnContext = createContext<LumeColumn>(null);
|
||||
|
||||
export function ColumnProvider({
|
||||
column,
|
||||
children,
|
||||
}: { column: LumeColumn; children: ReactNode }) {
|
||||
return (
|
||||
<ColumnContext.Provider
|
||||
value={{
|
||||
columns,
|
||||
vlistRef,
|
||||
addColumn,
|
||||
removeColumn,
|
||||
moveColumn,
|
||||
updateColumn,
|
||||
loadAllColumns,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</ColumnContext.Provider>
|
||||
<ColumnContext.Provider value={column}>{children}</ColumnContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,4 @@
|
||||
import { NDKAppHandlerEvent } from "@nostr-dev-kit/ndk";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useArk } from "../../hooks/useArk";
|
||||
|
||||
export function AppHandler({ tag }: { tag: string[] }) {
|
||||
const ark = useArk();
|
||||
|
@ -1,19 +1,7 @@
|
||||
import { COL_TYPES } from "@lume/utils";
|
||||
import { useColumnContext } from "../../column/provider";
|
||||
|
||||
export function Hashtag({ tag }: { tag: string }) {
|
||||
const { addColumn } = useColumnContext();
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={async () =>
|
||||
await addColumn({
|
||||
kind: COL_TYPES.hashtag,
|
||||
title: tag,
|
||||
content: tag,
|
||||
})
|
||||
}
|
||||
className="text-blue-500 break-all cursor-default hover:text-blue-600"
|
||||
>
|
||||
{tag}
|
||||
|
@ -1,11 +1,10 @@
|
||||
import { PinIcon } from "@lume/icons";
|
||||
import { COL_TYPES, NOSTR_MENTIONS } from "@lume/utils";
|
||||
import { NOSTR_MENTIONS } from "@lume/utils";
|
||||
import { ReactNode, useMemo } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Link } from "react-router-dom";
|
||||
import reactStringReplace from "react-string-replace";
|
||||
import { useEvent } from "../../../hooks/useEvent";
|
||||
import { useColumnContext } from "../../column/provider";
|
||||
import { User } from "../../user";
|
||||
import { Hashtag } from "./hashtag";
|
||||
import { MentionUser } from "./user";
|
||||
@ -15,7 +14,6 @@ export function MentionNote({
|
||||
openable = true,
|
||||
}: { eventId: string; openable?: boolean }) {
|
||||
const { t } = useTranslation();
|
||||
const { addColumn } = useColumnContext();
|
||||
const { isLoading, isError, data } = useEvent(eventId);
|
||||
|
||||
const richContent = useMemo(() => {
|
||||
@ -133,13 +131,6 @@ export function MentionNote({
|
||||
</Link>
|
||||
<button
|
||||
type="button"
|
||||
onClick={async () =>
|
||||
await addColumn({
|
||||
kind: COL_TYPES.thread,
|
||||
title: "Thread",
|
||||
content: data.id,
|
||||
})
|
||||
}
|
||||
className="inline-flex items-center justify-center rounded-md text-neutral-600 dark:text-neutral-400 size-6 bg-neutral-200 dark:bg-neutral-800 hover:bg-neutral-300 dark:hover:bg-neutral-700"
|
||||
>
|
||||
<PinIcon className="size-4" />
|
||||
|
@ -1,10 +1,9 @@
|
||||
import { HorizontalDotsIcon } from "@lume/icons";
|
||||
import { COL_TYPES } from "@lume/utils";
|
||||
import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
|
||||
import { writeText } from "@tauri-apps/plugin-clipboard-manager";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Link, useNavigate } from "react-router-dom";
|
||||
import { useArk } from "../../hooks/useArk";
|
||||
import { useArk } from "../../provider";
|
||||
import { useNoteContext } from "./provider";
|
||||
|
||||
export function NoteMenu() {
|
||||
@ -19,7 +18,7 @@ export function NoteMenu() {
|
||||
};
|
||||
|
||||
const copyRaw = async () => {
|
||||
await writeText(JSON.stringify(await event.toNostrEvent()));
|
||||
await writeText(JSON.stringify(event));
|
||||
};
|
||||
|
||||
const copyNpub = async () => {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useArk } from "../../hooks/useArk";
|
||||
import { useArk } from "../../provider";
|
||||
import { AppHandler } from "./appHandler";
|
||||
import { useNoteContext } from "./provider";
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { CheckCircleIcon, DownloadIcon } from "@lume/icons";
|
||||
import { downloadDir } from "@tauri-apps/api/path";
|
||||
import { Window } from "@tauri-apps/api/window";
|
||||
import { WebviewWindow } from "@tauri-apps/api/webview";
|
||||
import { download } from "@tauri-apps/plugin-upload";
|
||||
import { SyntheticEvent, useState } from "react";
|
||||
|
||||
@ -23,7 +23,7 @@ export function ImagePreview({ url }: { url: string }) {
|
||||
|
||||
const open = async () => {
|
||||
const name = new URL(url).pathname.split("/").pop();
|
||||
return new Window("image-viewer", {
|
||||
return new WebviewWindow("image-viewer", {
|
||||
url,
|
||||
title: name,
|
||||
});
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { NavArrowDownIcon } from "@lume/icons";
|
||||
import { NDKEventWithReplies } from "@lume/types";
|
||||
import { EventWithReplies } from "@lume/types";
|
||||
import { cn } from "@lume/utils";
|
||||
import * as Collapsible from "@radix-ui/react-collapsible";
|
||||
import { useState } from "react";
|
||||
@ -10,7 +10,7 @@ import { ChildReply } from "./childReply";
|
||||
export function Reply({
|
||||
event,
|
||||
}: {
|
||||
event: NDKEventWithReplies;
|
||||
event: EventWithReplies;
|
||||
}) {
|
||||
const [t] = useTranslation();
|
||||
const [open, setOpen] = useState(false);
|
||||
|
@ -4,7 +4,7 @@ import { cn } from "@lume/utils";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Note } from "..";
|
||||
import { useArk } from "../../../hooks/useArk";
|
||||
import { useArk } from "../../../provider";
|
||||
import { User } from "../../user";
|
||||
|
||||
export function RepostNote({
|
||||
|
@ -1,10 +1,9 @@
|
||||
import { PinIcon } from "@lume/icons";
|
||||
import { COL_TYPES, cn } from "@lume/utils";
|
||||
import { cn } from "@lume/utils";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Link } from "react-router-dom";
|
||||
import { Note } from ".";
|
||||
import { useArk } from "../../hooks/useArk";
|
||||
import { useColumnContext } from "../column/provider";
|
||||
import { useArk } from "../../provider";
|
||||
import { useNoteContext } from "./provider";
|
||||
|
||||
export function NoteThread({
|
||||
|
@ -2,14 +2,11 @@ import { LoaderIcon } from "@lume/icons";
|
||||
import { cn } from "@lume/utils";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useArk } from "../../hooks/useArk";
|
||||
|
||||
export function UserFollowButton({
|
||||
target,
|
||||
className,
|
||||
}: { target: string; className?: string }) {
|
||||
const ark = useArk();
|
||||
|
||||
const [t] = useTranslation();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [followed, setFollowed] = useState(false);
|
||||
|
@ -8,7 +8,7 @@ export function UserNip05({ className }: { className?: string }) {
|
||||
|
||||
const { isLoading, data: verified } = useQuery({
|
||||
queryKey: ["nip05", user?.profile.nip05],
|
||||
queryFn: async ({ signal }: { signal: AbortSignal }) => {
|
||||
queryFn: async () => {
|
||||
if (!user) return false;
|
||||
if (!user.profile.nip05) return false;
|
||||
return false;
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Metadata } from "@lume/types";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { ReactNode, createContext, useContext } from "react";
|
||||
import { useArk } from "../../hooks/useArk";
|
||||
|
||||
const UserContext = createContext<{ pubkey: string; profile: Metadata }>(null);
|
||||
|
||||
@ -10,13 +10,12 @@ export function UserProvider({
|
||||
children,
|
||||
embed,
|
||||
}: { pubkey: string; children: ReactNode; embed?: string }) {
|
||||
const ark = useArk();
|
||||
const { data: profile } = useQuery({
|
||||
queryKey: ["user", pubkey],
|
||||
queryFn: async () => {
|
||||
if (embed) return JSON.parse(embed) as Metadata;
|
||||
|
||||
const profile = await ark.get_profile(pubkey);
|
||||
const profile: Metadata = await invoke("get_profile", { id: pubkey });
|
||||
|
||||
if (!profile)
|
||||
throw new Error(
|
||||
|
@ -1,10 +0,0 @@
|
||||
import { useContext } from "react";
|
||||
import { ArkContext } from "../provider";
|
||||
|
||||
export const useArk = () => {
|
||||
const context = useContext(ArkContext);
|
||||
if (context === undefined) {
|
||||
throw new Error("Please import Ark Provider to use useArk() hook");
|
||||
}
|
||||
return context;
|
||||
};
|
@ -1,5 +1,5 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useArk } from "./useArk";
|
||||
import { useArk } from "../provider";
|
||||
|
||||
export function useEvent(id: string) {
|
||||
const ark = useArk();
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useArk } from "./useArk";
|
||||
import { useArk } from "../provider";
|
||||
|
||||
export function useProfile(pubkey: string) {
|
||||
const ark = useArk();
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { useArk } from "./useArk";
|
||||
import { useArk } from "../provider";
|
||||
|
||||
export function useRelaylist() {
|
||||
const ark = useArk();
|
||||
|
@ -1,12 +1,9 @@
|
||||
export * from "./ark";
|
||||
export * from "./provider";
|
||||
export * from "./hooks/useEvent";
|
||||
export * from "./hooks/useArk";
|
||||
export * from "./hooks/useProfile";
|
||||
export * from "./hooks/useRelayList";
|
||||
export * from "./components/user";
|
||||
export * from "./components/column";
|
||||
export * from "./components/column/provider";
|
||||
export * from "./components/note";
|
||||
export * from "./components/note/primitives/text";
|
||||
export * from "./components/note/primitives/repost";
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { PropsWithChildren, createContext, useEffect, useMemo } from "react";
|
||||
import { PropsWithChildren, createContext, useContext, useMemo } from "react";
|
||||
import { Ark } from "./ark";
|
||||
|
||||
export const ArkContext = createContext<Ark>(undefined);
|
||||
@ -7,3 +7,11 @@ export const ArkProvider = ({ children }: PropsWithChildren<object>) => {
|
||||
const ark = useMemo(() => new Ark(), []);
|
||||
return <ArkContext.Provider value={ark}>{children}</ArkContext.Provider>;
|
||||
};
|
||||
|
||||
export const useArk = () => {
|
||||
const context = useContext(ArkContext);
|
||||
if (context === undefined) {
|
||||
throw new Error("Ark Provider is not import");
|
||||
}
|
||||
return context;
|
||||
};
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { Column } from "@lume/ark";
|
||||
import { IColumn } from "@lume/types";
|
||||
import { LumeColumn } from "@lume/types";
|
||||
import { EventRoute, UserRoute } from "@lume/ui";
|
||||
import { AntenasForm } from "./components/form";
|
||||
import { HomeRoute } from "./home";
|
||||
|
||||
export function Antenas({ column }: { column: IColumn }) {
|
||||
export function Antenas({ column }: { column: LumeColumn }) {
|
||||
const colKey = `antenas-${column.id}`;
|
||||
const created = !!column.content?.length;
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { Column, useColumnContext } from "@lume/ark";
|
||||
import { IColumn } from "@lume/types";
|
||||
import { LumeColumn } from "@lume/types";
|
||||
import { COL_TYPES } from "@lume/utils";
|
||||
|
||||
export function Default({ column }: { column: IColumn }) {
|
||||
export function Default({ column }: { column: LumeColumn }) {
|
||||
const { addColumn } = useColumnContext();
|
||||
|
||||
return (
|
||||
|
@ -1,13 +1,13 @@
|
||||
import { Column } from "@lume/ark";
|
||||
import { useStorage } from "@lume/storage";
|
||||
import { IColumn } from "@lume/types";
|
||||
import { LumeColumn } from "@lume/types";
|
||||
import { EventRoute, UserRoute } from "@lume/ui";
|
||||
import { NDKEvent, NDKKind } from "@nostr-dev-kit/ndk";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import { useRef } from "react";
|
||||
import { HomeRoute } from "./home";
|
||||
|
||||
export function ForYou({ column }: { column: IColumn }) {
|
||||
export function ForYou({ column }: { column: LumeColumn }) {
|
||||
const colKey = `foryou-${column.id}`;
|
||||
const storage = useStorage();
|
||||
const queryClient = useQueryClient();
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { Column } from "@lume/ark";
|
||||
import { IColumn } from "@lume/types";
|
||||
import { LumeColumn } from "@lume/types";
|
||||
import { EventRoute, UserRoute } from "@lume/ui";
|
||||
import { HomeRoute } from "./home";
|
||||
|
||||
export function Global({ column }: { column: IColumn }) {
|
||||
export function Global({ column }: { column: LumeColumn }) {
|
||||
const colKey = `global-${column.id}`;
|
||||
|
||||
return (
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { Column } from "@lume/ark";
|
||||
import { IColumn } from "@lume/types";
|
||||
import { LumeColumn } from "@lume/types";
|
||||
import { EventRoute, UserRoute } from "@lume/ui";
|
||||
import { GroupForm } from "./components/form";
|
||||
import { HomeRoute } from "./home";
|
||||
|
||||
export function Group({ column }: { column: IColumn }) {
|
||||
export function Group({ column }: { column: LumeColumn }) {
|
||||
const colKey = `group-${column.id}`;
|
||||
const created = !!column.content?.length;
|
||||
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { Column } from "@lume/ark";
|
||||
import { IColumn } from "@lume/types";
|
||||
import { LumeColumn } from "@lume/types";
|
||||
import { EventRoute, UserRoute } from "@lume/ui";
|
||||
import { HomeRoute } from "./home";
|
||||
|
||||
export function Hashtag({ column }: { column: IColumn }) {
|
||||
export function Hashtag({ column }: { column: LumeColumn }) {
|
||||
const colKey = `hashtag-${column.id}`;
|
||||
const hashtag = column.content.replace("#", "");
|
||||
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { Column } from "@lume/ark";
|
||||
import { IColumn } from "@lume/types";
|
||||
import { LumeColumn } from "@lume/types";
|
||||
import { HomeRoute } from "./home";
|
||||
import { EventRoute, UserRoute } from "@lume/ui";
|
||||
|
||||
export function Thread({ column }: { column: IColumn }) {
|
||||
export function Thread({ column }: { column: LumeColumn }) {
|
||||
return (
|
||||
<Column.Root>
|
||||
<Column.Header id={column.id} title={column.title} />
|
||||
|
@ -3,16 +3,15 @@ import { ArrowRightCircleIcon, LoaderIcon, SearchIcon } from "@lume/icons";
|
||||
import { Event, Kind } from "@lume/types";
|
||||
import { EmptyFeed } from "@lume/ui";
|
||||
import { FETCH_LIMIT } from "@lume/utils";
|
||||
import { NDKEvent, NDKKind } from "@nostr-dev-kit/ndk";
|
||||
import { useInfiniteQuery } from "@tanstack/react-query";
|
||||
import { useEffect, useMemo, useRef } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { CacheSnapshot, VList, VListHandle } from "virtua";
|
||||
|
||||
export function HomeRoute({ colKey }: { colKey: string }) {
|
||||
export function HomeRoute({ queryKey }: { queryKey: string }) {
|
||||
const ark = useArk();
|
||||
const ref = useRef<VListHandle>();
|
||||
const cacheKey = `${colKey}-vlist`;
|
||||
const cacheKey = `${queryKey}-vlist`;
|
||||
|
||||
const [offset, cache] = useMemo(() => {
|
||||
const serialized = sessionStorage.getItem(cacheKey);
|
||||
@ -22,16 +21,14 @@ export function HomeRoute({ colKey }: { colKey: string }) {
|
||||
|
||||
const { data, hasNextPage, isLoading, isFetchingNextPage, fetchNextPage } =
|
||||
useInfiniteQuery({
|
||||
queryKey: [colKey],
|
||||
queryKey: [queryKey],
|
||||
initialPageParam: 0,
|
||||
queryFn: async ({
|
||||
signal,
|
||||
pageParam,
|
||||
}: {
|
||||
signal: AbortSignal;
|
||||
pageParam: number;
|
||||
}) => {
|
||||
const events = await ark.get_text_events(FETCH_LIMIT);
|
||||
const events = await ark.get_text_events(FETCH_LIMIT, pageParam);
|
||||
return events;
|
||||
},
|
||||
getNextPageParam: (lastPage) => {
|
||||
@ -71,23 +68,6 @@ export function HomeRoute({ colKey }: { colKey: string }) {
|
||||
};
|
||||
}, []);
|
||||
|
||||
/*
|
||||
if (!ark.account.contacts.length) {
|
||||
return (
|
||||
<div className="px-3 mt-3">
|
||||
<EmptyFeed />
|
||||
<Link
|
||||
to="/suggest"
|
||||
className="mt-3 w-full gap-2 inline-flex items-center justify-center text-sm font-medium rounded-lg h-9 bg-blue-500 hover:bg-blue-600 text-white"
|
||||
>
|
||||
<SearchIcon className="size-5" />
|
||||
Find accounts to follow
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
*/
|
||||
|
||||
return (
|
||||
<div className="w-full h-full">
|
||||
<VList ref={ref} cache={cache} overscan={2} className="flex-1 px-3">
|
||||
|
@ -1,51 +1,25 @@
|
||||
import { Column } from "@lume/ark";
|
||||
import { IColumn } from "@lume/types";
|
||||
import { LumeColumn } from "@lume/types";
|
||||
import { EventRoute, SuggestRoute, UserRoute } from "@lume/ui";
|
||||
import { HomeRoute } from "./home";
|
||||
|
||||
export function Timeline({ column }: { column: IColumn }) {
|
||||
export function Timeline({ column }: { column: LumeColumn }) {
|
||||
const colKey = `timeline-${column.id}`;
|
||||
// const ark = useArk();
|
||||
// const queryClient = useQueryClient();
|
||||
// const since = useRef(Math.floor(Date.now() / 1000));
|
||||
|
||||
/*
|
||||
const refresh = async (events: NDKEvent[]) => {
|
||||
const uniqEvents = new Set(events);
|
||||
await queryClient.setQueryData(
|
||||
[colKey],
|
||||
(prev: { pageParams: number; pages: Array<NDKEvent[]> }) => ({
|
||||
...prev,
|
||||
pages: [[...uniqEvents], ...prev.pages],
|
||||
}),
|
||||
);
|
||||
};
|
||||
*/
|
||||
|
||||
return (
|
||||
<Column.Root>
|
||||
{/*<Column.Header id={column.id} queryKey={[colKey]} title="Timeline" />*/}
|
||||
{/*ark.account.contacts.length ? (
|
||||
<Column.Live
|
||||
filter={{
|
||||
kinds: [NDKKind.Text, NDKKind.Repost],
|
||||
authors: ark.account.contacts,
|
||||
since: since.current,
|
||||
}}
|
||||
onClick={refresh}
|
||||
/>
|
||||
) : null*/}
|
||||
<Column.Content>
|
||||
<Column.Route path="/" element={<HomeRoute colKey={colKey} />} />
|
||||
{/*
|
||||
<Column.Provider column={column}>
|
||||
<Column.Root>
|
||||
<Column.Header queryKey={[colKey]} />
|
||||
<Column.Content>
|
||||
<Column.Route path="/" element={<HomeRoute queryKey={colKey} />} />
|
||||
<Column.Route path="/events/:id" element={<EventRoute />} />
|
||||
<Column.Route path="/users/:id" element={<UserRoute />} />
|
||||
<Column.Route
|
||||
path="/suggest"
|
||||
element={<SuggestRoute queryKey={[colKey]} />}
|
||||
element={<SuggestRoute queryKey={colKey} />}
|
||||
/>
|
||||
*/}
|
||||
</Column.Content>
|
||||
</Column.Root>
|
||||
</Column.Content>
|
||||
</Column.Root>
|
||||
</Column.Provider>
|
||||
);
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { Column } from "@lume/ark";
|
||||
import { IColumn } from "@lume/types";
|
||||
import { LumeColumn } from "@lume/types";
|
||||
import { EventRoute, UserRoute } from "@lume/ui";
|
||||
import { HomeRoute } from "./home";
|
||||
|
||||
export function TrendingNotes({ column }: { column: IColumn }) {
|
||||
export function TrendingNotes({ column }: { column: LumeColumn }) {
|
||||
const colKey = `trending-notes-${column.id}`;
|
||||
|
||||
return (
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { Column } from "@lume/ark";
|
||||
import { IColumn } from "@lume/types";
|
||||
import { LumeColumn } from "@lume/types";
|
||||
import { EventRoute, UserRoute } from "@lume/ui";
|
||||
import { HomeRoute } from "./home";
|
||||
|
||||
export function User({ column }: { column: IColumn }) {
|
||||
export function User({ column }: { column: LumeColumn }) {
|
||||
return (
|
||||
<Column.Root>
|
||||
<Column.Header id={column.id} title={column.title} />
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { Column } from "@lume/ark";
|
||||
import { IColumn } from "@lume/types";
|
||||
import { LumeColumn } from "@lume/types";
|
||||
import { HomeRoute } from "./home";
|
||||
|
||||
export function Waifu({ column }: { column: IColumn }) {
|
||||
export function Waifu({ column }: { column: LumeColumn }) {
|
||||
const colKey = `waifu-${column.id}`;
|
||||
|
||||
return (
|
||||
|
@ -1,442 +0,0 @@
|
||||
// inspired by NDK Cache Dexie
|
||||
// source: https://github.com/nostr-dev-kit/ndk/tree/master/ndk-cache-dexie
|
||||
|
||||
import { LumeStorage } from "@lume/storage";
|
||||
import {
|
||||
Hexpubkey,
|
||||
NDKCacheAdapter,
|
||||
NDKEvent,
|
||||
NDKFilter,
|
||||
NDKRelay,
|
||||
NDKSubscription,
|
||||
NDKUserProfile,
|
||||
profileFromEvent,
|
||||
} from "@nostr-dev-kit/ndk";
|
||||
import { LRUCache } from "lru-cache";
|
||||
import { NostrEvent } from "nostr-fetch";
|
||||
import { matchFilter } from "nostr-tools";
|
||||
|
||||
export class NDKCacheAdapterTauri implements NDKCacheAdapter {
|
||||
#storage: LumeStorage;
|
||||
private dirtyProfiles: Set<Hexpubkey> = new Set();
|
||||
public profiles?: LRUCache<Hexpubkey, NDKUserProfile>;
|
||||
readonly locking: boolean;
|
||||
|
||||
constructor(storage: LumeStorage) {
|
||||
this.#storage = storage;
|
||||
this.locking = true;
|
||||
|
||||
this.profiles = new LRUCache({
|
||||
max: 100000,
|
||||
});
|
||||
|
||||
setInterval(() => {
|
||||
this.dumpProfiles();
|
||||
}, 1000 * 10);
|
||||
}
|
||||
|
||||
public async query(subscription: NDKSubscription): Promise<void> {
|
||||
Promise.allSettled(
|
||||
subscription.filters.map((filter) =>
|
||||
this.processFilter(filter, subscription),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
public async fetchProfile(pubkey: Hexpubkey) {
|
||||
if (!this.profiles) return null;
|
||||
|
||||
let profile = this.profiles.get(pubkey);
|
||||
|
||||
if (!profile) {
|
||||
const user = await this.#storage.getCacheUser(pubkey);
|
||||
if (user) {
|
||||
profile = user.profile as NDKUserProfile;
|
||||
this.profiles.set(pubkey, profile);
|
||||
}
|
||||
}
|
||||
|
||||
return profile;
|
||||
}
|
||||
|
||||
public saveProfile(pubkey: Hexpubkey, profile: NDKUserProfile) {
|
||||
if (!this.profiles) return;
|
||||
|
||||
this.profiles.set(pubkey, profile);
|
||||
|
||||
this.dirtyProfiles.add(pubkey);
|
||||
}
|
||||
|
||||
private async processFilter(
|
||||
filter: NDKFilter,
|
||||
subscription: NDKSubscription,
|
||||
): Promise<void> {
|
||||
const _filter = { ...filter };
|
||||
_filter.limit = undefined;
|
||||
const filterKeys = Object.keys(_filter || {})
|
||||
.sort()
|
||||
.filter((e) => e !== "limit");
|
||||
|
||||
try {
|
||||
await Promise.allSettled([
|
||||
this.byKindAndAuthor(filterKeys, filter, subscription),
|
||||
this.byAuthors(filterKeys, filter, subscription),
|
||||
this.byKinds(filterKeys, filter, subscription),
|
||||
this.byIdsQuery(filterKeys, filter, subscription),
|
||||
this.byNip33Query(filterKeys, filter, subscription),
|
||||
this.byTagsAndOptionallyKinds(filterKeys, filter, subscription),
|
||||
]);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
public async setEvent(
|
||||
event: NDKEvent,
|
||||
filters: NDKFilter[],
|
||||
relay?: NDKRelay,
|
||||
): Promise<void> {
|
||||
if (event.kind === 0) {
|
||||
if (!this.profiles) return;
|
||||
|
||||
const profile: NDKUserProfile = profileFromEvent(event);
|
||||
this.profiles.set(event.pubkey, profile);
|
||||
} else {
|
||||
let addEvent = true;
|
||||
|
||||
if (event.isParamReplaceable()) {
|
||||
const replaceableId = `${event.kind}:${event.pubkey}:${event.tagId()}`;
|
||||
const existingEvent = await this.#storage.getCacheEvent(replaceableId);
|
||||
if (
|
||||
existingEvent &&
|
||||
event.created_at &&
|
||||
existingEvent.createdAt > event.created_at
|
||||
) {
|
||||
addEvent = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (addEvent) {
|
||||
this.#storage.setCacheEvent({
|
||||
id: event.tagId(),
|
||||
pubkey: event.pubkey,
|
||||
content: event.content,
|
||||
// biome-ignore lint/style/noNonNullAssertion: <explanation>
|
||||
kind: event.kind!,
|
||||
// biome-ignore lint/style/noNonNullAssertion: <explanation>
|
||||
createdAt: event.created_at!,
|
||||
relay: relay?.url,
|
||||
event: JSON.stringify(event.rawEvent()),
|
||||
});
|
||||
|
||||
// Don't cache contact lists as tags since it's expensive
|
||||
// and there is no use case for it
|
||||
if (event.kind !== 3) {
|
||||
for (const tag of event.tags) {
|
||||
if (tag[0].length !== 1) return;
|
||||
|
||||
this.#storage.setCacheEventTag({
|
||||
id: `${event.id}:${tag[0]}:${tag[1]}`,
|
||||
eventId: event.id,
|
||||
tag: tag[0],
|
||||
value: tag[1],
|
||||
tagValue: tag[0] + tag[1],
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches by authors
|
||||
*/
|
||||
private async byAuthors(
|
||||
filterKeys: string[],
|
||||
filter: NDKFilter,
|
||||
subscription: NDKSubscription,
|
||||
): Promise<boolean> {
|
||||
const f = ["authors"];
|
||||
const hasAllKeys =
|
||||
filterKeys.length === f.length && f.every((k) => filterKeys.includes(k));
|
||||
|
||||
let foundEvents = false;
|
||||
|
||||
if (hasAllKeys && filter.authors) {
|
||||
for (const pubkey of filter.authors) {
|
||||
const events = await this.#storage.getCacheEventsByPubkey(pubkey);
|
||||
for (const event of events) {
|
||||
let rawEvent: NostrEvent;
|
||||
try {
|
||||
rawEvent = JSON.parse(event.event);
|
||||
} catch (e) {
|
||||
console.log("failed to parse event", e);
|
||||
continue;
|
||||
}
|
||||
|
||||
const ndkEvent = new NDKEvent(undefined, rawEvent);
|
||||
const relay = event.relay ? new NDKRelay(event.relay) : undefined;
|
||||
subscription.eventReceived(ndkEvent, relay, true);
|
||||
foundEvents = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return foundEvents;
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches by kinds
|
||||
*/
|
||||
private async byKinds(
|
||||
filterKeys: string[],
|
||||
filter: NDKFilter,
|
||||
subscription: NDKSubscription,
|
||||
): Promise<boolean> {
|
||||
const f = ["kinds"];
|
||||
const hasAllKeys =
|
||||
filterKeys.length === f.length && f.every((k) => filterKeys.includes(k));
|
||||
|
||||
let foundEvents = false;
|
||||
|
||||
if (hasAllKeys && filter.kinds) {
|
||||
for (const kind of filter.kinds) {
|
||||
const events = await this.#storage.getCacheEventsByKind(kind);
|
||||
for (const event of events) {
|
||||
let rawEvent: NostrEvent;
|
||||
try {
|
||||
rawEvent = JSON.parse(event.event);
|
||||
} catch (e) {
|
||||
console.log("failed to parse event", e);
|
||||
continue;
|
||||
}
|
||||
|
||||
const ndkEvent = new NDKEvent(undefined, rawEvent);
|
||||
const relay = event.relay ? new NDKRelay(event.relay) : undefined;
|
||||
subscription.eventReceived(ndkEvent, relay, true);
|
||||
foundEvents = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return foundEvents;
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches by ids
|
||||
*/
|
||||
private async byIdsQuery(
|
||||
filterKeys: string[],
|
||||
filter: NDKFilter,
|
||||
subscription: NDKSubscription,
|
||||
): Promise<boolean> {
|
||||
const f = ["ids"];
|
||||
const hasAllKeys =
|
||||
filterKeys.length === f.length && f.every((k) => filterKeys.includes(k));
|
||||
|
||||
if (hasAllKeys && filter.ids) {
|
||||
for (const id of filter.ids) {
|
||||
const event = await this.#storage.getCacheEvent(id);
|
||||
if (!event) continue;
|
||||
|
||||
let rawEvent: NostrEvent;
|
||||
try {
|
||||
rawEvent = JSON.parse(event.event);
|
||||
} catch (e) {
|
||||
console.log("failed to parse event", e);
|
||||
continue;
|
||||
}
|
||||
|
||||
const ndkEvent = new NDKEvent(undefined, rawEvent);
|
||||
const relay = event.relay ? new NDKRelay(event.relay) : undefined;
|
||||
subscription.eventReceived(ndkEvent, relay, true);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches by NIP-33
|
||||
*/
|
||||
private async byNip33Query(
|
||||
filterKeys: string[],
|
||||
filter: NDKFilter,
|
||||
subscription: NDKSubscription,
|
||||
): Promise<boolean> {
|
||||
const f = ["#d", "authors", "kinds"];
|
||||
const hasAllKeys =
|
||||
filterKeys.length === f.length && f.every((k) => filterKeys.includes(k));
|
||||
|
||||
if (hasAllKeys && filter.kinds && filter.authors) {
|
||||
for (const kind of filter.kinds) {
|
||||
const replaceableKind = kind >= 30000 && kind < 40000;
|
||||
|
||||
if (!replaceableKind) continue;
|
||||
|
||||
for (const author of filter.authors) {
|
||||
for (const dTag of filter["#d"]) {
|
||||
const replaceableId = `${kind}:${author}:${dTag}`;
|
||||
const event = await this.#storage.getCacheEvent(replaceableId);
|
||||
if (!event) continue;
|
||||
|
||||
let rawEvent: NostrEvent;
|
||||
try {
|
||||
rawEvent = JSON.parse(event.event);
|
||||
} catch (e) {
|
||||
console.log("failed to parse event", e);
|
||||
continue;
|
||||
}
|
||||
|
||||
const ndkEvent = new NDKEvent(undefined, rawEvent);
|
||||
const relay = event.relay ? new NDKRelay(event.relay) : undefined;
|
||||
subscription.eventReceived(ndkEvent, relay, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches by kind & author
|
||||
*/
|
||||
private async byKindAndAuthor(
|
||||
filterKeys: string[],
|
||||
filter: NDKFilter,
|
||||
subscription: NDKSubscription,
|
||||
): Promise<boolean> {
|
||||
const f = ["authors", "kinds"];
|
||||
const hasAllKeys =
|
||||
filterKeys.length === f.length && f.every((k) => filterKeys.includes(k));
|
||||
let foundEvents = false;
|
||||
|
||||
if (!hasAllKeys) return false;
|
||||
|
||||
if (filter.kinds && filter.authors) {
|
||||
for (const kind of filter.kinds) {
|
||||
for (const author of filter.authors) {
|
||||
const events = await this.#storage.getCacheEventsByKindAndAuthor(
|
||||
kind,
|
||||
author,
|
||||
);
|
||||
|
||||
for (const event of events) {
|
||||
let rawEvent: NostrEvent;
|
||||
try {
|
||||
rawEvent = JSON.parse(event.event);
|
||||
} catch (e) {
|
||||
console.log("failed to parse event", e);
|
||||
continue;
|
||||
}
|
||||
|
||||
const ndkEvent = new NDKEvent(undefined, rawEvent);
|
||||
const relay = event.relay ? new NDKRelay(event.relay) : undefined;
|
||||
subscription.eventReceived(ndkEvent, relay, true);
|
||||
foundEvents = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return foundEvents;
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches by tags and optionally filters by tags
|
||||
*/
|
||||
private async byTagsAndOptionallyKinds(
|
||||
filterKeys: string[],
|
||||
filter: NDKFilter,
|
||||
subscription: NDKSubscription,
|
||||
): Promise<boolean> {
|
||||
for (const filterKey of filterKeys) {
|
||||
const isKind = filterKey === "kinds";
|
||||
const isTag = filterKey.startsWith("#") && filterKey.length === 2;
|
||||
|
||||
if (!isKind && !isTag) return false;
|
||||
}
|
||||
|
||||
const events = await this.filterByTag(filterKeys, filter);
|
||||
const kinds = filter.kinds as number[];
|
||||
|
||||
for (const event of events) {
|
||||
// biome-ignore lint/style/noNonNullAssertion: <explanation>
|
||||
if (!kinds?.includes(event.kind!)) continue;
|
||||
subscription.eventReceived(event, undefined, true);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private async filterByTag(
|
||||
filterKeys: string[],
|
||||
filter: NDKFilter,
|
||||
): Promise<NDKEvent[]> {
|
||||
const retEvents: NDKEvent[] = [];
|
||||
|
||||
for (const filterKey of filterKeys) {
|
||||
if (filterKey.length !== 2) continue;
|
||||
const tag = filterKey.slice(1);
|
||||
// const values = filter[filterKey] as string[];
|
||||
const values: string[] = [];
|
||||
for (const [key, value] of Object.entries(filter)) {
|
||||
if (key === filterKey) values.push(value as string);
|
||||
}
|
||||
|
||||
for (const value of values) {
|
||||
const eventTags = await this.#storage.getCacheEventTagsByTagValue(
|
||||
tag + value,
|
||||
);
|
||||
if (!eventTags.length) continue;
|
||||
|
||||
const eventIds = eventTags.map((t) => t.eventId);
|
||||
|
||||
const events = await this.#storage.getCacheEvents(eventIds);
|
||||
for (const event of events) {
|
||||
let rawEvent: NostrEvent;
|
||||
try {
|
||||
rawEvent = JSON.parse(event.event);
|
||||
|
||||
// Make sure all passed filters match the event
|
||||
if (!matchFilter(filter, rawEvent)) continue;
|
||||
} catch (e) {
|
||||
console.log("failed to parse event", e);
|
||||
continue;
|
||||
}
|
||||
|
||||
const ndkEvent = new NDKEvent(undefined, rawEvent);
|
||||
const relay = event.relay ? new NDKRelay(event.relay) : undefined;
|
||||
ndkEvent.relay = relay;
|
||||
retEvents.push(ndkEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return retEvents;
|
||||
}
|
||||
|
||||
private async dumpProfiles(): Promise<void> {
|
||||
const profiles = [];
|
||||
|
||||
if (!this.profiles) return;
|
||||
|
||||
for (const pubkey of this.dirtyProfiles) {
|
||||
const profile = this.profiles.get(pubkey);
|
||||
|
||||
if (!profile) continue;
|
||||
|
||||
profiles.push({
|
||||
pubkey,
|
||||
profile: JSON.stringify(profile),
|
||||
createdAt: Date.now(),
|
||||
});
|
||||
}
|
||||
|
||||
if (profiles.length) {
|
||||
await this.#storage.setCacheProfiles(profiles);
|
||||
}
|
||||
|
||||
this.dirtyProfiles.clear();
|
||||
}
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
{
|
||||
"name": "@lume/ndk-cache-tauri",
|
||||
"version": "0.0.0",
|
||||
"main": "./index.ts",
|
||||
"private": true,
|
||||
"license": "MIT",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"dependencies": {
|
||||
"@lume/storage": "workspace:*",
|
||||
"@nostr-dev-kit/ndk": "^2.4.0",
|
||||
"lru-cache": "^10.2.0",
|
||||
"nostr-fetch": "^0.15.0",
|
||||
"nostr-tools": "1.17.0",
|
||||
"react": "^18.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@lume/tsconfig": "workspace:*",
|
||||
"@types/react": "^18.2.52",
|
||||
"typescript": "^5.3.3"
|
||||
}
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
{
|
||||
"extends": "@lume/tsconfig/base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "dist"
|
||||
},
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
@ -9,7 +9,11 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@tauri-apps/plugin-store": "2.0.0-beta.0",
|
||||
"react": "^18.2.0"
|
||||
"react": "^18.2.0",
|
||||
"scheduler": "^0.23.0",
|
||||
"use-context-selector": "^1.4.1",
|
||||
"virtua": "^0.23.3",
|
||||
"zustand": "^4.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@lume/tsconfig": "workspace:*",
|
||||
|
@ -1,2 +1 @@
|
||||
export * from "./storage";
|
||||
export * from "./provider";
|
||||
|
@ -1,26 +1,118 @@
|
||||
import { LumeColumn } from "@lume/types";
|
||||
import { locale, platform } from "@tauri-apps/plugin-os";
|
||||
import { Store } from "@tauri-apps/plugin-store";
|
||||
import { PropsWithChildren, createContext, useContext } from "react";
|
||||
import {
|
||||
MutableRefObject,
|
||||
PropsWithChildren,
|
||||
useCallback,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import { createContext, useContextSelector } from "use-context-selector";
|
||||
import { type VListHandle } from "virtua";
|
||||
import { LumeStorage } from "./storage";
|
||||
|
||||
const StorageContext = createContext<LumeStorage>(null);
|
||||
|
||||
const store = new Store("lume.data");
|
||||
const platformName = await platform();
|
||||
const osLocale = await locale();
|
||||
|
||||
const db = new LumeStorage(store, platformName, osLocale);
|
||||
const store = new Store("lume.dat");
|
||||
const storage = new LumeStorage(store, platformName, osLocale);
|
||||
await storage.init();
|
||||
|
||||
type StorageContext = {
|
||||
storage: LumeStorage;
|
||||
column: {
|
||||
columns: LumeColumn[];
|
||||
vlistRef: MutableRefObject<VListHandle>;
|
||||
create: (column: LumeColumn) => void;
|
||||
remove: (id: number) => void;
|
||||
move: (id: number, position: "left" | "right") => void;
|
||||
update: (id: number, title: string, content: string) => void;
|
||||
};
|
||||
};
|
||||
|
||||
const StorageContext = createContext<StorageContext>(null);
|
||||
|
||||
export const StorageProvider = ({ children }: PropsWithChildren<object>) => {
|
||||
const vlistRef = useRef<VListHandle>(null);
|
||||
|
||||
const [columns, setColumns] = useState<LumeColumn[]>([
|
||||
{
|
||||
id: 1,
|
||||
title: "Newsfeed",
|
||||
content: "",
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: "For You",
|
||||
content: "",
|
||||
},
|
||||
]);
|
||||
|
||||
const create = useCallback((column: LumeColumn) => {
|
||||
setColumns((prev) => [...prev, column]);
|
||||
vlistRef?.current.scrollToIndex(columns.length);
|
||||
}, []);
|
||||
|
||||
const remove = useCallback((id: number) => {
|
||||
setColumns((prev) => prev.filter((t) => t.id !== id));
|
||||
}, []);
|
||||
|
||||
const update = useCallback(
|
||||
(id: number, title: string, content: string) => {
|
||||
const newCols = columns.map((col) => {
|
||||
if (col.id === id) {
|
||||
return { ...col, title, content };
|
||||
}
|
||||
return col;
|
||||
});
|
||||
|
||||
setColumns(newCols);
|
||||
},
|
||||
[columns],
|
||||
);
|
||||
|
||||
const move = useCallback(
|
||||
(id: number, position: "left" | "right") => {
|
||||
const newCols = [...columns];
|
||||
|
||||
const col = newCols.find((el) => el.id === id);
|
||||
const colIndex = newCols.findIndex((el) => el.id === id);
|
||||
|
||||
newCols.splice(colIndex, 1);
|
||||
|
||||
if (position === "left") newCols.splice(colIndex - 1, 0, col);
|
||||
if (position === "right") newCols.splice(colIndex + 1, 0, col);
|
||||
|
||||
setColumns(newCols);
|
||||
},
|
||||
[columns],
|
||||
);
|
||||
|
||||
return (
|
||||
<StorageContext.Provider value={db}>{children}</StorageContext.Provider>
|
||||
<StorageContext.Provider
|
||||
value={{
|
||||
storage,
|
||||
column: { columns, vlistRef, create, remove, move, update },
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</StorageContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useStorage = () => {
|
||||
const context = useContext(StorageContext);
|
||||
const context = useContextSelector(StorageContext, (state) => state.storage);
|
||||
if (context === undefined) {
|
||||
throw new Error("Please import Storage Provider to use useStorage() hook");
|
||||
throw new Error("Storage Provider is required");
|
||||
}
|
||||
return context;
|
||||
};
|
||||
|
||||
export const useColumn = () => {
|
||||
const context = useContextSelector(StorageContext, (state) => state.column);
|
||||
if (context === undefined) {
|
||||
throw new Error("Storage Provider is required");
|
||||
}
|
||||
return context;
|
||||
};
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { Settings } from "@lume/types";
|
||||
import { Platform } from "@tauri-apps/plugin-os";
|
||||
import { Store } from "@tauri-apps/plugin-store";
|
||||
|
||||
@ -5,17 +6,7 @@ export class LumeStorage {
|
||||
#store: Store;
|
||||
readonly platform: Platform;
|
||||
readonly locale: string;
|
||||
public settings: {
|
||||
autoupdate: boolean;
|
||||
nsecbunker: boolean;
|
||||
media: boolean;
|
||||
hashtag: boolean;
|
||||
lowPower: boolean;
|
||||
translation: boolean;
|
||||
translateApiKey: string;
|
||||
instantZap: boolean;
|
||||
defaultZapAmount: number;
|
||||
};
|
||||
public settings: Settings;
|
||||
|
||||
constructor(store: Store, platform: Platform, locale: string) {
|
||||
this.#store = store;
|
||||
@ -34,8 +25,24 @@ export class LumeStorage {
|
||||
};
|
||||
}
|
||||
|
||||
public async createSetting(key: string, value: string | boolean) {
|
||||
public async init() {
|
||||
this.loadSettings();
|
||||
}
|
||||
|
||||
public async loadSettings() {
|
||||
const settings: Settings = JSON.parse(await this.#store.get("settings"));
|
||||
for (const [key, value] of Object.entries(settings)) {
|
||||
this.settings[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
public async createSetting(key: string, value: string | number | boolean) {
|
||||
this.settings[key] = value;
|
||||
await this.#store.set(this.settings[key], { value });
|
||||
|
||||
const settings: Settings = JSON.parse(await this.#store.get("settings"));
|
||||
const newSettings = { ...settings, key: value };
|
||||
|
||||
await this.#store.set("settings", newSettings);
|
||||
await this.#store.save();
|
||||
}
|
||||
}
|
||||
|
71
packages/types/index.d.ts
vendored
71
packages/types/index.d.ts
vendored
@ -1,4 +1,14 @@
|
||||
import { type NDKEvent, type NDKUserProfile } from "@nostr-dev-kit/ndk";
|
||||
export interface Settings {
|
||||
autoupdate: boolean;
|
||||
nsecbunker: boolean;
|
||||
media: boolean;
|
||||
hashtag: boolean;
|
||||
lowPower: boolean;
|
||||
translation: boolean;
|
||||
translateApiKey: string;
|
||||
instantZap: boolean;
|
||||
defaultZapAmount: number;
|
||||
}
|
||||
|
||||
export interface Keys {
|
||||
npub: string;
|
||||
@ -28,16 +38,20 @@ export interface Event {
|
||||
sig: string;
|
||||
}
|
||||
|
||||
export interface EventWithReplies extends Event {
|
||||
replies: Array<Event>;
|
||||
}
|
||||
|
||||
export interface Metadata {
|
||||
name: Option<string>;
|
||||
display_name: Option<string>;
|
||||
about: Option<string>;
|
||||
website: Option<string>;
|
||||
picture: Option<string>;
|
||||
banner: Option<string>;
|
||||
nip05: Option<string>;
|
||||
lud06: Option<string>;
|
||||
lud16: Option<string>;
|
||||
name?: string;
|
||||
display_name?: string;
|
||||
about?: string;
|
||||
website?: string;
|
||||
picture?: string;
|
||||
banner?: string;
|
||||
nip05?: string;
|
||||
lud06?: string;
|
||||
lud16?: string;
|
||||
}
|
||||
|
||||
export interface CurrentAccount {
|
||||
@ -61,9 +75,8 @@ export interface RichContent {
|
||||
notes: string[];
|
||||
}
|
||||
|
||||
export interface IColumn {
|
||||
id?: number;
|
||||
kind: number;
|
||||
export interface LumeColumn {
|
||||
id: number;
|
||||
title: string;
|
||||
content: string;
|
||||
}
|
||||
@ -75,10 +88,6 @@ export interface Opengraph {
|
||||
image?: string;
|
||||
}
|
||||
|
||||
export interface NDKEventWithReplies extends NDKEvent {
|
||||
replies: Array<NDKEvent>;
|
||||
}
|
||||
|
||||
export interface NostrBuildResponse {
|
||||
ok: boolean;
|
||||
data?: {
|
||||
@ -99,34 +108,6 @@ export interface NostrBuildResponse {
|
||||
};
|
||||
}
|
||||
|
||||
export interface NDKCacheUser {
|
||||
pubkey: string;
|
||||
profile: string | NDKUserProfile;
|
||||
createdAt: number;
|
||||
}
|
||||
|
||||
export interface NDKCacheUserProfile extends NDKUserProfile {
|
||||
npub: string;
|
||||
}
|
||||
|
||||
export interface NDKCacheEvent {
|
||||
id: string;
|
||||
pubkey: string;
|
||||
content: string;
|
||||
kind: number;
|
||||
createdAt: number;
|
||||
relay: string;
|
||||
event: string;
|
||||
}
|
||||
|
||||
export interface NDKCacheEventTag {
|
||||
id: string;
|
||||
eventId: string;
|
||||
tag: string;
|
||||
value: string;
|
||||
tagValue: string;
|
||||
}
|
||||
|
||||
export interface NIP11 {
|
||||
name: string;
|
||||
description: string;
|
||||
|
@ -10,8 +10,5 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nostr-dev-kit/ndk": "^2.4.0"
|
||||
}
|
||||
}
|
||||
|
@ -1,20 +1,11 @@
|
||||
import { useStorage } from "@lume/storage";
|
||||
import { cn } from "@lume/utils";
|
||||
import { Outlet } from "react-router-dom";
|
||||
import { Editor } from "../editor/column";
|
||||
import { Navigation } from "../navigation";
|
||||
import { SearchDialog } from "../search/dialog";
|
||||
|
||||
export function AppLayout() {
|
||||
const storage = useStorage();
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"flex h-screen w-screen flex-col",
|
||||
storage.platform !== "macos" ? "bg-neutral-50 dark:bg-neutral-950" : "",
|
||||
)}
|
||||
>
|
||||
<div className="flex h-screen w-screen flex-col bg-gradient-to-tl from-neutral-50 to-neutral-200 dark:from-neutral-950 dark:to-neutral-800">
|
||||
<div data-tauri-drag-region className="h-9 shrink-0" />
|
||||
<div className="flex w-full h-full min-h-0">
|
||||
<Navigation />
|
||||
|
@ -25,7 +25,7 @@ const LUME_USERS = [
|
||||
"npub1zfss807aer0j26mwp2la0ume0jqde3823rmu97ra6sgyyg956e0s6xw445",
|
||||
];
|
||||
|
||||
export function SuggestRoute({ queryKey }: { queryKey: string[] }) {
|
||||
export function SuggestRoute({ queryKey }: { queryKey: string }) {
|
||||
const queryClient = useQueryClient();
|
||||
const navigate = useNavigate();
|
||||
|
||||
@ -45,7 +45,7 @@ export function SuggestRoute({ queryKey }: { queryKey: string[] }) {
|
||||
|
||||
const submit = async () => {
|
||||
try {
|
||||
await queryClient.refetchQueries({ queryKey });
|
||||
await queryClient.refetchQueries({ queryKey: [queryKey] });
|
||||
return navigate("/", { replace: true });
|
||||
} catch (e) {
|
||||
toast.error(String(e));
|
||||
|
61
pnpm-lock.yaml
generated
61
pnpm-lock.yaml
generated
@ -1061,6 +1061,18 @@ importers:
|
||||
react:
|
||||
specifier: ^18.2.0
|
||||
version: 18.2.0
|
||||
scheduler:
|
||||
specifier: ^0.23.0
|
||||
version: 0.23.0
|
||||
use-context-selector:
|
||||
specifier: ^1.4.1
|
||||
version: 1.4.1(react@18.2.0)(scheduler@0.23.0)
|
||||
virtua:
|
||||
specifier: ^0.23.3
|
||||
version: 0.23.3(react-dom@18.2.0)(react@18.2.0)
|
||||
zustand:
|
||||
specifier: ^4.5.0
|
||||
version: 4.5.0(@types/react@18.2.52)(react@18.2.0)
|
||||
devDependencies:
|
||||
'@lume/tsconfig':
|
||||
specifier: workspace:*
|
||||
@ -1100,10 +1112,6 @@ importers:
|
||||
packages/tsconfig: {}
|
||||
|
||||
packages/types:
|
||||
dependencies:
|
||||
'@nostr-dev-kit/ndk':
|
||||
specifier: ^2.4.0
|
||||
version: 2.4.0(typescript@5.3.3)
|
||||
devDependencies:
|
||||
typescript:
|
||||
specifier: ^5.3.3
|
||||
@ -9812,6 +9820,23 @@ packages:
|
||||
tslib: 2.6.2
|
||||
dev: false
|
||||
|
||||
/use-context-selector@1.4.1(react@18.2.0)(scheduler@0.23.0):
|
||||
resolution: {integrity: sha512-Io2ArvcRO+6MWIhkdfMFt+WKQX+Vb++W8DS2l03z/Vw/rz3BclKpM0ynr4LYGyU85Eke+Yx5oIhTY++QR0ZDoA==}
|
||||
peerDependencies:
|
||||
react: '>=16.8.0'
|
||||
react-dom: '*'
|
||||
react-native: '*'
|
||||
scheduler: '>=0.19.0'
|
||||
peerDependenciesMeta:
|
||||
react-dom:
|
||||
optional: true
|
||||
react-native:
|
||||
optional: true
|
||||
dependencies:
|
||||
react: 18.2.0
|
||||
scheduler: 0.23.0
|
||||
dev: false
|
||||
|
||||
/use-debounce@10.0.0(react@18.2.0):
|
||||
resolution: {integrity: sha512-XRjvlvCB46bah9IBXVnq/ACP2lxqXyZj0D9hj4K5OzNroMDpTEBg8Anuh1/UfRTRs7pLhQ+RiNxxwZu9+MVl1A==}
|
||||
engines: {node: '>= 16.0.0'}
|
||||
@ -9837,6 +9862,14 @@ packages:
|
||||
tslib: 2.6.2
|
||||
dev: false
|
||||
|
||||
/use-sync-external-store@1.2.0(react@18.2.0):
|
||||
resolution: {integrity: sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
dependencies:
|
||||
react: 18.2.0
|
||||
dev: false
|
||||
|
||||
/utf-8-validate@5.0.10:
|
||||
resolution: {integrity: sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==}
|
||||
engines: {node: '>=6.14.2'}
|
||||
@ -10395,5 +10428,25 @@ packages:
|
||||
resolution: {integrity: sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==}
|
||||
dev: false
|
||||
|
||||
/zustand@4.5.0(@types/react@18.2.52)(react@18.2.0):
|
||||
resolution: {integrity: sha512-zlVFqS5TQ21nwijjhJlx4f9iGrXSL0o/+Dpy4txAP22miJ8Ti6c1Ol1RLNN98BMib83lmDH/2KmLwaNXpjrO1A==}
|
||||
engines: {node: '>=12.7.0'}
|
||||
peerDependencies:
|
||||
'@types/react': '>=16.8'
|
||||
immer: '>=9.0.6'
|
||||
react: '>=16.8'
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
immer:
|
||||
optional: true
|
||||
react:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@types/react': 18.2.52
|
||||
react: 18.2.0
|
||||
use-sync-external-store: 1.2.0(react@18.2.0)
|
||||
dev: false
|
||||
|
||||
/zwitch@2.0.4:
|
||||
resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}
|
||||
|
@ -19,6 +19,9 @@
|
||||
"notification:default",
|
||||
"os:allow-locale",
|
||||
"os:allow-platform",
|
||||
"updater:allow-check",
|
||||
"updater:default",
|
||||
"window:allow-start-dragging",
|
||||
{
|
||||
"identifier": "http:default",
|
||||
"allow": [
|
||||
|
@ -1 +1 @@
|
||||
{"desktop-capability":{"identifier":"desktop-capability","description":"Capability for the desktop","context":"local","windows":["main","settings","event-*","user-*","column-*"],"permissions":["path:default","event:default","window:default","app:default","resources:default","menu:default","tray:default","theme:allow-set-theme","theme:allow-get-theme","notification:allow-is-permission-granted","notification:allow-request-permission","notification:default","os:allow-locale","os:allow-platform",{"identifier":"http:default","allow":[{"url":"http://**/"},{"url":"https://**/"}]},{"identifier":"fs:allow-read-text-file","allow":[{"path":"$RESOURCE/locales/*"}]}],"platforms":["linux","macOS","windows"]}}
|
||||
{"desktop-capability":{"identifier":"desktop-capability","description":"Capability for the desktop","context":"local","windows":["main","settings","event-*","user-*","column-*"],"permissions":["path:default","event:default","window:default","app:default","resources:default","menu:default","tray:default","theme:allow-set-theme","theme:allow-get-theme","notification:allow-is-permission-granted","notification:allow-request-permission","notification:default","os:allow-locale","os:allow-platform","updater:allow-check","updater:default","window:allow-start-dragging",{"identifier":"http:default","allow":[{"url":"http://**/"},{"url":"https://**/"}]},{"identifier":"fs:allow-read-text-file","allow":[{"path":"$RESOURCE/locales/*"}]}],"platforms":["linux","macOS","windows"]}}
|
@ -1,19 +1,17 @@
|
||||
{
|
||||
"$schema": "../node_modules/@tauri-apps/cli/schema.json",
|
||||
"app": {
|
||||
"windows": [
|
||||
{
|
||||
"width": 1080,
|
||||
"height": 800,
|
||||
"minWidth": 1080,
|
||||
"minHeight": 800,
|
||||
"resizable": true,
|
||||
"title": "Lume",
|
||||
"center": true,
|
||||
"fullscreen": false,
|
||||
"fileDropEnabled": true,
|
||||
"decorations": true
|
||||
}
|
||||
]
|
||||
}
|
||||
"$schema": "../node_modules/@tauri-apps/cli/schema.json",
|
||||
"app": {
|
||||
"windows": [
|
||||
{
|
||||
"title": "Lume",
|
||||
"label": "main",
|
||||
"titleBarStyle": "Overlay",
|
||||
"width": 1080,
|
||||
"height": 800,
|
||||
"minWidth": 1080,
|
||||
"minHeight": 800,
|
||||
"center": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -1,27 +1,19 @@
|
||||
{
|
||||
"$schema": "../node_modules/@tauri-apps/cli/schema.json",
|
||||
"app": {
|
||||
"windows": [
|
||||
{
|
||||
"width": 1080,
|
||||
"height": 800,
|
||||
"minWidth": 1080,
|
||||
"minHeight": 800,
|
||||
"resizable": true,
|
||||
"title": "Lume",
|
||||
"titleBarStyle": "Overlay",
|
||||
"center": true,
|
||||
"fullscreen": false,
|
||||
"hiddenTitle": true,
|
||||
"fileDropEnabled": true,
|
||||
"decorations": true,
|
||||
"transparent": true,
|
||||
"windowEffects": {
|
||||
"effects": [
|
||||
"sidebar"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
"$schema": "../node_modules/@tauri-apps/cli/schema.json",
|
||||
"app": {
|
||||
"windows": [
|
||||
{
|
||||
"title": "Lume",
|
||||
"label": "main",
|
||||
"titleBarStyle": "Overlay",
|
||||
"width": 1080,
|
||||
"height": 800,
|
||||
"minWidth": 1080,
|
||||
"minHeight": 800,
|
||||
"center": true,
|
||||
"hiddenTitle": true,
|
||||
"decorations": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -1,20 +1,16 @@
|
||||
{
|
||||
"$schema": "../node_modules/@tauri-apps/cli/schema.json",
|
||||
"app": {
|
||||
"windows": [
|
||||
{
|
||||
"width": 1080,
|
||||
"height": 800,
|
||||
"minWidth": 1080,
|
||||
"minHeight": 800,
|
||||
"resizable": true,
|
||||
"title": "Lume",
|
||||
"center": true,
|
||||
"fullscreen": false,
|
||||
"hiddenTitle": true,
|
||||
"fileDropEnabled": true,
|
||||
"decorations": false
|
||||
}
|
||||
]
|
||||
}
|
||||
"$schema": "../node_modules/@tauri-apps/cli/schema.json",
|
||||
"app": {
|
||||
"windows": [
|
||||
{
|
||||
"title": "Lume",
|
||||
"label": "main",
|
||||
"width": 1080,
|
||||
"height": 800,
|
||||
"minWidth": 1080,
|
||||
"minHeight": 800,
|
||||
"center": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user