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): Improve assets filter #4512

Merged
merged 32 commits into from
Jan 10, 2025
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
bf71636
refactor(wallet): move notification to separate component.
panteleymonchuk Dec 16, 2024
bc69f2f
feat(wallet): enhance hidden asset functionality with undo option and…
panteleymonchuk Dec 16, 2024
351bfe4
feat(wallet): add undo functionality for showing hidden assets with n…
panteleymonchuk Dec 16, 2024
a7a42f7
feat(core): move HiddenAssetsProvider.
panteleymonchuk Dec 16, 2024
0b491ad
feat(core): move useGetNFTs.
panteleymonchuk Dec 16, 2024
5137f19
feat(core): update useGetNFTs to accept filter and improve asset fetc…
panteleymonchuk Dec 16, 2024
5185c8d
Merge remote-tracking branch 'origin/develop' into tooling-dashboard/…
panteleymonchuk Dec 17, 2024
70752dd
refactor(dashboard): remove 'Hidden' asset category and related logic
panteleymonchuk Dec 18, 2024
73f2001
refactor(wallet-dashboard): remove 'Hidden' asset category from layout
panteleymonchuk Dec 18, 2024
9537c65
refactor(dashboard): remove HiddenAssets context, update default sele…
panteleymonchuk Dec 18, 2024
47a7033
Merge remote-tracking branch 'origin/develop' into tooling-dashboard/…
panteleymonchuk Dec 18, 2024
90e0791
feat(core): add refetch capability to useGetNFTs hook
panteleymonchuk Dec 18, 2024
1647efa
Merge branch 'develop' into tooling-dashboard/improve-asset-filter
panteleymonchuk Dec 18, 2024
2a08552
refactor(core): improve hide/show logic
panteleymonchuk Dec 20, 2024
2939b92
refactor(core): remove undo functionality and streamline asset visibi…
panteleymonchuk Dec 20, 2024
f12a355
refactor(core): simplify asset visibility management and improve erro…
panteleymonchuk Dec 20, 2024
600373a
refactor(wallet): rename MoveAssetNotification to MovedAssetNotification
panteleymonchuk Dec 20, 2024
d4c6b1d
Merge branch 'develop' into tooling-dashboard/improve-asset-filter
panteleymonchuk Dec 20, 2024
8d81ff3
Merge remote-tracking branch 'origin/develop' into tooling-dashboard/…
panteleymonchuk Dec 23, 2024
57851fe
refactor(dashboard): adapt logic to useGetNFTs hook.
panteleymonchuk Dec 23, 2024
372e5af
Merge branch 'develop' into tooling-dashboard/improve-asset-filter
panteleymonchuk Dec 27, 2024
502121b
Merge remote-tracking branch 'origin/develop' into tooling-dashboard/…
panteleymonchuk Jan 8, 2025
170730c
fix(wallet-dashboard): improve asset loading logic and conditional re…
panteleymonchuk Jan 8, 2025
04c36ba
feat(wallet-dashboard): move logic for page to the hook
panteleymonchuk Jan 8, 2025
7e296af
Merge branch 'develop' into tooling-dashboard/improve-asset-filter
panteleymonchuk Jan 8, 2025
8ea1ae8
fix(dashboard): load more if we have intersection
panteleymonchuk Jan 9, 2025
f9cb9e0
Merge remote-tracking branch 'origin/develop' into tooling-dashboard/…
panteleymonchuk Jan 9, 2025
6c44bc4
feat(ui-kit, dashboard): add disabled state to Chip component and upd…
panteleymonchuk Jan 9, 2025
96168f3
Merge branch 'develop' into tooling-dashboard/improve-asset-filter
panteleymonchuk Jan 10, 2025
25de57e
refactor(core): reorganize usePageAssets hook into ui directory
panteleymonchuk Jan 10, 2025
7c4f517
refactor(core): move filter from param to hook direct.
panteleymonchuk Jan 10, 2025
dd69e67
Merge branch 'develop' into tooling-dashboard/improve-asset-filter
panteleymonchuk Jan 10, 2025
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/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"bignumber.js": "^9.1.1",
"clsx": "^2.1.1",
"formik": "^2.4.2",
"idb-keyval": "^6.2.1",
"qrcode.react": "^4.0.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
Expand Down
143 changes: 143 additions & 0 deletions apps/core/src/contexts/HiddenAssetsProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
// Copyright (c) Mysten Labs, Inc.
// Modifications Copyright (c) 2024 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0
marc2332 marked this conversation as resolved.
Show resolved Hide resolved

