Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(wallet-dashboard): add styles for non visual assets #3761

Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
b0c9983
feat(wallet-dashboard): add protected layout
VmMad Oct 25, 2024
670f496
feat: add missing TopNav
VmMad Oct 28, 2024
d43a921
fix: tests
VmMad Oct 28, 2024
5ba9405
Merge branch 'develop' into tooling-dashboard/add-connected-layout
VmMad Oct 28, 2024
1100c2c
fix: imports
VmMad Oct 28, 2024
0f30b19
Merge branch 'develop' into tooling-dashboard/add-connected-layout
evavirseda Oct 28, 2024
a74ff0e
feat(wallet-dashboard): add assets page
VmMad Oct 28, 2024
9d4abe6
Merge branch 'tooling-dashboard/add-connected-layout' into tooling-da…
VmMad Oct 28, 2024
37ce97e
fix: add missing themes
VmMad Oct 28, 2024
8a8f711
Merge branch 'tooling-dashboard/add-connected-layout' into tooling-da…
VmMad Oct 28, 2024
6de4fc2
feat: improve connect button size
VmMad Oct 28, 2024
bc4612b
revert: "feat: improve connect button size"
VmMad Oct 28, 2024
516126f
Merge branch 'develop' into tooling-dashboard/add-assets-layout
VmMad Oct 29, 2024
551b48f
feat(wallet-dashboard): add grid for visual assets
VmMad Oct 29, 2024
546ac20
feat(Wallet-dashboard): add styles for non visual assets
VmMad Oct 29, 2024
ea32c22
refactor: remove unnecessary useMemo
VmMad Oct 30, 2024
8b5348a
fix: remove commented lines
VmMad Oct 30, 2024
f6ddc10
Merge branch 'develop' into tooling-dashboard/add-assets-layout
evavirseda Oct 30, 2024
a4db526
Merge branch 'tooling-dashboard/add-assets-layout' into tooling-dashb…
VmMad Nov 4, 2024
0753921
fix: improve type
VmMad Nov 6, 2024
f0131a0
Merge branch 'develop' into tooling-dashboard/add-assets-styles
VmMad Nov 6, 2024
9c5032e
fix: update IotaLogo
VmMad Nov 6, 2024
c88f22f
Merge branch 'tooling-dashboard/add-assets-styles' into tooling-dashb…
cpl121 Nov 6, 2024
eca8997
refactor: use explorer link and remove unnecessary code
VmMad Nov 7, 2024
f05b525
Merge branch 'develop' into tooling-dashboard/add-other-assets-styles
VmMad Nov 7, 2024
3540091
fix: bring back more details for visual sset details
VmMad Nov 7, 2024
889fe4e
fix: update to make it as reviewed
VmMad Nov 7, 2024
92f2afa
refactor: move explorer link generation logic to core
VmMad Nov 7, 2024
c1c5264
fix: update imports
VmMad Nov 7, 2024
79e89ba
fix: copy comment
VmMad Nov 8, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/core/src/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,6 @@ export * from './useUnlockTimelockedObjectsTransaction';
export * from './useGetAllOwnedObjects';
export * from './useGetTimelockedStakedObjects';
export * from './useGetActiveValidatorsInfo';
export * from './useCursorPagination';

export * from './stake';
56 changes: 56 additions & 0 deletions apps/core/src/hooks/useCursorPagination.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Copyright (c) 2024 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

import { useState } from 'react';
import { type InfiniteData, type UseInfiniteQueryResult } from '@tanstack/react-query';

interface PaginationProps {
hasFirst: boolean;
hasPrev: boolean;
hasNext: boolean;
onFirst(): void;
onPrev(): void;
onNext(): void;
}

interface CursorPaginationProps extends PaginationProps {
currentPage: number;
}

export interface PaginationResponse<Cursor = string> {
nextCursor: Cursor | null;
hasNextPage: boolean;
}

