Skip to content

Commit

Permalink
Merge branch 'develop' into dev-tools/de-deprecate-clap
Browse files Browse the repository at this point in the history
  • Loading branch information
DaughterOfMars committed Jan 15, 2025
2 parents 405372b + e44ad9a commit 45eed06
Show file tree
Hide file tree
Showing 199 changed files with 1,868 additions and 1,269 deletions.
21 changes: 21 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

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
7 changes: 5 additions & 2 deletions apps/core/src/components/transaction/TransactionReceipt.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0

import { InfoBox, InfoBoxStyle, InfoBoxType } from '@iota/apps-ui-kit';
import { formatDate, type useTransactionSummary } from '../../hooks';
import type { useTransactionSummary } from '../../hooks';
import { CheckmarkFilled } from '@iota/ui-icons';
import { IotaTransactionBlockResponse } from '@iota/iota-sdk/client';
import { STAKING_REQUEST_EVENT, UNSTAKING_REQUEST_EVENT } from '../../constants';
Expand All @@ -11,6 +11,7 @@ import { UnstakeTransactionInfo } from './info';
import { TransactionSummary } from './summary';
import { RenderExplorerLink, RenderValidatorLogo } from '../../types';
import { GasFees } from '../gas';
import { formatDate } from '../../utils';