import { get, set } from 'idb-keyval';
import { createContext, useCallback, useContext, useEffect, useState, type ReactNode } from 'react';

const HIDDEN_ASSET_IDS = 'hidden-asset-ids';

export type HiddenAssets =
| {
type: 'loading';
}
| {
type: 'loaded';
assetIds: string[];
};

interface HiddenAssetContext {
hiddenAssets: HiddenAssets;
setHiddenAssetIds: (hiddenAssetIds: string[]) => void;
hideAsset: (assetId: string) => Promise<string | undefined>;
undoHideAsset: (assetId: string) => Promise<void>;
showAsset: (assetId: string) => Promise<string | undefined>;
undoShowAsset: (assetId: string) => Promise<void>;
}

export const HiddenAssetsContext = createContext<HiddenAssetContext>({
hiddenAssets: {
type: 'loading',
},
setHiddenAssetIds: () => {},
hideAsset: async () => undefined,
undoHideAsset: async () => undefined,
showAsset: async () => undefined,
undoShowAsset: async () => undefined,
panteleymonchuk marked this conversation as resolved.
Show resolved Hide resolved
});

export const HiddenAssetsProvider = ({ children }: { children: ReactNode }) => {
panteleymonchuk marked this conversation as resolved.
Show resolved Hide resolved
const [hiddenAssets, setHiddenAssets] = useState<HiddenAssets>({
type: 'loading',
});

const hiddenAssetIds = hiddenAssets.type === 'loaded' ? hiddenAssets.assetIds : [];

useEffect(() => {
(async () => {
const hiddenAssets = (await get<string[]>(HIDDEN_ASSET_IDS)) ?? [];
panteleymonchuk marked this conversation as resolved.
Show resolved Hide resolved
setHiddenAssetIds(hiddenAssets);
})();
}, []);

function setHiddenAssetIds(hiddenAssetIds: string[]) {
panteleymonchuk marked this conversation as resolved.
Show resolved Hide resolved
setHiddenAssets({
type: 'loaded',
assetIds: hiddenAssetIds,
});
}

const hideAssetId = useCallback(
async (newAssetId: string) => {
if (hiddenAssetIds.includes(newAssetId)) return;

const newHiddenAssetIds = [...hiddenAssetIds, newAssetId];
setHiddenAssetIds(newHiddenAssetIds);
await set(HIDDEN_ASSET_IDS, newHiddenAssetIds);
panteleymonchuk marked this conversation as resolved.
Show resolved Hide resolved
return newAssetId;
},
[hiddenAssetIds],
);

const undoHideAsset = async (assetId: string) => {
panteleymonchuk marked this conversation as resolved.
Show resolved Hide resolved
try {
let updatedHiddenAssetIds;
setHiddenAssets((previous) => {
const previousIds = previous.type === 'loaded' ? previous.assetIds : [];
updatedHiddenAssetIds = previousIds.filter((id) => id !== assetId);
return {
type: 'loaded',
assetIds: updatedHiddenAssetIds,
};
});
await set(HIDDEN_ASSET_IDS, updatedHiddenAssetIds);
} catch (error) {
// Restore the asset ID back to the hidden asset IDs list
setHiddenAssetIds([...hiddenAssetIds, assetId]);
await set(HIDDEN_ASSET_IDS, hiddenAssetIds);
}
};

const showAssetId = useCallback(
async (newAssetId: string) => {
if (!hiddenAssetIds.includes(newAssetId)) return;

try {
const updatedHiddenAssetIds = hiddenAssetIds.filter((id) => id !== newAssetId);
setHiddenAssetIds(updatedHiddenAssetIds);
await set(HIDDEN_ASSET_IDS, updatedHiddenAssetIds);
return newAssetId;
} catch (error) {
// Restore the asset ID back to the hidden asset IDs list
setHiddenAssetIds([...hiddenAssetIds, newAssetId]);
await set(HIDDEN_ASSET_IDS, hiddenAssetIds);
}
panteleymonchuk marked this conversation as resolved.
Show resolved Hide resolved
},
[hiddenAssetIds],
);

const undoShowAsset = async (assetId: string) => {
panteleymonchuk marked this conversation as resolved.
Show resolved Hide resolved
let newHiddenAssetIds;
setHiddenAssets((previous) => {
const previousIds = previous.type === 'loaded' ? previous.assetIds : [];
newHiddenAssetIds = [...previousIds, assetId];
return {
type: 'loaded',
assetIds: newHiddenAssetIds,
};
});
await set(HIDDEN_ASSET_IDS, newHiddenAssetIds);
};

return (
<HiddenAssetsContext.Provider
value={{
hiddenAssets:
hiddenAssets.type === 'loaded'
? { ...hiddenAssets, assetIds: Array.from(new Set(hiddenAssetIds)) }
: { type: 'loading' },
setHiddenAssetIds,
panteleymonchuk marked this conversation as resolved.
Show resolved Hide resolved
hideAsset: hideAssetId,
undoHideAsset: undoHideAsset,
showAsset: showAssetId,
undoShowAsset: undoShowAsset,
panteleymonchuk marked this conversation as resolved.
Show resolved Hide resolved
}}
>
{children}
</HiddenAssetsContext.Provider>
);
};