export function useCursorPagination<T>(query: UseInfiniteQueryResult<InfiniteData<T>>) {
const [currentPage, setCurrentPage] = useState(0);

return {
...query,
data: query.data?.pages[currentPage],
pagination: {
onFirst: () => setCurrentPage(0),
onNext: () => {
if (!query.data || query.isFetchingNextPage) {
return;
}

// Make sure we are at the end before fetching another page
if (currentPage >= query.data.pages.length - 1) {
query.fetchNextPage();
}

setCurrentPage(currentPage + 1);
},
onPrev: () => {
setCurrentPage(Math.max(currentPage - 1, 0));
},
hasFirst: currentPage !== 0,
hasNext:
!query.isFetchingNextPage &&
(currentPage < (query.data?.pages.length ?? 0) - 1 || !!query.hasNextPage),
hasPrev: currentPage !== 0,
currentPage,
} satisfies CursorPaginationProps,
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@

import { InfoBox, InfoBoxStyle, InfoBoxType, Select, SelectSize } from '@iota/apps-ui-kit';
import { useIotaClientQuery, useIotaClient, useIotaClientInfiniteQuery } from '@iota/dapp-kit';
import { useCursorPagination } from '@iota/core';
import { Warning } from '@iota/ui-icons';
import { useQuery } from '@tanstack/react-query';
import { useState } from 'react';
import { PlaceholderTable, TableCard, useCursorPagination } from '~/components/ui';
import { PlaceholderTable, TableCard } from '~/components/ui';
import { PAGE_SIZES_RANGE_20_60 } from '~/lib/constants';
import { generateEpochsTableColumns } from '~/lib/ui';
import { numberSuffix } from '~/lib/utils';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
import { useIotaClient } from '@iota/dapp-kit';
import { useQuery } from '@tanstack/react-query';
import { useEffect, useRef, useState } from 'react';
import { PlaceholderTable, TableCard, useCursorPagination } from '~/components/ui';
import { PlaceholderTable, TableCard } from '~/components/ui';
import { useCursorPagination } from '@iota/core';
import {
DEFAULT_TRANSACTIONS_LIMIT,
useGetTransactionBlocks,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@ import { InfoBox, InfoBoxStyle, InfoBoxType, Select, SelectSize } from '@iota/ap
import { useIotaClientQuery } from '@iota/dapp-kit';
import { Warning } from '@iota/ui-icons';
import { useMemo, useState } from 'react';
import { PlaceholderTable, TableCard, useCursorPagination } from '~/components/ui';
import { PlaceholderTable, TableCard } from '~/components/ui';
import { DEFAULT_CHECKPOINTS_LIMIT, useGetCheckpoints } from '~/hooks/useGetCheckpoints';
import { PAGE_SIZES_RANGE_20_60 } from '~/lib/constants';
import { generateCheckpointsTableColumns } from '~/lib/ui';
import { numberSuffix } from '~/lib/utils';
import { useCursorPagination } from '@iota/core';

interface CheckpointsTableProps {
disablePagination?: boolean;
Expand Down
9 changes: 7 additions & 2 deletions apps/explorer/src/components/owned-objects/OwnedObjects.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@
// Modifications Copyright (c) 2024 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

import { useGetKioskContents, useGetOwnedObjects, useLocalStorage } from '@iota/core';
import {
useGetKioskContents,
useGetOwnedObjects,
useLocalStorage,
useCursorPagination,
} from '@iota/core';
import {
Button,
ButtonSize,
Expand All @@ -27,7 +32,7 @@ import clsx from 'clsx';
import { useEffect, useMemo, useState } from 'react';
import { ListView, SmallThumbnailsView, ThumbnailsView } from '~/components';
import { ObjectViewMode } from '~/lib/enums';
import { Pagination, useCursorPagination } from '~/components/ui';
import { Pagination } from '~/components/ui';
import { PAGE_SIZES_RANGE_10_50 } from '~/lib/constants';

const SHOW_PAGINATION_MAX_ITEMS = 9;
Expand Down
40 changes: 1 addition & 39 deletions apps/explorer/src/components/ui/Pagination.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

import { Button, ButtonSize, ButtonType } from '@iota/apps-ui-kit';
import { ArrowLeft, ArrowRight, DoubleArrowLeft } from '@iota/ui-icons';
import { type InfiniteData, type UseInfiniteQueryResult } from '@tanstack/react-query';
import { useState } from 'react';

interface PaginationProps {
Expand All @@ -16,49 +15,12 @@ interface PaginationProps {
onNext(): void;
}

interface CursorPaginationProps extends PaginationProps {
currentPage: number;
}

export interface PaginationResponse<Cursor = string> {
nextCursor: Cursor | null;
hasNextPage: boolean;
}

export function useCursorPagination<T>(query: UseInfiniteQueryResult<InfiniteData<T>>) {
const [currentPage, setCurrentPage] = useState(0);

return {
...query,
data: query.data?.pages[currentPage],
pagination: {
onFirst: () => setCurrentPage(0),
onNext: () => {
if (!query.data || query.isFetchingNextPage) {
return;
}

// Make sure we are at the end before fetching another page
if (currentPage >= query.data.pages.length - 1) {
query.fetchNextPage();
}

setCurrentPage(currentPage + 1);
},
onPrev: () => {
setCurrentPage(Math.max(currentPage - 1, 0));
},
hasFirst: currentPage !== 0,
hasNext:
!query.isFetchingNextPage &&
(currentPage < (query.data?.pages.length ?? 0) - 1 || !!query.hasNextPage),
hasPrev: currentPage !== 0,
currentPage,
} satisfies CursorPaginationProps,
};
}

/** @deprecated Prefer `useCursorPagination` + `useInfiniteQuery` for pagination. */
/** @deprecated Prefer `useCursorPagination` from core + `useInfiniteQuery` for pagination. */
export function usePaginationStack<Cursor = string>() {
const [stack, setStack] = useState<Cursor[]>([]);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@

import { DropdownPosition, Select, SelectSize } from '@iota/apps-ui-kit';
import { useState } from 'react';
import { PlaceholderTable, TableCard, useCursorPagination } from '~/components/ui';
import { PlaceholderTable, TableCard } from '~/components/ui';
import { useCursorPagination } from '@iota/core';
import {
DEFAULT_TRANSACTIONS_LIMIT,
useGetTransactionBlocks,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Copyright (c) 2024 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

export { VisualAssetCard } from './VisualAssetCard';
export * from './VisualAssetCard';
export * from './visual-asset-card.enums';
55 changes: 28 additions & 27 deletions apps/wallet-dashboard/app/(protected)/assets/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,16 @@

'use client';

import { AssetCard, VirtualList } from '@/components';
import { ASSETS_ROUTE } from '@/lib/constants/routes.constants';
import { Panel, Title, Chip } from '@iota/apps-ui-kit';
import { hasDisplayData, useGetOwnedObjects } from '@iota/core';
import { PageSizeSelector, PaginationOptions } from '@/components';
import { Panel, Title, Chip, TitleSize, DropdownPosition } from '@iota/apps-ui-kit';
import { hasDisplayData, useCursorPagination, useGetOwnedObjects } from '@iota/core';
import { useCurrentAccount } from '@iota/dapp-kit';
import { IotaObjectData } from '@iota/iota-sdk/client';
import { useState } from 'react';
import { AssetCategory } from '@/lib/enums';
import Link from 'next/link';
import { AssetList } from '@/components/AssetsList';

const PAGINATION_RANGE = [20, 40, 60];

const ASSET_CATEGORIES: { label: string; value: AssetCategory }[] = [
{
Expand All @@ -26,22 +27,21 @@ const ASSET_CATEGORIES: { label: string; value: AssetCategory }[] = [

export default function AssetsDashboardPage(): React.JSX.Element {
const [selectedCategory, setSelectedCategory] = useState<AssetCategory>(AssetCategory.Visual);
const [limit, setLimit] = useState<number>(PAGINATION_RANGE[1]);

const account = useCurrentAccount();
const {
data: ownedObjects,
fetchNextPage,
hasNextPage,
isFetchingNextPage,
} = useGetOwnedObjects(account?.address);
const ownedObjectsQuery = useGetOwnedObjects(account?.address, undefined, limit);

const { data, pagination } = useCursorPagination(ownedObjectsQuery);

const { data: ownedObjects } = data || {};

const [visual, nonVisual] = (() => {
const visual: IotaObjectData[] = [];
const nonVisual: IotaObjectData[] = [];

ownedObjects?.pages
.flatMap((page) => page.data)
.filter((asset) => asset.data && asset.data.objectId)
ownedObjects
?.filter((asset) => asset.data && asset.data.objectId)
.forEach((asset) => {
if (asset.data) {
if (hasDisplayData(asset)) {
Expand All @@ -64,7 +64,7 @@ export default function AssetsDashboardPage(): React.JSX.Element {

return (
<Panel>
<Title title="Assets" />
<Title title="Assets" size={TitleSize.Medium} />
<div className="px-lg">
<div className="flex flex-row items-center justify-start gap-xs py-xs">
{ASSET_CATEGORIES.map((tab) => (
Expand All @@ -77,18 +77,19 @@ export default function AssetsDashboardPage(): React.JSX.Element {
))}
</div>

<div className="max-h-[600px] overflow-auto py-sm">
<VirtualList
items={assetList}
hasNextPage={hasNextPage}
isFetchingNextPage={isFetchingNextPage}
fetchNextPage={fetchNextPage}
estimateSize={() => 180}
render={(asset) => (
<Link href={ASSETS_ROUTE.path + `/${asset.objectId}`}>
<AssetCard asset={asset} />
</Link>
)}
<AssetList assets={assetList} selectedCategory={selectedCategory} />
evavirseda marked this conversation as resolved.
Show resolved Hide resolved
<div className="flex flex-row items-center justify-end py-xs">
<PaginationOptions
pagination={pagination}
action={
<PageSizeSelector
pagination={pagination}
range={PAGINATION_RANGE}
dropdownPosition={DropdownPosition.Top}
setLimit={(e) => setLimit(e)}
limit={limit.toString()}
/>
}
/>
</div>
</div>
Expand Down
4 changes: 4 additions & 0 deletions apps/wallet-dashboard/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -81,4 +81,8 @@ body {
'coins activity activity';
}
}

.grid-template-visual-assets {
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
}
}
74 changes: 74 additions & 0 deletions apps/wallet-dashboard/components/AssetCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Copyright (c) 2024 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

import { AssetCategory } from '@/lib/enums';
import { ArrowTopRight, VisibilityOff } from '@iota/ui-icons';
import {
Card,
CardAction,
CardActionType,
CardBody,
CardType,
VisualAssetCard,
VisualAssetType,
} from '@iota/apps-ui-kit';
import { useGetNFTMeta } from '@iota/core';
import { IotaObjectData } from '@iota/iota-sdk/client';
import { formatAddress, parseStructTag } from '@iota/iota-sdk/utils';

interface AssetCardProps {
asset: IotaObjectData;
type: AssetCategory;
onClick?: () => void;
cpl121 marked this conversation as resolved.
Show resolved Hide resolved
}

export function AssetCard({ asset, type, onClick }: AssetCardProps): React.JSX.Element {
return type === AssetCategory.Visual ? (
<VisualAsset asset={asset} icon={<VisibilityOff />} onClick={onClick} />
) : (
<NonVisualAssetCard key={asset.digest} asset={asset} onClick={onClick} />
);
}

type NonVisualAssetCardProps = {
asset: IotaObjectData;
} & Pick<React.ComponentProps<typeof Card>, 'onClick'>;

export function NonVisualAssetCard({ asset, onClick }: NonVisualAssetCardProps): React.JSX.Element {
const { address, module, name } = parseStructTag(asset.type!);
return (
<Card type={CardType.Default} isHoverable onClick={onClick}>
<CardBody
title={formatAddress(asset.objectId!)}
subtitle={`${formatAddress(address)}::${module}::${name}`}
isTextTruncated
/>
<CardAction type={CardActionType.Link} icon={<ArrowTopRight />} />
</Card>
);
}

type VisualAssetProps = {
asset: IotaObjectData;
} & Pick<React.ComponentProps<typeof VisualAssetCard>, 'onClick' | 'onIconClick' | 'icon'>;

export function VisualAsset({
asset,
icon,
onClick,
onIconClick,
}: VisualAssetProps): React.JSX.Element | null {
const { data: nftMeta } = useGetNFTMeta(asset.objectId);
return asset.display && nftMeta && nftMeta.imageUrl ? (
<VisualAssetCard
cpl121 marked this conversation as resolved.
Show resolved Hide resolved
assetSrc={nftMeta?.imageUrl ?? asset?.display?.data?.imageUrl ?? ''}
assetTitle={nftMeta?.name ?? asset?.display?.data?.name}
assetType={VisualAssetType.Image}
altText={nftMeta?.name ?? (asset?.display?.data?.name || 'NFT')}
isHoverable
icon={icon}
onClick={onClick}
onIconClick={onIconClick}
/>
) : null;
}
Loading
Loading