resizable widget
This commit is contained in:
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from './wrapper';
|
||||
export * from './local/feeds';
|
||||
export * from './local/network';
|
||||
export * from './local/user';
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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 (
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
32
src/shared/widgets/wrapper.tsx
Normal file
32
src/shared/widgets/wrapper.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user