diff --git a/package.json b/package.json
index 7a0ea3e9..933b4147 100644
--- a/package.json
+++ b/package.json
@@ -18,6 +18,7 @@
"**/*.{ts, tsx, css, md, html, json}": "prettier --cache --write"
},
"dependencies": {
+ "@dnd-kit/core": "^6.0.8",
"@getalby/sdk": "^2.4.0",
"@nostr-dev-kit/ndk": "^1.2.1",
"@nostr-fetch/adapter-ndk": "^0.12.2",
@@ -47,6 +48,7 @@
"nostr-fetch": "^0.13.0",
"nostr-tools": "^1.15.0",
"qrcode.react": "^3.1.0",
+ "re-resizable": "^6.9.11",
"react": "^18.2.0",
"react-currency-input-field": "^3.6.11",
"react-dom": "^18.2.0",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index fc164463..817f014b 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -5,6 +5,9 @@ settings:
excludeLinksFromLockfile: false
dependencies:
+ '@dnd-kit/core':
+ specifier: ^6.0.8
+ version: 6.0.8(react-dom@18.2.0)(react@18.2.0)
'@getalby/sdk':
specifier: ^2.4.0
version: 2.4.0
@@ -92,6 +95,9 @@ dependencies:
qrcode.react:
specifier: ^3.1.0
version: 3.1.0(react@18.2.0)
+ re-resizable:
+ specifier: ^6.9.11
+ version: 6.9.11(react-dom@18.2.0)(react@18.2.0)
react:
specifier: ^18.2.0
version: 18.2.0
@@ -124,7 +130,7 @@ dependencies:
version: 3.0.1
tauri-plugin-sql-api:
specifier: github:tauri-apps/tauri-plugin-sql#v1
- version: github.com/tauri-apps/tauri-plugin-sql/b8fd19dac907cc8c3d78681cd4803a326b8b861e
+ version: github.com/tauri-apps/tauri-plugin-sql/26467343db277e79daf3a216372ba25e1fec0deb
tauri-plugin-store-api:
specifier: github:tauri-apps/tauri-plugin-store#v1
version: github.com/tauri-apps/tauri-plugin-store/a65ce9bfb168a9a3cd7ed4102b9f22770cc3abfa
@@ -369,6 +375,37 @@ packages:
to-fast-properties: 2.0.0
dev: true
+ /@dnd-kit/accessibility@3.0.1(react@18.2.0):
+ resolution: {integrity: sha512-HXRrwS9YUYQO9lFRc/49uO/VICbM+O+ZRpFDe9Pd1rwVv2PCNkRiTZRdxrDgng/UkvdC3Re9r2vwPpXXrWeFzg==}
+ peerDependencies:
+ react: '>=16.8.0'
+ dependencies:
+ react: 18.2.0
+ tslib: 2.6.2
+ dev: false
+
+ /@dnd-kit/core@6.0.8(react-dom@18.2.0)(react@18.2.0):
+ resolution: {integrity: sha512-lYaoP8yHTQSLlZe6Rr9qogouGUz9oRUj4AHhDQGQzq/hqaJRpFo65X+JKsdHf8oUFBzx5A+SJPUvxAwTF2OabA==}
+ peerDependencies:
+ react: '>=16.8.0'
+ react-dom: '>=16.8.0'
+ dependencies:
+ '@dnd-kit/accessibility': 3.0.1(react@18.2.0)
+ '@dnd-kit/utilities': 3.2.1(react@18.2.0)
+ react: 18.2.0
+ react-dom: 18.2.0(react@18.2.0)
+ tslib: 2.6.2
+ dev: false
+
+ /@dnd-kit/utilities@3.2.1(react@18.2.0):
+ resolution: {integrity: sha512-OOXqISfvBw/1REtkSK2N3Fi2EQiLMlWUlqnOK/UpOISqBZPWpE6TqL+jcPtMOkE8TqYGiURvRdPSI9hltNUjEA==}
+ peerDependencies:
+ react: '>=16.8.0'
+ dependencies:
+ react: 18.2.0
+ tslib: 2.6.2
+ dev: false
+
/@esbuild/android-arm64@0.18.20:
resolution: {integrity: sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==}
engines: {node: '>=12'}
@@ -5134,6 +5171,16 @@ packages:
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
dev: true
+ /re-resizable@6.9.11(react-dom@18.2.0)(react@18.2.0):
+ resolution: {integrity: sha512-a3hiLWck/NkmyLvGWUuvkAmN1VhwAz4yOhS6FdMTaxCUVN9joIWkT11wsO68coG/iEYuwn+p/7qAmfQzRhiPLQ==}
+ peerDependencies:
+ react: ^16.13.1 || ^17.0.0 || ^18.0.0
+ react-dom: ^16.13.1 || ^17.0.0 || ^18.0.0
+ dependencies:
+ react: 18.2.0
+ react-dom: 18.2.0(react@18.2.0)
+ dev: false
+
/react-currency-input-field@3.6.11(react@18.2.0):
resolution: {integrity: sha512-M9vOx1eaioSaYWirm7W2WSBi4bpLg+LK4Gf7C1kNhy6MvoSoOzd0mYZPxA78OC9UBIQ2nM080Wu9D1CwTY6n3w==}
peerDependencies:
@@ -6314,8 +6361,8 @@ packages:
resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}
dev: false
- github.com/tauri-apps/tauri-plugin-sql/b8fd19dac907cc8c3d78681cd4803a326b8b861e:
- resolution: {tarball: https://codeload.github.com/tauri-apps/tauri-plugin-sql/tar.gz/b8fd19dac907cc8c3d78681cd4803a326b8b861e}
+ github.com/tauri-apps/tauri-plugin-sql/26467343db277e79daf3a216372ba25e1fec0deb:
+ resolution: {tarball: https://codeload.github.com/tauri-apps/tauri-plugin-sql/tar.gz/26467343db277e79daf3a216372ba25e1fec0deb}
name: tauri-plugin-sql-api
version: 0.0.0
dependencies:
diff --git a/src/shared/widgets/global/articles.tsx b/src/shared/widgets/global/articles.tsx
index c12f530e..fa19b01f 100644
--- a/src/shared/widgets/global/articles.tsx
+++ b/src/shared/widgets/global/articles.tsx
@@ -7,6 +7,7 @@ import { useNDK } from '@libs/ndk/provider';
import { ArticleNote, NoteSkeleton, NoteWrapper } from '@shared/notes';
import { TitleBar } from '@shared/titleBar';
+import { WidgetWrapper } from '@shared/widgets';
import { Widget } from '@utils/types';
@@ -51,7 +52,7 @@ export function GlobalArticlesWidget({ params }: { params: Widget }) {
);
return (
-
+
{status === 'loading' ? (
@@ -89,6 +90,6 @@ export function GlobalArticlesWidget({ params }: { params: Widget }) {
)}
-
+
);
}
diff --git a/src/shared/widgets/global/files.tsx b/src/shared/widgets/global/files.tsx
index d82857b7..56011e19 100644
--- a/src/shared/widgets/global/files.tsx
+++ b/src/shared/widgets/global/files.tsx
@@ -7,6 +7,7 @@ import { useNDK } from '@libs/ndk/provider';
import { FileNote, NoteSkeleton, NoteWrapper } from '@shared/notes';
import { TitleBar } from '@shared/titleBar';
+import { WidgetWrapper } from '@shared/widgets';
import { Widget } from '@utils/types';
@@ -52,7 +53,7 @@ export function GlobalFilesWidget({ params }: { params: Widget }) {
);
return (
-
+
{status === 'loading' ? (
@@ -90,6 +91,6 @@ export function GlobalFilesWidget({ params }: { params: Widget }) {
)}
-
+
);
}
diff --git a/src/shared/widgets/global/hashtag.tsx b/src/shared/widgets/global/hashtag.tsx
index 7833c377..374b87c0 100644
--- a/src/shared/widgets/global/hashtag.tsx
+++ b/src/shared/widgets/global/hashtag.tsx
@@ -15,6 +15,7 @@ import {
UnknownNote,
} from '@shared/notes';
import { TitleBar } from '@shared/titleBar';
+import { WidgetWrapper } from '@shared/widgets';
import { nHoursAgo } from '@utils/date';
import { Widget } from '@utils/types';
@@ -114,7 +115,7 @@ export function GlobalHashtagWidget({ params }: { params: Widget }) {
);
return (
-
+
{status === 'loading' ? (
@@ -152,6 +153,6 @@ export function GlobalHashtagWidget({ params }: { params: Widget }) {
)}
-
+
);
}
diff --git a/src/shared/widgets/index.ts b/src/shared/widgets/index.ts
index 7b29f9d0..fd0dcdbf 100644
--- a/src/shared/widgets/index.ts
+++ b/src/shared/widgets/index.ts
@@ -1,3 +1,4 @@
+export * from './wrapper';
export * from './local/feeds';
export * from './local/network';
export * from './local/user';
diff --git a/src/shared/widgets/local/articles.tsx b/src/shared/widgets/local/articles.tsx
index 11fecff0..fd49fc1e 100644
--- a/src/shared/widgets/local/articles.tsx
+++ b/src/shared/widgets/local/articles.tsx
@@ -8,6 +8,7 @@ import { useStorage } from '@libs/storage/provider';
import { ArrowRightCircleIcon, LoaderIcon } from '@shared/icons';
import { FileNote, NoteSkeleton, NoteWrapper } from '@shared/notes';
import { TitleBar } from '@shared/titleBar';
+import { WidgetWrapper } from '@shared/widgets';
import { DBEvent, Widget } from '@utils/types';
@@ -53,7 +54,7 @@ export function LocalArticlesWidget({ params }: { params: Widget }) {
);
return (
-
+
{status === 'loading' ? (
@@ -125,6 +126,6 @@ export function LocalArticlesWidget({ params }: { params: Widget }) {
-
+
);
}
diff --git a/src/shared/widgets/local/feeds.tsx b/src/shared/widgets/local/feeds.tsx
index ac1dc890..23a4bf9a 100644
--- a/src/shared/widgets/local/feeds.tsx
+++ b/src/shared/widgets/local/feeds.tsx
@@ -16,6 +16,7 @@ import {
} from '@shared/notes';
import { NoteSkeleton } from '@shared/notes/skeleton';
import { TitleBar } from '@shared/titleBar';
+import { WidgetWrapper } from '@shared/widgets';
import { DBEvent, Widget } from '@utils/types';
@@ -116,7 +117,7 @@ export function LocalFeedsWidget({ params }: { params: Widget }) {
);
return (
-
+
{status === 'loading' ? (
@@ -188,6 +189,6 @@ export function LocalFeedsWidget({ params }: { params: Widget }) {
-
+
);
}
diff --git a/src/shared/widgets/local/files.tsx b/src/shared/widgets/local/files.tsx
index ec5f3ced..d8328cc0 100644
--- a/src/shared/widgets/local/files.tsx
+++ b/src/shared/widgets/local/files.tsx
@@ -8,6 +8,7 @@ import { useStorage } from '@libs/storage/provider';
import { ArrowRightCircleIcon, LoaderIcon } from '@shared/icons';
import { FileNote, NoteSkeleton, NoteWrapper } from '@shared/notes';
import { TitleBar } from '@shared/titleBar';
+import { WidgetWrapper } from '@shared/widgets';
import { DBEvent, Widget } from '@utils/types';
@@ -53,7 +54,7 @@ export function LocalFilesWidget({ params }: { params: Widget }) {
);
return (
-
+
{status === 'loading' ? (
@@ -125,6 +126,6 @@ export function LocalFilesWidget({ params }: { params: Widget }) {
-
+
);
}
diff --git a/src/shared/widgets/local/follows.tsx b/src/shared/widgets/local/follows.tsx
index 73810d4e..0d22b0f8 100644
--- a/src/shared/widgets/local/follows.tsx
+++ b/src/shared/widgets/local/follows.tsx
@@ -16,6 +16,7 @@ import {
} from '@shared/notes';
import { NoteSkeleton } from '@shared/notes/skeleton';
import { TitleBar } from '@shared/titleBar';
+import { WidgetWrapper } from '@shared/widgets';
import { DBEvent, Widget } from '@utils/types';
@@ -115,7 +116,7 @@ export function LocalFollowsWidget({ params }: { params: Widget }) {
);
return (
-
+
{status === 'loading' ? (
@@ -193,6 +194,6 @@ export function LocalFollowsWidget({ params }: { params: Widget }) {
) : null}
-
+
);
}
diff --git a/src/shared/widgets/local/network.tsx b/src/shared/widgets/local/network.tsx
index 68aa833e..fd177ad1 100644
--- a/src/shared/widgets/local/network.tsx
+++ b/src/shared/widgets/local/network.tsx
@@ -16,6 +16,7 @@ import {
} from '@shared/notes';
import { NoteSkeleton } from '@shared/notes/skeleton';
import { TitleBar } from '@shared/titleBar';
+import { WidgetWrapper } from '@shared/widgets';
import { useNostr } from '@utils/hooks/useNostr';
import { toRawEvent } from '@utils/rawEvent';
@@ -127,19 +128,15 @@ export function LocalNetworkWidget() {
since: db.account.last_login_at ?? Math.floor(Date.now() / 1000),
};
- sub(
- filter,
- async (event) => {
- const rawEvent = toRawEvent(event);
- await db.createEvent(rawEvent);
- },
- false // don't close sub on eose
- );
+ sub(filter, async (event) => {
+ const rawEvent = toRawEvent(event);
+ await db.createEvent(rawEvent);
+ });
}
}, []);
return (
-
+
{status === 'loading' ? (
@@ -217,6 +214,6 @@ export function LocalNetworkWidget() {
) : null}
-
+
);
}
diff --git a/src/shared/widgets/local/thread.tsx b/src/shared/widgets/local/thread.tsx
index 1d754610..8236e558 100644
--- a/src/shared/widgets/local/thread.tsx
+++ b/src/shared/widgets/local/thread.tsx
@@ -16,6 +16,7 @@ import { RepliesList } from '@shared/notes/replies/list';
import { NoteSkeleton } from '@shared/notes/skeleton';
import { TitleBar } from '@shared/titleBar';
import { User } from '@shared/user';
+import { WidgetWrapper } from '@shared/widgets';
import { useEvent } from '@utils/hooks/useEvent';
import { Widget } from '@utils/types';
@@ -41,7 +42,7 @@ export function LocalThreadWidget({ params }: { params: Widget }) {
);
return (
-
+
{status === 'loading' ? (
@@ -69,6 +70,6 @@ export function LocalThreadWidget({ params }: { params: Widget }) {
-
+
);
}
diff --git a/src/shared/widgets/local/user.tsx b/src/shared/widgets/local/user.tsx
index 4266d76c..fc920bc9 100644
--- a/src/shared/widgets/local/user.tsx
+++ b/src/shared/widgets/local/user.tsx
@@ -16,6 +16,7 @@ import {
} from '@shared/notes';
import { TitleBar } from '@shared/titleBar';
import { UserProfile } from '@shared/userProfile';
+import { WidgetWrapper } from '@shared/widgets';
import { nHoursAgo } from '@utils/date';
import { Widget } from '@utils/types';
@@ -120,7 +121,7 @@ export function LocalUserWidget({ params }: { params: Widget }) {
);
return (
-
+
@@ -166,6 +167,6 @@ export function LocalUserWidget({ params }: { params: Widget }) {
-
+
);
}
diff --git a/src/shared/widgets/nostrBand/trendingAccounts.tsx b/src/shared/widgets/nostrBand/trendingAccounts.tsx
index c8e3286d..6dfa8358 100644
--- a/src/shared/widgets/nostrBand/trendingAccounts.tsx
+++ b/src/shared/widgets/nostrBand/trendingAccounts.tsx
@@ -2,6 +2,7 @@ import { useQuery } from '@tanstack/react-query';
import { NoteSkeleton } from '@shared/notes/skeleton';
import { TitleBar } from '@shared/titleBar';
+import { WidgetWrapper } from '@shared/widgets';
import { NostrBandUserProfile, type Profile } from '@shared/widgets/nostrBandUserProfile';
import { Widget } from '@utils/types';
@@ -31,7 +32,7 @@ export function TrendingAccountsWidget({ params }: { params: Widget }) {
);
return (
-
+
{status === 'loading' ? (
@@ -56,6 +57,6 @@ export function TrendingAccountsWidget({ params }: { params: Widget }) {
)}
-
+
);
}
diff --git a/src/shared/widgets/nostrBand/trendingNotes.tsx b/src/shared/widgets/nostrBand/trendingNotes.tsx
index 321b2b88..5056e9e9 100644
--- a/src/shared/widgets/nostrBand/trendingNotes.tsx
+++ b/src/shared/widgets/nostrBand/trendingNotes.tsx
@@ -3,6 +3,7 @@ import { useQuery } from '@tanstack/react-query';
import { NoteSkeleton, NoteWrapper, TextNote } from '@shared/notes';
import { TitleBar } from '@shared/titleBar';
+import { WidgetWrapper } from '@shared/widgets';
import { Widget } from '@utils/types';
@@ -31,7 +32,7 @@ export function TrendingNotesWidget({ params }: { params: Widget }) {
);
return (
-
+
{status === 'loading' ? (
@@ -58,6 +59,6 @@ export function TrendingNotesWidget({ params }: { params: Widget }) {
)}
-
+
);
}
diff --git a/src/shared/widgets/other/learnNostr.tsx b/src/shared/widgets/other/learnNostr.tsx
index aee20b27..b48de3d6 100644
--- a/src/shared/widgets/other/learnNostr.tsx
+++ b/src/shared/widgets/other/learnNostr.tsx
@@ -2,6 +2,7 @@ import { useNavigate } from 'react-router-dom';
import { ArrowRightIcon } from '@shared/icons';
import { TitleBar } from '@shared/titleBar';
+import { WidgetWrapper } from '@shared/widgets';
import { useResources } from '@stores/resources';
@@ -21,7 +22,7 @@ export function LearnNostrWidget({ params }: { params: Widget }) {
};
return (
-
+
{resources.map((resource, index) => (
@@ -58,6 +59,6 @@ export function LearnNostrWidget({ params }: { params: Widget }) {
))}
-
+
);
}
diff --git a/src/shared/widgets/wrapper.tsx b/src/shared/widgets/wrapper.tsx
new file mode 100644
index 00000000..72e8587f
--- /dev/null
+++ b/src/shared/widgets/wrapper.tsx
@@ -0,0 +1,32 @@
+import { Resizable } from 're-resizable';
+import { ReactNode, useState } from 'react';
+import { twMerge } from 'tailwind-merge';
+
+export function WidgetWrapper({
+ children,
+ className,
+}: {
+ children: ReactNode;
+ className?: string;
+}) {
+ const [width, setWidth] = useState(400);
+
+ return (
+ e.preventDefault()}
+ onResizeStop={(e, direction, ref, d) => {
+ setWidth((prevWidth) => prevWidth + d.width);
+ }}
+ minWidth={400}
+ minHeight={'100vh'}
+ className={twMerge(
+ 'relative shrink-0 grow-0 basis-[400px] bg-white/10 backdrop-blur-xl',
+ className
+ )}
+ enable={{ right: true }}
+ >
+ {children}
+
+ );
+}
diff --git a/src/stores/widgets.ts b/src/stores/widgets.ts
index d1805eef..60f6745d 100644
--- a/src/stores/widgets.ts
+++ b/src/stores/widgets.ts
@@ -10,6 +10,7 @@ interface WidgetState {
fetchWidgets: (db: LumeStorage) => void;
setWidget: (db: LumeStorage, { kind, title, content }: Widget) => void;
removeWidget: (db: LumeStorage, id: string) => void;
+ reorderWidget: (id: string, position: number) => void;
}
export const WidgetKinds = {
@@ -141,6 +142,18 @@ export const useWidgets = create()(
await db.removeWidget(id);
set((state) => ({ widgets: state.widgets.filter((widget) => widget.id !== id) }));
},
+ reorderWidget: (id: string, position: number) =>
+ set((state) => {
+ const widgets = [...state.widgets];
+ const widget = widgets.find((widget) => widget.id === id);
+ if (!widget) return { widgets };
+
+ const idx = widgets.indexOf(widget);
+ widgets.splice(idx, 1);
+ widgets.splice(position, 0, widget);
+
+ return { widgets };
+ }),
}),
{
name: 'widgets',