wip: migrate frontend to new backend
This commit is contained in:
@@ -19,11 +19,11 @@ export default function App() {
|
|||||||
<I18nextProvider i18n={i18n} defaultNS={"translation"}>
|
<I18nextProvider i18n={i18n} defaultNS={"translation"}>
|
||||||
<QueryClientProvider client={queryClient}>
|
<QueryClientProvider client={queryClient}>
|
||||||
<Toaster position="top-center" theme="system" closeButton />
|
<Toaster position="top-center" theme="system" closeButton />
|
||||||
<StorageProvider>
|
<ArkProvider>
|
||||||
<ArkProvider>
|
<StorageProvider>
|
||||||
<Router />
|
<Router />
|
||||||
</ArkProvider>
|
</StorageProvider>
|
||||||
</StorageProvider>
|
</ArkProvider>
|
||||||
</QueryClientProvider>
|
</QueryClientProvider>
|
||||||
</I18nextProvider>
|
</I18nextProvider>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { Timeline } from "@columns/timeline";
|
|||||||
export function HomeScreen() {
|
export function HomeScreen() {
|
||||||
return (
|
return (
|
||||||
<div className="relative w-full h-full">
|
<div className="relative w-full h-full">
|
||||||
<Timeline column={{ id: 1, kind: 1, title: "", content: "" }} />
|
<Timeline column={{ id: 1, title: "", content: "" }} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,9 +11,11 @@ export class Ark {
|
|||||||
public async verify_signer() {
|
public async verify_signer() {
|
||||||
try {
|
try {
|
||||||
const cmd: string = await invoke("verify_signer");
|
const cmd: string = await invoke("verify_signer");
|
||||||
if (!cmd) return false;
|
if (cmd) {
|
||||||
this.account.pubkey = cmd;
|
this.account.pubkey = cmd;
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(String(e));
|
console.error(String(e));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,48 +5,33 @@ import {
|
|||||||
RefreshIcon,
|
RefreshIcon,
|
||||||
TrashIcon,
|
TrashIcon,
|
||||||
} from "@lume/icons";
|
} from "@lume/icons";
|
||||||
|
import { useColumn } from "@lume/storage";
|
||||||
import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
|
import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
|
||||||
import { useQueryClient } from "@tanstack/react-query";
|
import { useQueryClient } from "@tanstack/react-query";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { InterestModal } from "./interestModal";
|
|
||||||
import { useColumnContext } from "./provider";
|
import { useColumnContext } from "./provider";
|
||||||
|
|
||||||
export function ColumnHeader({
|
export function ColumnHeader({
|
||||||
id,
|
|
||||||
title,
|
|
||||||
queryKey,
|
queryKey,
|
||||||
}: {
|
}: {
|
||||||
id: number;
|
|
||||||
title: string;
|
|
||||||
queryKey?: string[];
|
queryKey?: string[];
|
||||||
}) {
|
}) {
|
||||||
const queryClient = useQueryClient();
|
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { moveColumn, removeColumn } = useColumnContext();
|
const { move, remove } = useColumn();
|
||||||
|
|
||||||
|
const column = useColumnContext();
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
const refresh = async () => {
|
const refresh = async () => {
|
||||||
if (queryKey) await queryClient.refetchQueries({ queryKey });
|
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 (
|
return (
|
||||||
<DropdownMenu.Root>
|
<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">
|
<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>
|
<DropdownMenu.Trigger asChild>
|
||||||
<div className="inline-flex items-center gap-1.5">
|
<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" />
|
<ChevronDownIcon className="size-5" />
|
||||||
</div>
|
</div>
|
||||||
</DropdownMenu.Trigger>
|
</DropdownMenu.Trigger>
|
||||||
@@ -65,18 +50,10 @@ export function ColumnHeader({
|
|||||||
{t("global.refresh")}
|
{t("global.refresh")}
|
||||||
</button>
|
</button>
|
||||||
</DropdownMenu.Item>
|
</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>
|
<DropdownMenu.Item asChild>
|
||||||
<button
|
<button
|
||||||
type="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"
|
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" />
|
<MoveLeftIcon className="size-4" />
|
||||||
@@ -86,7 +63,7 @@ export function ColumnHeader({
|
|||||||
<DropdownMenu.Item asChild>
|
<DropdownMenu.Item asChild>
|
||||||
<button
|
<button
|
||||||
type="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"
|
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" />
|
<MoveRightIcon className="size-4" />
|
||||||
@@ -97,7 +74,7 @@ export function ColumnHeader({
|
|||||||
<DropdownMenu.Item asChild>
|
<DropdownMenu.Item asChild>
|
||||||
<button
|
<button
|
||||||
type="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"
|
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" />
|
<TrashIcon className="size-4" />
|
||||||
|
|||||||
@@ -2,9 +2,11 @@ import { Route } from "react-router-dom";
|
|||||||
import { ColumnContent } from "./content";
|
import { ColumnContent } from "./content";
|
||||||
import { ColumnHeader } from "./header";
|
import { ColumnHeader } from "./header";
|
||||||
import { ColumnLiveWidget } from "./live";
|
import { ColumnLiveWidget } from "./live";
|
||||||
|
import { ColumnProvider } from "./provider";
|
||||||
import { ColumnRoot } from "./root";
|
import { ColumnRoot } from "./root";
|
||||||
|
|
||||||
export const Column = {
|
export const Column = {
|
||||||
|
Provider: ColumnProvider,
|
||||||
Root: ColumnRoot,
|
Root: ColumnRoot,
|
||||||
Live: ColumnLiveWidget,
|
Live: ColumnLiveWidget,
|
||||||
Header: ColumnHeader,
|
Header: ColumnHeader,
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
import { ArrowUpIcon } from "@lume/icons";
|
import { ArrowUpIcon } from "@lume/icons";
|
||||||
import { NDKEvent, NDKFilter } from "@nostr-dev-kit/ndk";
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useArk } from "../../hooks/useArk";
|
|
||||||
|
|
||||||
export function ColumnLiveWidget({
|
export function ColumnLiveWidget({
|
||||||
filter,
|
filter,
|
||||||
|
|||||||
@@ -1,136 +1,14 @@
|
|||||||
import { useStorage } from "@lume/storage";
|
import { LumeColumn } from "@lume/types";
|
||||||
import { IColumn } from "@lume/types";
|
import { ReactNode, createContext, useContext } from "react";
|
||||||
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";
|
|
||||||
|
|
||||||
type ColumnContext = {
|
const ColumnContext = createContext<LumeColumn>(null);
|
||||||
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;
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
|
export function ColumnProvider({
|
||||||
|
column,
|
||||||
|
children,
|
||||||
|
}: { column: LumeColumn; children: ReactNode }) {
|
||||||
return (
|
return (
|
||||||
<ColumnContext.Provider
|
<ColumnContext.Provider value={column}>{children}</ColumnContext.Provider>
|
||||||
value={{
|
|
||||||
columns,
|
|
||||||
vlistRef,
|
|
||||||
addColumn,
|
|
||||||
removeColumn,
|
|
||||||
moveColumn,
|
|
||||||
updateColumn,
|
|
||||||
loadAllColumns,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</ColumnContext.Provider>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
import { NDKAppHandlerEvent } from "@nostr-dev-kit/ndk";
|
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { useArk } from "../../hooks/useArk";
|
|
||||||
|
|
||||||
export function AppHandler({ tag }: { tag: string[] }) {
|
export function AppHandler({ tag }: { tag: string[] }) {
|
||||||
const ark = useArk();
|
const ark = useArk();
|
||||||
|
|||||||
@@ -1,19 +1,7 @@
|
|||||||
import { COL_TYPES } from "@lume/utils";
|
|
||||||
import { useColumnContext } from "../../column/provider";
|
|
||||||
|
|
||||||
export function Hashtag({ tag }: { tag: string }) {
|
export function Hashtag({ tag }: { tag: string }) {
|
||||||
const { addColumn } = useColumnContext();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
type="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"
|
className="text-blue-500 break-all cursor-default hover:text-blue-600"
|
||||||
>
|
>
|
||||||
{tag}
|
{tag}
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
import { PinIcon } from "@lume/icons";
|
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 { ReactNode, useMemo } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import reactStringReplace from "react-string-replace";
|
import reactStringReplace from "react-string-replace";
|
||||||
import { useEvent } from "../../../hooks/useEvent";
|
import { useEvent } from "../../../hooks/useEvent";
|
||||||
import { useColumnContext } from "../../column/provider";
|
|
||||||
import { User } from "../../user";
|
import { User } from "../../user";
|
||||||
import { Hashtag } from "./hashtag";
|
import { Hashtag } from "./hashtag";
|
||||||
import { MentionUser } from "./user";
|
import { MentionUser } from "./user";
|
||||||
@@ -15,7 +14,6 @@ export function MentionNote({
|
|||||||
openable = true,
|
openable = true,
|
||||||
}: { eventId: string; openable?: boolean }) {
|
}: { eventId: string; openable?: boolean }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { addColumn } = useColumnContext();
|
|
||||||
const { isLoading, isError, data } = useEvent(eventId);
|
const { isLoading, isError, data } = useEvent(eventId);
|
||||||
|
|
||||||
const richContent = useMemo(() => {
|
const richContent = useMemo(() => {
|
||||||
@@ -133,13 +131,6 @@ export function MentionNote({
|
|||||||
</Link>
|
</Link>
|
||||||
<button
|
<button
|
||||||
type="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"
|
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" />
|
<PinIcon className="size-4" />
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import { HorizontalDotsIcon } from "@lume/icons";
|
import { HorizontalDotsIcon } from "@lume/icons";
|
||||||
import { COL_TYPES } from "@lume/utils";
|
|
||||||
import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
|
import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
|
||||||
import { writeText } from "@tauri-apps/plugin-clipboard-manager";
|
import { writeText } from "@tauri-apps/plugin-clipboard-manager";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Link, useNavigate } from "react-router-dom";
|
import { Link, useNavigate } from "react-router-dom";
|
||||||
import { useArk } from "../../hooks/useArk";
|
import { useArk } from "../../provider";
|
||||||
import { useNoteContext } from "./provider";
|
import { useNoteContext } from "./provider";
|
||||||
|
|
||||||
export function NoteMenu() {
|
export function NoteMenu() {
|
||||||
@@ -19,7 +18,7 @@ export function NoteMenu() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const copyRaw = async () => {
|
const copyRaw = async () => {
|
||||||
await writeText(JSON.stringify(await event.toNostrEvent()));
|
await writeText(JSON.stringify(event));
|
||||||
};
|
};
|
||||||
|
|
||||||
const copyNpub = async () => {
|
const copyNpub = async () => {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useArk } from "../../hooks/useArk";
|
import { useArk } from "../../provider";
|
||||||
import { AppHandler } from "./appHandler";
|
import { AppHandler } from "./appHandler";
|
||||||
import { useNoteContext } from "./provider";
|
import { useNoteContext } from "./provider";
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { CheckCircleIcon, DownloadIcon } from "@lume/icons";
|
import { CheckCircleIcon, DownloadIcon } from "@lume/icons";
|
||||||
import { downloadDir } from "@tauri-apps/api/path";
|
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 { download } from "@tauri-apps/plugin-upload";
|
||||||
import { SyntheticEvent, useState } from "react";
|
import { SyntheticEvent, useState } from "react";
|
||||||
|
|
||||||
@@ -23,7 +23,7 @@ export function ImagePreview({ url }: { url: string }) {
|
|||||||
|
|
||||||
const open = async () => {
|
const open = async () => {
|
||||||
const name = new URL(url).pathname.split("/").pop();
|
const name = new URL(url).pathname.split("/").pop();
|
||||||
return new Window("image-viewer", {
|
return new WebviewWindow("image-viewer", {
|
||||||
url,
|
url,
|
||||||
title: name,
|
title: name,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { NavArrowDownIcon } from "@lume/icons";
|
import { NavArrowDownIcon } from "@lume/icons";
|
||||||
import { NDKEventWithReplies } from "@lume/types";
|
import { EventWithReplies } from "@lume/types";
|
||||||
import { cn } from "@lume/utils";
|
import { cn } from "@lume/utils";
|
||||||
import * as Collapsible from "@radix-ui/react-collapsible";
|
import * as Collapsible from "@radix-ui/react-collapsible";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
@@ -10,7 +10,7 @@ import { ChildReply } from "./childReply";
|
|||||||
export function Reply({
|
export function Reply({
|
||||||
event,
|
event,
|
||||||
}: {
|
}: {
|
||||||
event: NDKEventWithReplies;
|
event: EventWithReplies;
|
||||||
}) {
|
}) {
|
||||||
const [t] = useTranslation();
|
const [t] = useTranslation();
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { cn } from "@lume/utils";
|
|||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Note } from "..";
|
import { Note } from "..";
|
||||||
import { useArk } from "../../../hooks/useArk";
|
import { useArk } from "../../../provider";
|
||||||
import { User } from "../../user";
|
import { User } from "../../user";
|
||||||
|
|
||||||
export function RepostNote({
|
export function RepostNote({
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import { PinIcon } from "@lume/icons";
|
import { PinIcon } from "@lume/icons";
|
||||||
import { COL_TYPES, cn } from "@lume/utils";
|
import { cn } from "@lume/utils";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { Note } from ".";
|
import { Note } from ".";
|
||||||
import { useArk } from "../../hooks/useArk";
|
import { useArk } from "../../provider";
|
||||||
import { useColumnContext } from "../column/provider";
|
|
||||||
import { useNoteContext } from "./provider";
|
import { useNoteContext } from "./provider";
|
||||||
|
|
||||||
export function NoteThread({
|
export function NoteThread({
|
||||||
|
|||||||
@@ -2,14 +2,11 @@ import { LoaderIcon } from "@lume/icons";
|
|||||||
import { cn } from "@lume/utils";
|
import { cn } from "@lume/utils";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useArk } from "../../hooks/useArk";
|
|
||||||
|
|
||||||
export function UserFollowButton({
|
export function UserFollowButton({
|
||||||
target,
|
target,
|
||||||
className,
|
className,
|
||||||
}: { target: string; className?: string }) {
|
}: { target: string; className?: string }) {
|
||||||
const ark = useArk();
|
|
||||||
|
|
||||||
const [t] = useTranslation();
|
const [t] = useTranslation();
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [followed, setFollowed] = useState(false);
|
const [followed, setFollowed] = useState(false);
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ export function UserNip05({ className }: { className?: string }) {
|
|||||||
|
|
||||||
const { isLoading, data: verified } = useQuery({
|
const { isLoading, data: verified } = useQuery({
|
||||||
queryKey: ["nip05", user?.profile.nip05],
|
queryKey: ["nip05", user?.profile.nip05],
|
||||||
queryFn: async ({ signal }: { signal: AbortSignal }) => {
|
queryFn: async () => {
|
||||||
if (!user) return false;
|
if (!user) return false;
|
||||||
if (!user.profile.nip05) return false;
|
if (!user.profile.nip05) return false;
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Metadata } from "@lume/types";
|
import { Metadata } from "@lume/types";
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
import { ReactNode, createContext, useContext } from "react";
|
import { ReactNode, createContext, useContext } from "react";
|
||||||
import { useArk } from "../../hooks/useArk";
|
|
||||||
|
|
||||||
const UserContext = createContext<{ pubkey: string; profile: Metadata }>(null);
|
const UserContext = createContext<{ pubkey: string; profile: Metadata }>(null);
|
||||||
|
|
||||||
@@ -10,13 +10,12 @@ export function UserProvider({
|
|||||||
children,
|
children,
|
||||||
embed,
|
embed,
|
||||||
}: { pubkey: string; children: ReactNode; embed?: string }) {
|
}: { pubkey: string; children: ReactNode; embed?: string }) {
|
||||||
const ark = useArk();
|
|
||||||
const { data: profile } = useQuery({
|
const { data: profile } = useQuery({
|
||||||
queryKey: ["user", pubkey],
|
queryKey: ["user", pubkey],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
if (embed) return JSON.parse(embed) as Metadata;
|
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)
|
if (!profile)
|
||||||
throw new Error(
|
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 { useQuery } from "@tanstack/react-query";
|
||||||
import { useArk } from "./useArk";
|
import { useArk } from "../provider";
|
||||||
|
|
||||||
export function useEvent(id: string) {
|
export function useEvent(id: string) {
|
||||||
const ark = useArk();
|
const ark = useArk();
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { useArk } from "./useArk";
|
import { useArk } from "../provider";
|
||||||
|
|
||||||
export function useProfile(pubkey: string) {
|
export function useProfile(pubkey: string) {
|
||||||
const ark = useArk();
|
const ark = useArk();
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
import { useArk } from "./useArk";
|
import { useArk } from "../provider";
|
||||||
|
|
||||||
export function useRelaylist() {
|
export function useRelaylist() {
|
||||||
const ark = useArk();
|
const ark = useArk();
|
||||||
|
|||||||
@@ -1,12 +1,9 @@
|
|||||||
export * from "./ark";
|
|
||||||
export * from "./provider";
|
export * from "./provider";
|
||||||
export * from "./hooks/useEvent";
|
export * from "./hooks/useEvent";
|
||||||
export * from "./hooks/useArk";
|
|
||||||
export * from "./hooks/useProfile";
|
export * from "./hooks/useProfile";
|
||||||
export * from "./hooks/useRelayList";
|
export * from "./hooks/useRelayList";
|
||||||
export * from "./components/user";
|
export * from "./components/user";
|
||||||
export * from "./components/column";
|
export * from "./components/column";
|
||||||
export * from "./components/column/provider";
|
|
||||||
export * from "./components/note";
|
export * from "./components/note";
|
||||||
export * from "./components/note/primitives/text";
|
export * from "./components/note/primitives/text";
|
||||||
export * from "./components/note/primitives/repost";
|
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";
|
import { Ark } from "./ark";
|
||||||
|
|
||||||
export const ArkContext = createContext<Ark>(undefined);
|
export const ArkContext = createContext<Ark>(undefined);
|
||||||
@@ -7,3 +7,11 @@ export const ArkProvider = ({ children }: PropsWithChildren<object>) => {
|
|||||||
const ark = useMemo(() => new Ark(), []);
|
const ark = useMemo(() => new Ark(), []);
|
||||||
return <ArkContext.Provider value={ark}>{children}</ArkContext.Provider>;
|
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 { Column } from "@lume/ark";
|
||||||
import { IColumn } from "@lume/types";
|
import { LumeColumn } from "@lume/types";
|
||||||
import { EventRoute, UserRoute } from "@lume/ui";
|
import { EventRoute, UserRoute } from "@lume/ui";
|
||||||
import { AntenasForm } from "./components/form";
|
import { AntenasForm } from "./components/form";
|
||||||
import { HomeRoute } from "./home";
|
import { HomeRoute } from "./home";
|
||||||
|
|
||||||
export function Antenas({ column }: { column: IColumn }) {
|
export function Antenas({ column }: { column: LumeColumn }) {
|
||||||
const colKey = `antenas-${column.id}`;
|
const colKey = `antenas-${column.id}`;
|
||||||
const created = !!column.content?.length;
|
const created = !!column.content?.length;
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { Column, useColumnContext } from "@lume/ark";
|
import { Column, useColumnContext } from "@lume/ark";
|
||||||
import { IColumn } from "@lume/types";
|
import { LumeColumn } from "@lume/types";
|
||||||
import { COL_TYPES } from "@lume/utils";
|
import { COL_TYPES } from "@lume/utils";
|
||||||
|
|
||||||
export function Default({ column }: { column: IColumn }) {
|
export function Default({ column }: { column: LumeColumn }) {
|
||||||
const { addColumn } = useColumnContext();
|
const { addColumn } = useColumnContext();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import { Column } from "@lume/ark";
|
import { Column } from "@lume/ark";
|
||||||
import { useStorage } from "@lume/storage";
|
import { useStorage } from "@lume/storage";
|
||||||
import { IColumn } from "@lume/types";
|
import { LumeColumn } from "@lume/types";
|
||||||
import { EventRoute, UserRoute } from "@lume/ui";
|
import { EventRoute, UserRoute } from "@lume/ui";
|
||||||
import { NDKEvent, NDKKind } from "@nostr-dev-kit/ndk";
|
import { NDKEvent, NDKKind } from "@nostr-dev-kit/ndk";
|
||||||
import { useQueryClient } from "@tanstack/react-query";
|
import { useQueryClient } from "@tanstack/react-query";
|
||||||
import { useRef } from "react";
|
import { useRef } from "react";
|
||||||
import { HomeRoute } from "./home";
|
import { HomeRoute } from "./home";
|
||||||
|
|
||||||
export function ForYou({ column }: { column: IColumn }) {
|
export function ForYou({ column }: { column: LumeColumn }) {
|
||||||
const colKey = `foryou-${column.id}`;
|
const colKey = `foryou-${column.id}`;
|
||||||
const storage = useStorage();
|
const storage = useStorage();
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { Column } from "@lume/ark";
|
import { Column } from "@lume/ark";
|
||||||
import { IColumn } from "@lume/types";
|
import { LumeColumn } from "@lume/types";
|
||||||
import { EventRoute, UserRoute } from "@lume/ui";
|
import { EventRoute, UserRoute } from "@lume/ui";
|
||||||
import { HomeRoute } from "./home";
|
import { HomeRoute } from "./home";
|
||||||
|
|
||||||
export function Global({ column }: { column: IColumn }) {
|
export function Global({ column }: { column: LumeColumn }) {
|
||||||
const colKey = `global-${column.id}`;
|
const colKey = `global-${column.id}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { Column } from "@lume/ark";
|
import { Column } from "@lume/ark";
|
||||||
import { IColumn } from "@lume/types";
|
import { LumeColumn } from "@lume/types";
|
||||||
import { EventRoute, UserRoute } from "@lume/ui";
|
import { EventRoute, UserRoute } from "@lume/ui";
|
||||||
import { GroupForm } from "./components/form";
|
import { GroupForm } from "./components/form";
|
||||||
import { HomeRoute } from "./home";
|
import { HomeRoute } from "./home";
|
||||||
|
|
||||||
export function Group({ column }: { column: IColumn }) {
|
export function Group({ column }: { column: LumeColumn }) {
|
||||||
const colKey = `group-${column.id}`;
|
const colKey = `group-${column.id}`;
|
||||||
const created = !!column.content?.length;
|
const created = !!column.content?.length;
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { Column } from "@lume/ark";
|
import { Column } from "@lume/ark";
|
||||||
import { IColumn } from "@lume/types";
|
import { LumeColumn } from "@lume/types";
|
||||||
import { EventRoute, UserRoute } from "@lume/ui";
|
import { EventRoute, UserRoute } from "@lume/ui";
|
||||||
import { HomeRoute } from "./home";
|
import { HomeRoute } from "./home";
|
||||||
|
|
||||||
export function Hashtag({ column }: { column: IColumn }) {
|
export function Hashtag({ column }: { column: LumeColumn }) {
|
||||||
const colKey = `hashtag-${column.id}`;
|
const colKey = `hashtag-${column.id}`;
|
||||||
const hashtag = column.content.replace("#", "");
|
const hashtag = column.content.replace("#", "");
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { Column } from "@lume/ark";
|
import { Column } from "@lume/ark";
|
||||||
import { IColumn } from "@lume/types";
|
import { LumeColumn } from "@lume/types";
|
||||||
import { HomeRoute } from "./home";
|
import { HomeRoute } from "./home";
|
||||||
import { EventRoute, UserRoute } from "@lume/ui";
|
import { EventRoute, UserRoute } from "@lume/ui";
|
||||||
|
|
||||||
export function Thread({ column }: { column: IColumn }) {
|
export function Thread({ column }: { column: LumeColumn }) {
|
||||||
return (
|
return (
|
||||||
<Column.Root>
|
<Column.Root>
|
||||||
<Column.Header id={column.id} title={column.title} />
|
<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 { Event, Kind } from "@lume/types";
|
||||||
import { EmptyFeed } from "@lume/ui";
|
import { EmptyFeed } from "@lume/ui";
|
||||||
import { FETCH_LIMIT } from "@lume/utils";
|
import { FETCH_LIMIT } from "@lume/utils";
|
||||||
import { NDKEvent, NDKKind } from "@nostr-dev-kit/ndk";
|
|
||||||
import { useInfiniteQuery } from "@tanstack/react-query";
|
import { useInfiniteQuery } from "@tanstack/react-query";
|
||||||
import { useEffect, useMemo, useRef } from "react";
|
import { useEffect, useMemo, useRef } from "react";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { CacheSnapshot, VList, VListHandle } from "virtua";
|
import { CacheSnapshot, VList, VListHandle } from "virtua";
|
||||||
|
|
||||||
export function HomeRoute({ colKey }: { colKey: string }) {
|
export function HomeRoute({ queryKey }: { queryKey: string }) {
|
||||||
const ark = useArk();
|
const ark = useArk();
|
||||||
const ref = useRef<VListHandle>();
|
const ref = useRef<VListHandle>();
|
||||||
const cacheKey = `${colKey}-vlist`;
|
const cacheKey = `${queryKey}-vlist`;
|
||||||
|
|
||||||
const [offset, cache] = useMemo(() => {
|
const [offset, cache] = useMemo(() => {
|
||||||
const serialized = sessionStorage.getItem(cacheKey);
|
const serialized = sessionStorage.getItem(cacheKey);
|
||||||
@@ -22,16 +21,14 @@ export function HomeRoute({ colKey }: { colKey: string }) {
|
|||||||
|
|
||||||
const { data, hasNextPage, isLoading, isFetchingNextPage, fetchNextPage } =
|
const { data, hasNextPage, isLoading, isFetchingNextPage, fetchNextPage } =
|
||||||
useInfiniteQuery({
|
useInfiniteQuery({
|
||||||
queryKey: [colKey],
|
queryKey: [queryKey],
|
||||||
initialPageParam: 0,
|
initialPageParam: 0,
|
||||||
queryFn: async ({
|
queryFn: async ({
|
||||||
signal,
|
|
||||||
pageParam,
|
pageParam,
|
||||||
}: {
|
}: {
|
||||||
signal: AbortSignal;
|
|
||||||
pageParam: number;
|
pageParam: number;
|
||||||
}) => {
|
}) => {
|
||||||
const events = await ark.get_text_events(FETCH_LIMIT);
|
const events = await ark.get_text_events(FETCH_LIMIT, pageParam);
|
||||||
return events;
|
return events;
|
||||||
},
|
},
|
||||||
getNextPageParam: (lastPage) => {
|
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 (
|
return (
|
||||||
<div className="w-full h-full">
|
<div className="w-full h-full">
|
||||||
<VList ref={ref} cache={cache} overscan={2} className="flex-1 px-3">
|
<VList ref={ref} cache={cache} overscan={2} className="flex-1 px-3">
|
||||||
|
|||||||
@@ -1,51 +1,25 @@
|
|||||||
import { Column } from "@lume/ark";
|
import { Column } from "@lume/ark";
|
||||||
import { IColumn } from "@lume/types";
|
import { LumeColumn } from "@lume/types";
|
||||||
import { EventRoute, SuggestRoute, UserRoute } from "@lume/ui";
|
import { EventRoute, SuggestRoute, UserRoute } from "@lume/ui";
|
||||||
import { HomeRoute } from "./home";
|
import { HomeRoute } from "./home";
|
||||||
|
|
||||||
export function Timeline({ column }: { column: IColumn }) {
|
export function Timeline({ column }: { column: LumeColumn }) {
|
||||||
const colKey = `timeline-${column.id}`;
|
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 (
|
return (
|
||||||
<Column.Root>
|
<Column.Provider column={column}>
|
||||||
{/*<Column.Header id={column.id} queryKey={[colKey]} title="Timeline" />*/}
|
<Column.Root>
|
||||||
{/*ark.account.contacts.length ? (
|
<Column.Header queryKey={[colKey]} />
|
||||||
<Column.Live
|
<Column.Content>
|
||||||
filter={{
|
<Column.Route path="/" element={<HomeRoute queryKey={colKey} />} />
|
||||||
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.Route path="/events/:id" element={<EventRoute />} />
|
<Column.Route path="/events/:id" element={<EventRoute />} />
|
||||||
<Column.Route path="/users/:id" element={<UserRoute />} />
|
<Column.Route path="/users/:id" element={<UserRoute />} />
|
||||||
<Column.Route
|
<Column.Route
|
||||||
path="/suggest"
|
path="/suggest"
|
||||||
element={<SuggestRoute queryKey={[colKey]} />}
|
element={<SuggestRoute queryKey={colKey} />}
|
||||||
/>
|
/>
|
||||||
*/}
|
</Column.Content>
|
||||||
</Column.Content>
|
</Column.Root>
|
||||||
</Column.Root>
|
</Column.Provider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { Column } from "@lume/ark";
|
import { Column } from "@lume/ark";
|
||||||
import { IColumn } from "@lume/types";
|
import { LumeColumn } from "@lume/types";
|
||||||
import { EventRoute, UserRoute } from "@lume/ui";
|
import { EventRoute, UserRoute } from "@lume/ui";
|
||||||
import { HomeRoute } from "./home";
|
import { HomeRoute } from "./home";
|
||||||
|
|
||||||
export function TrendingNotes({ column }: { column: IColumn }) {
|
export function TrendingNotes({ column }: { column: LumeColumn }) {
|
||||||
const colKey = `trending-notes-${column.id}`;
|
const colKey = `trending-notes-${column.id}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { Column } from "@lume/ark";
|
import { Column } from "@lume/ark";
|
||||||
import { IColumn } from "@lume/types";
|
import { LumeColumn } from "@lume/types";
|
||||||
import { EventRoute, UserRoute } from "@lume/ui";
|
import { EventRoute, UserRoute } from "@lume/ui";
|
||||||
import { HomeRoute } from "./home";
|
import { HomeRoute } from "./home";
|
||||||
|
|
||||||
export function User({ column }: { column: IColumn }) {
|
export function User({ column }: { column: LumeColumn }) {
|
||||||
return (
|
return (
|
||||||
<Column.Root>
|
<Column.Root>
|
||||||
<Column.Header id={column.id} title={column.title} />
|
<Column.Header id={column.id} title={column.title} />
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { Column } from "@lume/ark";
|
import { Column } from "@lume/ark";
|
||||||
import { IColumn } from "@lume/types";
|
import { LumeColumn } from "@lume/types";
|
||||||
import { HomeRoute } from "./home";
|
import { HomeRoute } from "./home";
|
||||||
|
|
||||||
export function Waifu({ column }: { column: IColumn }) {
|
export function Waifu({ column }: { column: LumeColumn }) {
|
||||||
const colKey = `waifu-${column.id}`;
|
const colKey = `waifu-${column.id}`;
|
||||||
|
|
||||||
return (
|
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": {
|
"dependencies": {
|
||||||
"@tauri-apps/plugin-store": "2.0.0-beta.0",
|
"@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": {
|
"devDependencies": {
|
||||||
"@lume/tsconfig": "workspace:*",
|
"@lume/tsconfig": "workspace:*",
|
||||||
|
|||||||
@@ -1,2 +1 @@
|
|||||||
export * from "./storage";
|
|
||||||
export * from "./provider";
|
export * from "./provider";
|
||||||
|
|||||||
@@ -1,26 +1,118 @@
|
|||||||
|
import { LumeColumn } from "@lume/types";
|
||||||
import { locale, platform } from "@tauri-apps/plugin-os";
|
import { locale, platform } from "@tauri-apps/plugin-os";
|
||||||
import { Store } from "@tauri-apps/plugin-store";
|
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";
|
import { LumeStorage } from "./storage";
|
||||||
|
|
||||||
const StorageContext = createContext<LumeStorage>(null);
|
|
||||||
|
|
||||||
const store = new Store("lume.data");
|
|
||||||
const platformName = await platform();
|
const platformName = await platform();
|
||||||
const osLocale = await locale();
|
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>) => {
|
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 (
|
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 = () => {
|
export const useStorage = () => {
|
||||||
const context = useContext(StorageContext);
|
const context = useContextSelector(StorageContext, (state) => state.storage);
|
||||||
if (context === undefined) {
|
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;
|
return context;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { Settings } from "@lume/types";
|
||||||
import { Platform } from "@tauri-apps/plugin-os";
|
import { Platform } from "@tauri-apps/plugin-os";
|
||||||
import { Store } from "@tauri-apps/plugin-store";
|
import { Store } from "@tauri-apps/plugin-store";
|
||||||
|
|
||||||
@@ -5,17 +6,7 @@ export class LumeStorage {
|
|||||||
#store: Store;
|
#store: Store;
|
||||||
readonly platform: Platform;
|
readonly platform: Platform;
|
||||||
readonly locale: string;
|
readonly locale: string;
|
||||||
public settings: {
|
public settings: Settings;
|
||||||
autoupdate: boolean;
|
|
||||||
nsecbunker: boolean;
|
|
||||||
media: boolean;
|
|
||||||
hashtag: boolean;
|
|
||||||
lowPower: boolean;
|
|
||||||
translation: boolean;
|
|
||||||
translateApiKey: string;
|
|
||||||
instantZap: boolean;
|
|
||||||
defaultZapAmount: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
constructor(store: Store, platform: Platform, locale: string) {
|
constructor(store: Store, platform: Platform, locale: string) {
|
||||||
this.#store = store;
|
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;
|
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 {
|
export interface Keys {
|
||||||
npub: string;
|
npub: string;
|
||||||
@@ -28,16 +38,20 @@ export interface Event {
|
|||||||
sig: string;
|
sig: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface EventWithReplies extends Event {
|
||||||
|
replies: Array<Event>;
|
||||||
|
}
|
||||||
|
|
||||||
export interface Metadata {
|
export interface Metadata {
|
||||||
name: Option<string>;
|
name?: string;
|
||||||
display_name: Option<string>;
|
display_name?: string;
|
||||||
about: Option<string>;
|
about?: string;
|
||||||
website: Option<string>;
|
website?: string;
|
||||||
picture: Option<string>;
|
picture?: string;
|
||||||
banner: Option<string>;
|
banner?: string;
|
||||||
nip05: Option<string>;
|
nip05?: string;
|
||||||
lud06: Option<string>;
|
lud06?: string;
|
||||||
lud16: Option<string>;
|
lud16?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CurrentAccount {
|
export interface CurrentAccount {
|
||||||
@@ -61,9 +75,8 @@ export interface RichContent {
|
|||||||
notes: string[];
|
notes: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IColumn {
|
export interface LumeColumn {
|
||||||
id?: number;
|
id: number;
|
||||||
kind: number;
|
|
||||||
title: string;
|
title: string;
|
||||||
content: string;
|
content: string;
|
||||||
}
|
}
|
||||||
@@ -75,10 +88,6 @@ export interface Opengraph {
|
|||||||
image?: string;
|
image?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface NDKEventWithReplies extends NDKEvent {
|
|
||||||
replies: Array<NDKEvent>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface NostrBuildResponse {
|
export interface NostrBuildResponse {
|
||||||
ok: boolean;
|
ok: boolean;
|
||||||
data?: {
|
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 {
|
export interface NIP11 {
|
||||||
name: string;
|
name: string;
|
||||||
description: string;
|
description: string;
|
||||||
|
|||||||
@@ -10,8 +10,5 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"typescript": "^5.3.3"
|
"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 { Outlet } from "react-router-dom";
|
||||||
import { Editor } from "../editor/column";
|
import { Editor } from "../editor/column";
|
||||||
import { Navigation } from "../navigation";
|
import { Navigation } from "../navigation";
|
||||||
import { SearchDialog } from "../search/dialog";
|
import { SearchDialog } from "../search/dialog";
|
||||||
|
|
||||||
export function AppLayout() {
|
export function AppLayout() {
|
||||||
const storage = useStorage();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<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">
|
||||||
className={cn(
|
|
||||||
"flex h-screen w-screen flex-col",
|
|
||||||
storage.platform !== "macos" ? "bg-neutral-50 dark:bg-neutral-950" : "",
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<div data-tauri-drag-region className="h-9 shrink-0" />
|
<div data-tauri-drag-region className="h-9 shrink-0" />
|
||||||
<div className="flex w-full h-full min-h-0">
|
<div className="flex w-full h-full min-h-0">
|
||||||
<Navigation />
|
<Navigation />
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ const LUME_USERS = [
|
|||||||
"npub1zfss807aer0j26mwp2la0ume0jqde3823rmu97ra6sgyyg956e0s6xw445",
|
"npub1zfss807aer0j26mwp2la0ume0jqde3823rmu97ra6sgyyg956e0s6xw445",
|
||||||
];
|
];
|
||||||
|
|
||||||
export function SuggestRoute({ queryKey }: { queryKey: string[] }) {
|
export function SuggestRoute({ queryKey }: { queryKey: string }) {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
@@ -45,7 +45,7 @@ export function SuggestRoute({ queryKey }: { queryKey: string[] }) {
|
|||||||
|
|
||||||
const submit = async () => {
|
const submit = async () => {
|
||||||
try {
|
try {
|
||||||
await queryClient.refetchQueries({ queryKey });
|
await queryClient.refetchQueries({ queryKey: [queryKey] });
|
||||||
return navigate("/", { replace: true });
|
return navigate("/", { replace: true });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast.error(String(e));
|
toast.error(String(e));
|
||||||
|
|||||||
61
pnpm-lock.yaml
generated
61
pnpm-lock.yaml
generated
@@ -1061,6 +1061,18 @@ importers:
|
|||||||
react:
|
react:
|
||||||
specifier: ^18.2.0
|
specifier: ^18.2.0
|
||||||
version: 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:
|
devDependencies:
|
||||||
'@lume/tsconfig':
|
'@lume/tsconfig':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
@@ -1100,10 +1112,6 @@ importers:
|
|||||||
packages/tsconfig: {}
|
packages/tsconfig: {}
|
||||||
|
|
||||||
packages/types:
|
packages/types:
|
||||||
dependencies:
|
|
||||||
'@nostr-dev-kit/ndk':
|
|
||||||
specifier: ^2.4.0
|
|
||||||
version: 2.4.0(typescript@5.3.3)
|
|
||||||
devDependencies:
|
devDependencies:
|
||||||
typescript:
|
typescript:
|
||||||
specifier: ^5.3.3
|
specifier: ^5.3.3
|
||||||
@@ -9812,6 +9820,23 @@ packages:
|
|||||||
tslib: 2.6.2
|
tslib: 2.6.2
|
||||||
dev: false
|
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):
|
/use-debounce@10.0.0(react@18.2.0):
|
||||||
resolution: {integrity: sha512-XRjvlvCB46bah9IBXVnq/ACP2lxqXyZj0D9hj4K5OzNroMDpTEBg8Anuh1/UfRTRs7pLhQ+RiNxxwZu9+MVl1A==}
|
resolution: {integrity: sha512-XRjvlvCB46bah9IBXVnq/ACP2lxqXyZj0D9hj4K5OzNroMDpTEBg8Anuh1/UfRTRs7pLhQ+RiNxxwZu9+MVl1A==}
|
||||||
engines: {node: '>= 16.0.0'}
|
engines: {node: '>= 16.0.0'}
|
||||||
@@ -9837,6 +9862,14 @@ packages:
|
|||||||
tslib: 2.6.2
|
tslib: 2.6.2
|
||||||
dev: false
|
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:
|
/utf-8-validate@5.0.10:
|
||||||
resolution: {integrity: sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==}
|
resolution: {integrity: sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==}
|
||||||
engines: {node: '>=6.14.2'}
|
engines: {node: '>=6.14.2'}
|
||||||
@@ -10395,5 +10428,25 @@ packages:
|
|||||||
resolution: {integrity: sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==}
|
resolution: {integrity: sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==}
|
||||||
dev: false
|
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:
|
/zwitch@2.0.4:
|
||||||
resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}
|
resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}
|
||||||
|
|||||||
@@ -19,6 +19,9 @@
|
|||||||
"notification:default",
|
"notification:default",
|
||||||
"os:allow-locale",
|
"os:allow-locale",
|
||||||
"os:allow-platform",
|
"os:allow-platform",
|
||||||
|
"updater:allow-check",
|
||||||
|
"updater:default",
|
||||||
|
"window:allow-start-dragging",
|
||||||
{
|
{
|
||||||
"identifier": "http:default",
|
"identifier": "http:default",
|
||||||
"allow": [
|
"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",
|
"$schema": "../node_modules/@tauri-apps/cli/schema.json",
|
||||||
"app": {
|
"app": {
|
||||||
"windows": [
|
"windows": [
|
||||||
{
|
{
|
||||||
"width": 1080,
|
"title": "Lume",
|
||||||
"height": 800,
|
"label": "main",
|
||||||
"minWidth": 1080,
|
"titleBarStyle": "Overlay",
|
||||||
"minHeight": 800,
|
"width": 1080,
|
||||||
"resizable": true,
|
"height": 800,
|
||||||
"title": "Lume",
|
"minWidth": 1080,
|
||||||
"center": true,
|
"minHeight": 800,
|
||||||
"fullscreen": false,
|
"center": true
|
||||||
"fileDropEnabled": true,
|
}
|
||||||
"decorations": true
|
]
|
||||||
}
|
}
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,27 +1,19 @@
|
|||||||
{
|
{
|
||||||
"$schema": "../node_modules/@tauri-apps/cli/schema.json",
|
"$schema": "../node_modules/@tauri-apps/cli/schema.json",
|
||||||
"app": {
|
"app": {
|
||||||
"windows": [
|
"windows": [
|
||||||
{
|
{
|
||||||
"width": 1080,
|
"title": "Lume",
|
||||||
"height": 800,
|
"label": "main",
|
||||||
"minWidth": 1080,
|
"titleBarStyle": "Overlay",
|
||||||
"minHeight": 800,
|
"width": 1080,
|
||||||
"resizable": true,
|
"height": 800,
|
||||||
"title": "Lume",
|
"minWidth": 1080,
|
||||||
"titleBarStyle": "Overlay",
|
"minHeight": 800,
|
||||||
"center": true,
|
"center": true,
|
||||||
"fullscreen": false,
|
"hiddenTitle": true,
|
||||||
"hiddenTitle": true,
|
"decorations": true
|
||||||
"fileDropEnabled": true,
|
}
|
||||||
"decorations": true,
|
]
|
||||||
"transparent": true,
|
}
|
||||||
"windowEffects": {
|
|
||||||
"effects": [
|
|
||||||
"sidebar"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,16 @@
|
|||||||
{
|
{
|
||||||
"$schema": "../node_modules/@tauri-apps/cli/schema.json",
|
"$schema": "../node_modules/@tauri-apps/cli/schema.json",
|
||||||
"app": {
|
"app": {
|
||||||
"windows": [
|
"windows": [
|
||||||
{
|
{
|
||||||
"width": 1080,
|
"title": "Lume",
|
||||||
"height": 800,
|
"label": "main",
|
||||||
"minWidth": 1080,
|
"width": 1080,
|
||||||
"minHeight": 800,
|
"height": 800,
|
||||||
"resizable": true,
|
"minWidth": 1080,
|
||||||
"title": "Lume",
|
"minHeight": 800,
|
||||||
"center": true,
|
"center": true
|
||||||
"fullscreen": false,
|
}
|
||||||
"hiddenTitle": true,
|
]
|
||||||
"fileDropEnabled": true,
|
}
|
||||||
"decorations": false
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user