resizable widget

This commit is contained in:
Ren Amamiya 2023-09-20 14:31:14 +07:00
parent 296136203a
commit 0e5adb246f
18 changed files with 141 additions and 37 deletions

View File

@ -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",

View File

@ -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:

View File

@ -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 (
<div className="relative shrink-0 grow-0 basis-[400px] bg-white/10 backdrop-blur-xl">
<WidgetWrapper>
<TitleBar id={params.id} title={params.title} />
<div ref={parentRef} className="scrollbar-hide h-full overflow-y-auto pb-20">
{status === 'loading' ? (
@ -89,6 +90,6 @@ export function GlobalArticlesWidget({ params }: { params: Widget }) {
</div>
)}
</div>
</div>
</WidgetWrapper>
);
}

View File

@ -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 (
<div className="relative shrink-0 grow-0 basis-[400px] bg-white/10 backdrop-blur-xl">
<WidgetWrapper>
<TitleBar id={params.id} title={params.title} />
<div ref={parentRef} className="scrollbar-hide h-full overflow-y-auto pb-20">
{status === 'loading' ? (
@ -90,6 +91,6 @@ export function GlobalFilesWidget({ params }: { params: Widget }) {
</div>
)}
</div>
</div>
</WidgetWrapper>
);
}

View File

@ -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 (
<div className="relative shrink-0 grow-0 basis-[400px] bg-white/10 backdrop-blur-xl">
<WidgetWrapper>
<TitleBar id={params.id} title={params.title + ' in 24 hours ago'} />
<div ref={parentRef} className="scrollbar-hide h-full overflow-y-auto pb-20">
{status === 'loading' ? (
@ -152,6 +153,6 @@ export function GlobalHashtagWidget({ params }: { params: Widget }) {
</div>
)}
</div>
</div>
</WidgetWrapper>
);
}

View File

@ -1,3 +1,4 @@
export * from './wrapper';
export * from './local/feeds';
export * from './local/network';
export * from './local/user';

View File

@ -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 (
<div className="relative shrink-0 grow-0 basis-[400px] bg-white/10 backdrop-blur-xl">
<WidgetWrapper>
<TitleBar id={params.id} title={params.title} />
<div ref={parentRef} className="scrollbar-hide h-full overflow-y-auto pb-20">
{status === 'loading' ? (
@ -125,6 +126,6 @@ export function LocalArticlesWidget({ params }: { params: Widget }) {
</button>
</div>
</div>
</div>
</WidgetWrapper>
);
}

View File

@ -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 (
<div className="relative shrink-0 grow-0 basis-[400px] bg-white/10 backdrop-blur-xl">
<WidgetWrapper>
<TitleBar id={params.id} title={params.title} />
<div ref={parentRef} className="scrollbar-hide h-full overflow-y-auto pb-20">
{status === 'loading' ? (
@ -188,6 +189,6 @@ export function LocalFeedsWidget({ params }: { params: Widget }) {
</button>
</div>
</div>
</div>
</WidgetWrapper>
);
}

View File

@ -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 (
<div className="relative shrink-0 grow-0 basis-[400px] bg-white/10 backdrop-blur-xl">
<WidgetWrapper>
<TitleBar id={params.id} title={params.title} />
<div ref={parentRef} className="scrollbar-hide h-full overflow-y-auto pb-20">
{status === 'loading' ? (
@ -125,6 +126,6 @@ export function LocalFilesWidget({ params }: { params: Widget }) {
</button>
</div>
</div>
</div>
</WidgetWrapper>
);
}

View File

@ -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 (
<div className="relative shrink-0 grow-0 basis-[400px] bg-white/10 backdrop-blur-xl">
<WidgetWrapper>
<TitleBar id={params.id} title="Follows" />
<div ref={parentRef} className="scrollbar-hide h-full overflow-y-auto pb-20">
{status === 'loading' ? (
@ -193,6 +194,6 @@ export function LocalFollowsWidget({ params }: { params: Widget }) {
) : null}
</div>
</div>
</div>
</WidgetWrapper>
);
}

View File

@ -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) => {
sub(filter, async (event) => {
const rawEvent = toRawEvent(event);
await db.createEvent(rawEvent);
},
false // don't close sub on eose
);
});
}
}, []);
return (
<div className="relative shrink-0 grow-0 basis-[400px] bg-white/10 backdrop-blur-xl">
<WidgetWrapper>
<TitleBar title="👋 Network" />
<div ref={parentRef} className="scrollbar-hide h-full overflow-y-auto pb-20">
{status === 'loading' ? (
@ -217,6 +214,6 @@ export function LocalNetworkWidget() {
) : null}
</div>
</div>
</div>
</WidgetWrapper>
);
}

View File

@ -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 (
<div className="scrollbar-hide relative shrink-0 grow-0 basis-[400px] overflow-y-auto bg-white/10 backdrop-blur-xl">
<WidgetWrapper>
<TitleBar id={params.id} title={params.title} />
<div className="h-full">
{status === 'loading' ? (
@ -69,6 +70,6 @@ export function LocalThreadWidget({ params }: { params: Widget }) {
<RepliesList id={params.content} />
</div>
</div>
</div>
</WidgetWrapper>
);
}

View File

@ -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 (
<div className="relative shrink-0 grow-0 basis-[400px] bg-white/10 backdrop-blur-xl">
<WidgetWrapper>
<TitleBar id={params.id} title={params.title} />
<div ref={parentRef} className="scrollbar-hide h-full overflow-y-auto pb-20">
<div className="px-3 pt-1.5">
@ -166,6 +167,6 @@ export function LocalUserWidget({ params }: { params: Widget }) {
</div>
</div>
</div>
</div>
</WidgetWrapper>
);
}

View File

@ -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 (
<div className="relative shrink-0 grow-0 basis-[400px] bg-white/10 backdrop-blur-xl">
<WidgetWrapper>
<TitleBar id={params.id} title="Trending Accounts" />
<div className="scrollbar-hide h-full max-w-full overflow-y-auto pb-20">
{status === 'loading' ? (
@ -56,6 +57,6 @@ export function TrendingAccountsWidget({ params }: { params: Widget }) {
</div>
)}
</div>
</div>
</WidgetWrapper>
);
}

View File

@ -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 (
<div className="relative shrink-0 grow-0 basis-[400px] bg-white/10 backdrop-blur-xl">
<WidgetWrapper>
<TitleBar id={params.id} title="Trending Notes" />
<div className="scrollbar-hide h-full max-w-full overflow-y-auto pb-20">
{status === 'loading' ? (
@ -58,6 +59,6 @@ export function TrendingNotesWidget({ params }: { params: Widget }) {
</div>
)}
</div>
</div>
</WidgetWrapper>
);
}

View File

@ -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 (
<div className="relative shrink-0 grow-0 basis-[400px] bg-white/10 backdrop-blur-xl">
<WidgetWrapper>
<TitleBar id={params.id} title="The Joy of Nostr" />
<div className="scrollbar-hide h-full overflow-y-auto px-3 pb-20">
{resources.map((resource, index) => (
@ -58,6 +59,6 @@ export function LearnNostrWidget({ params }: { params: Widget }) {
</div>
))}
</div>
</div>
</WidgetWrapper>
);
}

View File

@ -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 (
<Resizable
size={{ width: width, height: '100vh' }}
onResizeStart={(e) => 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}
</Resizable>
);
}

View File

@ -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<WidgetState>()(
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',