export const useHiddenAssets = () => {
return useContext(HiddenAssetsContext);
};
1 change: 1 addition & 0 deletions apps/core/src/contexts/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
// SPDX-License-Identifier: Apache-2.0

export * from './ThemeContext';
export * from './HiddenAssetsProvider';
1 change: 1 addition & 0 deletions apps/core/src/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export * from './useOwnedNFT';
export * from './useNftDetails';
export * from './useCountdownByTimestamp';
export * from './useStakeRewardStatus';
export * from './useGetNFTs';
export * from './useRecognizedPackages';

export * from './stake';
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,15 @@
// Modifications Copyright (c) 2024 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

import { hasDisplayData, isKioskOwnerToken, useGetOwnedObjects, useKioskClient } from '@iota/core';
import { type IotaObjectData } from '@iota/iota-sdk/client';
import {
hasDisplayData,
isKioskOwnerToken,
useGetOwnedObjects,
useKioskClient,
HiddenAssets,
} from '../../';
import { type IotaObjectData, IotaObjectDataFilter } from '@iota/iota-sdk/client';
import { useMemo } from 'react';
import { useHiddenAssets } from '../pages/home/assets/HiddenAssetsProvider';

type OwnedAssets = {
visual: IotaObjectData[];
Expand All @@ -18,25 +23,26 @@ export enum AssetFilterTypes {
Other = 'other',
}

export function useGetNFTs(address?: string | null) {
const OBJECTS_PER_REQ = 50;

export function useGetNFTs(
address?: string | null,
filter?: IotaObjectDataFilter,
hiddenAssets?: HiddenAssets,
) {
const kioskClient = useKioskClient();
const {
data,
isFetching,
isPending,
error,
isError,
isFetchingNextPage,
hasNextPage,
fetchNextPage,
isLoading,
} = useGetOwnedObjects(
address,
{
MatchNone: [{ StructType: '0x2::coin::Coin' }],
},
50,
);
const { hiddenAssets } = useHiddenAssets();
refetch,
} = useGetOwnedObjects(address, filter, OBJECTS_PER_REQ);

const assets = useMemo(() => {
const ownedAssets: OwnedAssets = {
Expand All @@ -45,13 +51,16 @@ export function useGetNFTs(address?: string | null) {
hidden: [],
};

if (hiddenAssets.type === 'loading') {
if (hiddenAssets?.type === 'loading') {
return ownedAssets;
} else {
const groupedAssets = data?.pages
.flatMap((page) => page.data)
.reduce((acc, curr) => {
if (curr.data?.objectId && hiddenAssets.assetIds.includes(curr.data?.objectId))
if (
curr.data?.objectId &&
hiddenAssets?.assetIds?.includes(curr.data?.objectId)
)
acc.hidden.push(curr.data as IotaObjectData);
else if (hasDisplayData(curr) || isKioskOwnerToken(kioskClient.network, curr))
acc.visual.push(curr.data as IotaObjectData);
Expand All @@ -64,12 +73,14 @@ export function useGetNFTs(address?: string | null) {

return {
data: assets,
isFetching,
isLoading,
hasNextPage,
isFetchingNextPage,
fetchNextPage,
isPending: isPending,
isError: isError,
error,
refetch,
};
}
75 changes: 43 additions & 32 deletions apps/wallet-dashboard/app/(protected)/assets/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,14 @@
'use client';

import { Panel, Title, Chip, TitleSize } from '@iota/apps-ui-kit';
import { hasDisplayData, useGetOwnedObjects } from '@iota/core';
import { hasDisplayData, useGetNFTs } from '@iota/core';
import { useCurrentAccount } from '@iota/dapp-kit';
import { IotaObjectData } from '@iota/iota-sdk/client';
import { useState } from 'react';
import { useState, useEffect } from 'react';
import { AssetCategory } from '@/lib/enums';
import { AssetList } from '@/components/AssetsList';
import { AssetDialog } from '@/components/Dialogs/Assets';

const OBJECTS_PER_REQ = 50;

const ASSET_CATEGORIES: { label: string; value: AssetCategory }[] = [
{
label: 'Visual',
Expand All @@ -27,34 +25,46 @@ const ASSET_CATEGORIES: { label: string; value: AssetCategory }[] = [

export default function AssetsDashboardPage(): React.JSX.Element {
const [selectedAsset, setSelectedAsset] = useState<IotaObjectData | null>(null);
const [selectedCategory, setSelectedCategory] = useState<AssetCategory>(AssetCategory.Visual);
const [selectedCategory, setSelectedCategory] = useState<AssetCategory | null>(null);
const account = useCurrentAccount();
const { data, isFetching, fetchNextPage, hasNextPage, refetch } = useGetOwnedObjects(
account?.address,
undefined,
OBJECTS_PER_REQ,
);
const {
data: ownedAssets,
isFetching,
fetchNextPage,
hasNextPage,
refetch,
} = useGetNFTs(account?.address);

const assets: IotaObjectData[] = [];
const assets: IotaObjectData[] = (() => {
if (selectedCategory === null) return [] as IotaObjectData[];

for (const page of data?.pages || []) {
for (const asset of page.data) {
if (asset.data && asset.data.objectId) {
if (selectedCategory == AssetCategory.Visual) {
if (hasDisplayData(asset)) {
assets.push(asset.data);
}
} else if (selectedCategory == AssetCategory.Other) {
assets.push(asset.data);
}
const assetsList = ownedAssets ? ownedAssets[selectedCategory] : [];
return assetsList.filter((asset) => {
if (selectedCategory === AssetCategory.Visual) {
return hasDisplayData({ data: asset });
}
}
}
return true;
});
})();

function onAssetClick(asset: IotaObjectData) {
setSelectedAsset(asset);
}

useEffect(() => {
if (!ownedAssets || selectedCategory !== null) {
return;
}

const defaultCategory =
ownedAssets.visual.length > 0
? AssetCategory.Visual
: ownedAssets.other.length > 0
? AssetCategory.Other
: AssetCategory.Visual;
setSelectedCategory(defaultCategory);
}, [ownedAssets, selectedCategory]);
panteleymonchuk marked this conversation as resolved.
Show resolved Hide resolved

return (
<Panel>
<Title title="Assets" size={TitleSize.Medium} />
Expand All @@ -69,15 +79,16 @@ export default function AssetsDashboardPage(): React.JSX.Element {
/>
))}
</div>

<AssetList
assets={assets}
selectedCategory={selectedCategory}
onClick={onAssetClick}
hasNextPage={hasNextPage}
isFetchingNextPage={isFetching}
fetchNextPage={fetchNextPage}
/>
{selectedCategory && (
<AssetList
assets={assets}
selectedCategory={selectedCategory}
onClick={onAssetClick}
hasNextPage={hasNextPage}
isFetchingNextPage={isFetching}
fetchNextPage={fetchNextPage}
/>
)}
{selectedAsset && (
<AssetDialog
onClose={() => setSelectedAsset(null)}
Expand Down
4 changes: 2 additions & 2 deletions apps/wallet-dashboard/lib/enums/assetCategory.enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
// SPDX-License-Identifier: Apache-2.0

export enum AssetCategory {
Visual = 'Visual',
Other = 'Other',
Visual = 'visual',
Other = 'other',
}
Loading
Loading