mirror of
https://github.com/luminous-devs/lume.git
synced 2024-09-18 11:13:30 +00:00
redesign widget list
This commit is contained in:
parent
09aea3cff5
commit
5c8850ea8f
@ -2,9 +2,17 @@ import { useCallback } from 'react';
|
||||
|
||||
import { useStorage } from '@libs/storage/provider';
|
||||
|
||||
import {
|
||||
ArticleIcon,
|
||||
FileIcon,
|
||||
FollowsIcon,
|
||||
GroupFeedsIcon,
|
||||
HashtagIcon,
|
||||
TrendingIcon,
|
||||
} from '@shared/icons';
|
||||
import { TitleBar } from '@shared/titleBar';
|
||||
|
||||
import { DefaultWidgets, useWidgets } from '@stores/widgets';
|
||||
import { DefaultWidgets, WidgetKinds, useWidgets } from '@stores/widgets';
|
||||
|
||||
import { Widget, WidgetGroup, WidgetGroupItem } from '@utils/types';
|
||||
|
||||
@ -20,6 +28,31 @@ export function WidgetList({ params }: { params: Widget }) {
|
||||
removeWidget(db, params.id);
|
||||
};
|
||||
|
||||
const renderIcon = useCallback(
|
||||
(kind: number) => {
|
||||
switch (kind) {
|
||||
case WidgetKinds.tmp.xfeed:
|
||||
return <GroupFeedsIcon className="h-5 w-5 text-white" />;
|
||||
case WidgetKinds.local.follows:
|
||||
return <FollowsIcon className="h-5 w-5 text-white" />;
|
||||
case WidgetKinds.local.files:
|
||||
case WidgetKinds.global.files:
|
||||
return <FileIcon className="h-5 w-5 text-white" />;
|
||||
case WidgetKinds.local.articles:
|
||||
case WidgetKinds.global.articles:
|
||||
return <ArticleIcon className="h-5 w-5 text-white" />;
|
||||
case WidgetKinds.tmp.xhashtag:
|
||||
return <HashtagIcon className="h-5 w-4 text-white" />;
|
||||
case WidgetKinds.nostrBand.trendingAccounts:
|
||||
case WidgetKinds.nostrBand.trendingNotes:
|
||||
return <TrendingIcon className="h-5 w-4 text-white" />;
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
},
|
||||
[DefaultWidgets]
|
||||
);
|
||||
|
||||
const renderItem = useCallback(
|
||||
(row: WidgetGroup) => {
|
||||
return (
|
||||
@ -27,10 +60,29 @@ export function WidgetList({ params }: { params: Widget }) {
|
||||
<h3 className="font-medium text-white/50">{row.title}</h3>
|
||||
<div className="flex flex-col divide-y divide-white/5 overflow-hidden rounded-xl bg-white/10">
|
||||
{row.data.map((item, index) => (
|
||||
<button onClick={() => openWidget(item)} key={index}>
|
||||
<div className="inline-flex h-14 w-full flex-col items-start justify-center gap-1 px-4 hover:bg-white/10">
|
||||
<h5 className="font-medium leading-none">{item.title}</h5>
|
||||
<p className="text-xs leading-none text-white/50">{item.description}</p>
|
||||
<button
|
||||
onClick={() => openWidget(item)}
|
||||
key={index}
|
||||
className="flex items-center gap-2.5 px-4 hover:bg-white/10"
|
||||
>
|
||||
{item.icon ? (
|
||||
<div className="h-10 w-10 shrink-0 rounded-md">
|
||||
<img
|
||||
src={item.icon}
|
||||
alt={item.title}
|
||||
className="h-10 w-10 object-cover"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="inline-flex h-10 w-10 shrink-0 items-center justify-center rounded-md bg-white/10">
|
||||
{renderIcon(item.kind)}
|
||||
</div>
|
||||
)}
|
||||
<div className="inline-flex h-16 w-full flex-col items-start justify-center gap-1">
|
||||
<h5 className="line-clamp-1 font-medium leading-none">{item.title}</h5>
|
||||
<p className="line-clamp-1 text-xs leading-none text-white/50">
|
||||
{item.description}
|
||||
</p>
|
||||
</div>
|
||||
</button>
|
||||
))}
|
||||
@ -44,7 +96,7 @@ export function WidgetList({ params }: { params: Widget }) {
|
||||
return (
|
||||
<div className="relative h-full shrink-0 grow-0 basis-[400px] overflow-hidden bg-white/10">
|
||||
<TitleBar id={params.id} title="Add widget" />
|
||||
<div className="flex flex-col gap-8 px-3">
|
||||
<div className="flex flex-col gap-6 px-3">
|
||||
{DefaultWidgets.map((row: WidgetGroup) => renderItem(row))}
|
||||
</div>
|
||||
<div className="mt-6 px-3">
|
||||
@ -55,7 +107,7 @@ export function WidgetList({ params }: { params: Widget }) {
|
||||
className="inline-flex h-14 w-full items-center justify-center gap-2.5 rounded-xl bg-white/5 text-sm font-medium text-white/50"
|
||||
>
|
||||
Build your own widget{' '}
|
||||
<div className="-rotate-3 transform rounded-md bg-white/10 px-1.5 py-1">
|
||||
<div className="-rotate-3 transform rounded-md border border-white/20 bg-white/10 px-1.5 py-1">
|
||||
<span className="bg-gradient-to-t from-fuchsia-200 via-red-200 to-orange-300 bg-clip-text text-xs text-transparent">
|
||||
Coming soon
|
||||
</span>
|
||||
|
@ -17,40 +17,32 @@ export const NDKInstance = () => {
|
||||
const cacheAdapter = useMemo(() => new TauriAdapter(), [ndk]);
|
||||
|
||||
// TODO: fully support NIP-11
|
||||
async function verifyRelays(relays: string[]) {
|
||||
async function getExplicitRelays() {
|
||||
try {
|
||||
const urls: string[] = relays.map((relay) => {
|
||||
if (relay.startsWith('ws')) {
|
||||
return relay.replace('ws', 'http');
|
||||
}
|
||||
if (relay.startsWith('wss')) {
|
||||
return relay.replace('wss', 'https');
|
||||
}
|
||||
});
|
||||
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort('timeout'), 10000);
|
||||
|
||||
const requests = urls.map((url) =>
|
||||
fetch(url, {
|
||||
// get relays
|
||||
const relays = (await db.getExplicitRelayUrls()) ?? FULL_RELAYS;
|
||||
|
||||
const requests = relays.map((relay) => {
|
||||
const url = new URL(relay);
|
||||
|
||||
return fetch(`https://${url.hostname + url.pathname}`, {
|
||||
headers: { Accept: 'application/nostr+json' },
|
||||
signal: controller.signal,
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
const responses = await Promise.all(requests);
|
||||
const errors = responses.filter((response) => !response.ok);
|
||||
|
||||
if (errors.length > 0) {
|
||||
throw errors.map((response) => Error(response.statusText));
|
||||
}
|
||||
if (errors.length > 0) throw errors.map((response) => Error(response.statusText));
|
||||
|
||||
const verifiedRelays: string[] = responses.map((res) => {
|
||||
if (res.url.startsWith('http')) {
|
||||
return res.url.replace('htto', 'ws');
|
||||
}
|
||||
if (res.url.startsWith('https')) {
|
||||
return res.url.replace('https', 'wss');
|
||||
}
|
||||
const url = new URL(res.url);
|
||||
if (url.protocol === 'http:') return `ws://${url.hostname + url.pathname}`;
|
||||
if (url.protocol === 'https:') return `wss://${url.hostname + url.pathname}`;
|
||||
});
|
||||
|
||||
// clear timeout
|
||||
@ -59,31 +51,14 @@ export const NDKInstance = () => {
|
||||
// return all validate relays
|
||||
return verifiedRelays;
|
||||
} catch (e) {
|
||||
console.error('verify relay failed with error: ', e);
|
||||
await message(e, { title: 'Cannot connect to relays', type: 'error' });
|
||||
}
|
||||
}
|
||||
|
||||
async function initNDK() {
|
||||
let explicitRelayUrls: string[];
|
||||
const explicitRelayUrlsFromDB = await db.getExplicitRelayUrls();
|
||||
|
||||
console.log('relays in db: ', explicitRelayUrlsFromDB);
|
||||
console.log('ndk cache adapter: ', cacheAdapter);
|
||||
|
||||
if (explicitRelayUrlsFromDB) {
|
||||
explicitRelayUrls = await verifyRelays(explicitRelayUrlsFromDB);
|
||||
} else {
|
||||
explicitRelayUrls = await verifyRelays(FULL_RELAYS);
|
||||
}
|
||||
|
||||
if (explicitRelayUrls.length < 1) {
|
||||
await message('Something is wrong. No relays have been found.', {
|
||||
title: 'Lume',
|
||||
type: 'error',
|
||||
});
|
||||
}
|
||||
|
||||
const explicitRelayUrls = await getExplicitRelays();
|
||||
const instance = new NDK({ explicitRelayUrls, cacheAdapter });
|
||||
|
||||
try {
|
||||
await instance.connect(10000);
|
||||
} catch (error) {
|
||||
|
22
src/shared/icons/article.tsx
Normal file
22
src/shared/icons/article.tsx
Normal file
@ -0,0 +1,22 @@
|
||||
import { SVGProps } from 'react';
|
||||
|
||||
export function ArticleIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="1.5"
|
||||
d="M16.25 12V4.75a1 1 0 00-1-1H3.75a1 1 0 00-1 1v13a2.5 2.5 0 002.5 2.5H18.5M16.25 12v5.75a2.5 2.5 0 005 0V13a1 1 0 00-1-1h-4zm-9.5 3.75h5.5m-5.5-8h5.5v4.5h-5.5v-4.5z"
|
||||
></path>
|
||||
</svg>
|
||||
);
|
||||
}
|
@ -13,10 +13,9 @@ export function FileIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement
|
||||
<path
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="1.5"
|
||||
d="M8.75 6.75h6.5m-6.5 4h6.5m-6.5 4h2.5m-5.5 6.5h12.5a1 1 0 001-1V3.75a1 1 0 00-1-1H5.75a1 1 0 00-1 1v16.5a1 1 0 001 1z"
|
||||
/>
|
||||
d="M12.75 3.25v5a1 1 0 001 1h5m-10 4h3.5m-3.5 4h6.5m-9.5-14.5h6.586a1 1 0 01.707.293l5.914 5.914a1 1 0 01.293.707V20.25a1 1 0 01-1 1H5.75a1 1 0 01-1-1V3.75a1 1 0 011-1z"
|
||||
></path>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
22
src/shared/icons/follows.tsx
Normal file
22
src/shared/icons/follows.tsx
Normal file
@ -0,0 +1,22 @@
|
||||
import { SVGProps } from 'react';
|
||||
|
||||
export function FollowsIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="1.5"
|
||||
d="M17.75 19.25h3.673c.581 0 1.045-.496.947-1.07-.531-3.118-2.351-5.43-5.37-5.43-.446 0-.866.05-1.26.147M11.25 7a3.25 3.25 0 11-6.5 0 3.25 3.25 0 016.5 0zm8.5.5a2.75 2.75 0 11-5.5 0 2.75 2.75 0 015.5 0zM1.87 19.18c.568-3.68 2.647-6.43 6.13-6.43 3.482 0 5.561 2.75 6.13 6.43.088.575-.375 1.07-.956 1.07H2.825c-.58 0-1.043-.495-.955-1.07z"
|
||||
></path>
|
||||
</svg>
|
||||
);
|
||||
}
|
22
src/shared/icons/groupFeeds.tsx
Normal file
22
src/shared/icons/groupFeeds.tsx
Normal file
@ -0,0 +1,22 @@
|
||||
import { SVGProps } from 'react';
|
||||
|
||||
export function GroupFeedsIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="1.5"
|
||||
d="M14.425 13.18c3.361-1.396 7.598.605 8.454 6.003.09.573-.372 1.067-.952 1.067H16.75M10.75 7a3.25 3.25 0 11-6.5 0 3.25 3.25 0 016.5 0zm9 0a3.25 3.25 0 11-6.5 0 3.25 3.25 0 016.5 0zm-6.966 13.25H2.072c-.58 0-1.042-.497-.951-1.07 1.362-8.573 11.252-8.573 12.614 0 .091.573-.371 1.07-.951 1.07z"
|
||||
></path>
|
||||
</svg>
|
||||
);
|
||||
}
|
@ -16,7 +16,7 @@ export function HashtagIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElem
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="1.5"
|
||||
d="M8.75 3.75l-2 16.5m10.5-16.5l-2 16.5M3.75 7.75h16.5m0 8.5H3.75"
|
||||
/>
|
||||
></path>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
@ -58,3 +58,6 @@ export * from './chevronUp';
|
||||
export * from './secure';
|
||||
export * from './verified';
|
||||
export * from './mention';
|
||||
export * from './groupFeeds';
|
||||
export * from './article';
|
||||
export * from './follows';
|
||||
|
@ -3,7 +3,7 @@ import { createJSONStorage, persist } from 'zustand/middleware';
|
||||
|
||||
import { LumeStorage } from '@libs/storage/instance';
|
||||
|
||||
import { Widget } from '@utils/types';
|
||||
import { Widget, WidgetGroup } from '@utils/types';
|
||||
|
||||
interface WidgetState {
|
||||
widgets: null | Array<Widget>;
|
||||
@ -39,7 +39,7 @@ export const WidgetKinds = {
|
||||
},
|
||||
};
|
||||
|
||||
export const DefaultWidgets = [
|
||||
export const DefaultWidgets: Array<WidgetGroup> = [
|
||||
{
|
||||
title: 'Network / Follows',
|
||||
data: [
|
||||
|
1
src/utils/types.d.ts
vendored
1
src/utils/types.d.ts
vendored
@ -46,6 +46,7 @@ export interface WidgetGroupItem {
|
||||
title: string;
|
||||
description: string;
|
||||
kind: number;
|
||||
icon?: string;
|
||||
}
|
||||
|
||||
export interface Widget {
|
||||
|
Loading…
Reference in New Issue
Block a user