interface TransactionReceiptProps {
txn: IotaTransactionBlockResponse;
Expand Down Expand Up @@ -86,7 +87,9 @@ interface TransactionStatusProps {
}

function TransactionStatus({ success, timestamp, isIncoming }: TransactionStatusProps) {
const txnDate = timestamp ? formatDate(Number(timestamp)) : '';
const txnDate = timestamp
? formatDate(Number(timestamp), ['day', 'month', 'year', 'hour', 'minute'])
: '';
const successMessage = isIncoming ? 'Successfully received' : 'Successfully sent';
return (
<InfoBox
Expand Down
112 changes: 112 additions & 0 deletions apps/core/src/contexts/HiddenAssetsProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
// Copyright (c) Mysten Labs, Inc.
// Modifications Copyright (c) 2024 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

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

const HIDDEN_ASSET_IDS = 'hidden-asset-ids';

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

interface HiddenAssetContext {
hiddenAssets: HiddenAssets;
hideAsset: (assetId: string) => string | void;
showAsset: (assetId: string) => string | void;
}

export const HiddenAssetsContext = createContext<HiddenAssetContext>({
hiddenAssets: {
type: 'loading',
},
hideAsset: () => {},
showAsset: () => {},
});

export const HiddenAssetsProvider = ({ children }: PropsWithChildren) => {
const [hiddenAssets, setHiddenAssets] = useState<HiddenAssets>({
type: 'loading',
});
const hiddenAssetIdsRef = useRef<string[]>([]);

useEffect(() => {
(async () => {
try {
const hiddenAssetsFromStorage = (await get<string[]>(HIDDEN_ASSET_IDS)) ?? [];
hiddenAssetIdsRef.current = hiddenAssetsFromStorage;
setHiddenAssetIds(hiddenAssetsFromStorage);
} catch (error) {
console.error('Failed to load hidden assets from storage:', error);
setHiddenAssetIds([]);
}
})();
}, []);

function setHiddenAssetIds(hiddenAssetIds: string[]) {
hiddenAssetIdsRef.current = hiddenAssetIds;
setHiddenAssets({
type: 'loaded',
assetIds: hiddenAssetIds,
});
}

const syncIdb = useCallback(async (nextState: string[], prevState: string[]) => {
try {
await set(HIDDEN_ASSET_IDS, nextState);
} catch (error) {
console.error('Error syncing with IndexedDB:', error);
// Revert to the previous state on failure
setHiddenAssetIds(prevState);
}
}, []);

const hideAsset = useCallback((assetId: string) => {
const prevIds = [...hiddenAssetIdsRef.current];
const newHiddenAssetIds = Array.from(new Set([...hiddenAssetIdsRef.current, assetId]));
setHiddenAssetIds(newHiddenAssetIds);
syncIdb(newHiddenAssetIds, prevIds);
return assetId;
}, []);

const showAsset = useCallback((assetId: string) => {
// Ensure the asset exists in the hidden list
if (!hiddenAssetIdsRef.current.includes(assetId)) return;

const prevIds = [...hiddenAssetIdsRef.current];
// Compute the new list of hidden assets
const updatedHiddenAssetIds = hiddenAssetIdsRef.current.filter((id) => id !== assetId);
setHiddenAssetIds(updatedHiddenAssetIds);
syncIdb(updatedHiddenAssetIds, prevIds);
}, []);

return (
<HiddenAssetsContext.Provider
value={{
hiddenAssets,
hideAsset,
showAsset,
}}
>
{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';
4 changes: 3 additions & 1 deletion apps/core/src/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ export * from './useOwnedNFT';
export * from './useNftDetails';
export * from './useCountdownByTimestamp';
export * from './useStakeRewardStatus';
export * from './useGetNFTs';
export * from './useRecognizedPackages';

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

export * from './usePageAssets';
131 changes: 131 additions & 0 deletions apps/core/src/hooks/ui/usePageAssets.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
// Copyright (c) 2024 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0
import { useState, useMemo, useRef, useEffect } from 'react';
import { useGetNFTs, HiddenAssets, useOnScreen } from '../..';

export enum AssetCategory {
Visual = 'Visual',
Other = 'Other',
Hidden = 'Hidden',
}

export function usePageAssets(address: string | null, hiddenAssets?: HiddenAssets) {
const [selectedAssetCategory, setSelectedAssetCategory] = useState<AssetCategory | null>(null);
const observerElem = useRef<HTMLDivElement | null>(null);
const { isIntersecting } = useOnScreen(observerElem);
const {
data: ownedAssets,
hasNextPage,
isFetchingNextPage,
fetchNextPage,
error,
isPending,
isError,
isFetching,
refetch,
} = useGetNFTs(address, hiddenAssets);

const isAssetsLoaded = !!ownedAssets;
const isSpinnerVisible = isFetchingNextPage && hasNextPage;

const filteredAssets = (() => {
if (!ownedAssets) return [];
switch (selectedAssetCategory) {
case AssetCategory.Visual:
return ownedAssets.visual;
case AssetCategory.Other:
return ownedAssets.other;
default:
return [];
}
})();

const filteredHiddenAssets = useMemo(() => {
return (
ownedAssets?.hidden
.flatMap((data) => {
return {
data: data,
display: data?.display?.data,
};
})
.sort((nftA, nftB) => {
const nameA = nftA.display?.name || '';
const nameB = nftB.display?.name || '';

if (nameA < nameB) {
return -1;
} else if (nameA > nameB) {
return 1;
}
return 0;
}) ?? []
);
}, [ownedAssets]);

// Fetch the next page if the user scrolls to the bottom of the page
useEffect(() => {
if (isIntersecting && hasNextPage && !isFetchingNextPage) {
fetchNextPage();
}
}, [isIntersecting, fetchNextPage, hasNextPage, isFetchingNextPage]);

// select the default category if no category is selected and assets are loaded
useEffect(() => {
let computeSelectedCategory = false;
if (
(selectedAssetCategory === AssetCategory.Visual && ownedAssets?.visual.length === 0) ||
(selectedAssetCategory === AssetCategory.Other && ownedAssets?.other.length === 0) ||
(selectedAssetCategory === AssetCategory.Hidden && ownedAssets?.hidden.length === 0) ||
!selectedAssetCategory
) {
computeSelectedCategory = true;
}
if (computeSelectedCategory && ownedAssets) {
const defaultCategory =
ownedAssets.visual.length > 0
? AssetCategory.Visual
: ownedAssets.other.length > 0
? AssetCategory.Other
: ownedAssets.hidden.length > 0
? AssetCategory.Hidden
: null;
setSelectedAssetCategory(defaultCategory);
}
}, [ownedAssets]);

// Fetch the next page if there are no visual assets, other + hidden assets are present in multiples of 50, and there are more pages to fetch
useEffect(() => {
if (
hasNextPage &&
ownedAssets?.visual.length === 0 &&
ownedAssets?.other.length + ownedAssets?.hidden.length > 0 &&
(ownedAssets.other.length + ownedAssets.hidden.length) % 50 === 0 &&
!isFetchingNextPage
) {
fetchNextPage();
setSelectedAssetCategory(null);
}
}, [hasNextPage, ownedAssets, isFetchingNextPage]);

return {
// reexport from useGetNFTs
ownedAssets,
hasNextPage,
isFetchingNextPage,
fetchNextPage,
error,
isPending,
isError,
isFetching,
refetch,

isAssetsLoaded,
filteredAssets,
filteredHiddenAssets,
selectedAssetCategory,
setSelectedAssetCategory,
observerElem,
isSpinnerVisible,
};
}
2 changes: 1 addition & 1 deletion apps/core/src/hooks/useFileExtensionType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ export const extractFileType = async (imgUrl: string) => {
});
};
*/
export default function useFileExtensionType(url: string) {
export function useFileExtensionType(url: string) {
if (!url) return { name: '', type: '' };
const fileType = url.split('.').pop() || '';
return FILE_EXTENSION_TYPE_MAP[fileType] || { name: '', type: '' };
Expand Down
Loading

0 comments on commit 45eed06

Please sign in to comment.