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: support NFTs and Uniques #1564

Merged
merged 105 commits into from
Nov 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
105 commits
Select commit Hold shift + click to select a range
4ec661f
refactor: refactor theme type for backgroundFL
AMIRKHANEF Sep 24, 2024
5df1994
chore: add directory path
AMIRKHANEF Sep 24, 2024
1617b4f
chore: add NFT svg icon
AMIRKHANEF Sep 24, 2024
21ed1c4
chore: add NFT button
AMIRKHANEF Sep 24, 2024
f69f368
chore: add NFT supported chains
AMIRKHANEF Sep 24, 2024
0e64ac0
fix: settings button on full screen onClick issue
AMIRKHANEF Sep 24, 2024
6a299d0
chore: create constants.ts for NFT
AMIRKHANEF Sep 24, 2024
180c62b
chore: create useNftInfo.ts
AMIRKHANEF Sep 24, 2024
841b1c1
chore: create NFTItem.tsx
AMIRKHANEF Sep 24, 2024
88253d4
chore: create nftFullScreenModal.tsx
AMIRKHANEF Sep 24, 2024
5b68689
chore: nftDetails.tsx
AMIRKHANEF Sep 24, 2024
fa1e910
chore: create nftAvatar.tsx
AMIRKHANEF Sep 24, 2024
7af97d3
chore: create index.tsx for NFT
AMIRKHANEF Sep 24, 2024
a390168
refactor: update files naming
AMIRKHANEF Sep 24, 2024
f88e6c6
chore: support uniques and change components names
AMIRKHANEF Sep 25, 2024
989cae8
refactor: organize components in folders, update filter section
AMIRKHANEF Sep 25, 2024
f62a531
feat: add disabled to the InputFilter.tsx component
AMIRKHANEF Sep 25, 2024
9a8af75
chore: update filter section
AMIRKHANEF Sep 25, 2024
ee37e38
refactor: optimize item data fetch manner
AMIRKHANEF Sep 28, 2024
b3fafeb
Merge branch 'NFT' of https://github.com/AMIRKHANEF/polkagate-extensi…
AMIRKHANEF Sep 28, 2024
20d44d3
chore: add translation
AMIRKHANEF Sep 28, 2024
ffeea18
refactor: move nft button from account settings to most common tasks
AMIRKHANEF Sep 28, 2024
a8f5bf2
chore: add NFT_CHAINS to FullScreenHeader chain switcher
AMIRKHANEF Sep 28, 2024
659edbe
Merge branch 'NFT' of https://github.com/AMIRKHANEF/polkagate-extensi…
AMIRKHANEF Sep 30, 2024
bb0a43e
feat: add new skeleton item
AMIRKHANEF Sep 30, 2024
7530541
fix: items list height
AMIRKHANEF Sep 30, 2024
b7c71ea
fix: remove back button
AMIRKHANEF Sep 30, 2024
0086c58
refactor: update NFT details and full screen
AMIRKHANEF Sep 30, 2024
93dbefb
chore: add some other properties
AMIRKHANEF Sep 30, 2024
8622ef3
chore: add more details to NFT details popup
AMIRKHANEF Oct 1, 2024
c586082
refactor: add tabs and disaply more information on the Details page
AMIRKHANEF Oct 2, 2024
9efe01e
chore: display animation if available
AMIRKHANEF Oct 5, 2024
7abb169
feat: play audio NFTs
AMIRKHANEF Oct 6, 2024
4239030
Merge branch 'NFT' of https://github.com/AMIRKHANEF/polkagate-extensi…
AMIRKHANEF Oct 6, 2024
afbab6e
Merge branch 'PolkaGate:main' into NFT
AMIRKHANEF Oct 6, 2024
0987e56
fix: enable NFTs for all chains, sanitized button name
AMIRKHANEF Oct 6, 2024
28581c9
Merge branch 'NFT' of https://github.com/AMIRKHANEF/polkagate-extensi…
AMIRKHANEF Oct 7, 2024
5bbcad3
feat: use worker to get all nft items on all chains
AMIRKHANEF Oct 7, 2024
27698fc
refactor: some minor changes in components
AMIRKHANEF Oct 7, 2024
f469f34
fix: amend
AMIRKHANEF Oct 7, 2024
d1b9d9d
chore: some small changes in ui
AMIRKHANEF Oct 7, 2024
dac6622
refactor: remove unused and handle spacing
AMIRKHANEF Oct 7, 2024
55d0b52
refactor: small changes
AMIRKHANEF Oct 7, 2024
d7ac69a
refactor: some small ui changes, and fix nft items filtering issue
AMIRKHANEF Oct 8, 2024
6924304
refactor: change the nft icon
AMIRKHANEF Oct 8, 2024
3940582
style: adjust font sizes plus some spacings
Nick-1979 Oct 8, 2024
be6b95e
Merge branch 'NFT' of https://github.com/AMIRKHANEF/polkagate-extensi…
Nick-1979 Oct 8, 2024
c0b3af2
fix: image fit
Nick-1979 Oct 8, 2024
6a06339
fix: api chain issue
Nick-1979 Oct 8, 2024
7225c55
fix: display short address till the identity fetch
AMIRKHANEF Oct 9, 2024
b1b7631
fix: fix nft items size issue
AMIRKHANEF Oct 13, 2024
b058a5f
feat: display unique items gif
AMIRKHANEF Oct 13, 2024
315013b
chore: fix CI workflow failure
Nick-1979 Oct 13, 2024
386ae68
chore: update styles
Nick-1979 Oct 13, 2024
dddda02
Merge branch 'main' into pr/1564
Nick-1979 Oct 13, 2024
7706c88
chore: merge main
Nick-1979 Oct 13, 2024
a3ed1f2
style: add tab divider
Nick-1979 Oct 13, 2024
64e776c
chore: update text
Nick-1979 Oct 13, 2024
1b95720
refactor: rename function to differ from worker name
Nick-1979 Oct 13, 2024
6807f54
refactor: to remove unnecessary code
Nick-1979 Oct 14, 2024
a61c1de
fix: update saved data type, and some minor changes
AMIRKHANEF Oct 14, 2024
14d482e
Merge branch 'NFT' of https://github.com/AMIRKHANEF/polkagate-extensi…
AMIRKHANEF Oct 14, 2024
2aa10fb
refactor: add type
Nick-1979 Oct 14, 2024
006b4e3
fix: update fetch policy
AMIRKHANEF Oct 14, 2024
8cb1758
style: adjust font size in thumbnail
Nick-1979 Oct 14, 2024
5189f87
style: make thumbnails center
Nick-1979 Oct 14, 2024
cdc00bc
refactor: rename item to Thumbnail
Nick-1979 Oct 14, 2024
a555252
refactor: rename for readability
Nick-1979 Oct 14, 2024
a12c5b1
refactor: add buttons plus a rename
Nick-1979 Oct 14, 2024
754c497
style: adjust spacings
Nick-1979 Oct 14, 2024
39b0588
feat: display nfts on the Home page for accounts
AMIRKHANEF Oct 15, 2024
579ddaa
fix: fix decimal issue for prices and types
AMIRKHANEF Oct 15, 2024
042338b
style: add wordpress loading
Nick-1979 Oct 15, 2024
f929eff
chore: add tooltip
Nick-1979 Oct 15, 2024
af2ff72
fix: make tootltip singular if there is only one
Nick-1979 Oct 15, 2024
3579473
fix: adjust progress type and position
Nick-1979 Oct 15, 2024
3194343
Merge branch 'main' into pr/1564
Nick-1979 Oct 16, 2024
57c5cbb
Merge branch 'main' into pr/1564
Nick-1979 Oct 16, 2024
88d4434
style: add elevation effect on hover
Nick-1979 Oct 17, 2024
0de4df2
refactor: add NFT_URL_ON_KODADOT
Nick-1979 Oct 17, 2024
2b578f0
refactor: major refactor
AMIRKHANEF Oct 21, 2024
810d20a
refactor: minor refactor
AMIRKHANEF Oct 22, 2024
ab3d12d
fix: fix itemsDetails id issue
AMIRKHANEF Oct 22, 2024
cd7a63a
fix: cold start issue
AMIRKHANEF Oct 22, 2024
983506d
Merge branch 'NFT' of https://github.com/AMIRKHANEF/polkagate-extensi…
AMIRKHANEF Oct 26, 2024
0e43019
fix: fix translation issue
AMIRKHANEF Oct 26, 2024
e330aea
feat: create a new class for managing chrome storage for nft items
AMIRKHANEF Oct 26, 2024
2bd1f23
refactor: remove NftItem context
AMIRKHANEF Oct 26, 2024
dc0fbd2
refactor: use class manager instead of context
AMIRKHANEF Oct 26, 2024
b34be93
fix: manor fixes
AMIRKHANEF Oct 26, 2024
0ea2430
fix: manor issue
AMIRKHANEF Oct 28, 2024
47462ec
refactor: update NftManager logic for cold start
AMIRKHANEF Oct 28, 2024
0013459
feat: separate InfoRow into a individual file
AMIRKHANEF Oct 28, 2024
95d3ca5
feat: get and display collection name
AMIRKHANEF Oct 28, 2024
a518f08
Merge branch 'NFT' of https://github.com/AMIRKHANEF/polkagate-extensi…
AMIRKHANEF Nov 3, 2024
1b61eee
chore: remove logs
AMIRKHANEF Nov 3, 2024
62aad98
refactor: move class creation inside the component
AMIRKHANEF Nov 3, 2024
0cd8f5a
chore: add more http status code
AMIRKHANEF Nov 3, 2024
02391af
chore: add nft url
AMIRKHANEF Nov 3, 2024
2eee8a6
Merge branch 'main' into pr/1564
Nick-1979 Nov 8, 2024
473426e
refactor: using fontawsome icon name as a prop
Nick-1979 Nov 9, 2024
649e730
style: set min list height
AMIRKHANEF Nov 9, 2024
c0a3e5c
feat: add modal backdrop as blur
Nick-1979 Nov 9, 2024
cc0ecd7
refactor: rename and adjust color
Nick-1979 Nov 9, 2024
6d55648
style: reorder items to show
Nick-1979 Nov 9, 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 .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"Ekbatanifard",
"iadd",
"Infotip",
"IPFS",
"judgements",
"Kusama",
"Polkadot",
Expand Down
1 change: 1 addition & 0 deletions packages/extension-base/src/defaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const START_WITH_PATH = [
'/send/',
'/stake/',
'/socialRecovery/',
'/nft/',
'/derivefs/'
] as const;

Expand Down
205 changes: 205 additions & 0 deletions packages/extension-polkagate/src/class/nftManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
// Copyright 2019-2024 @polkadot/extension-polkagate authors & contributors
// SPDX-License-Identifier: Apache-2.0

import type { ItemInformation, ItemMetadata, ItemOnChainInfo } from '../fullscreen/nft/utils/types';
import type { NftItemsType } from '../util/types';

// Define types for listener functions
type Listener = (address: string, nftItemsInformation: ItemInformation[]) => void;
type InitializationListener = () => void;

// Error class for NFT-specific errors
class NftManagerError extends Error {
constructor (message: string) {
super(message);
this.name = 'NftManagerError';
}
}

export default class NftManager {
// Store nft items and listeners
private nfts: NftItemsType = {};
private listeners = new Set<Listener>();
private initializationListeners = new Set<InitializationListener>();
private readonly STORAGE_KEY = 'nftItems';
private isInitialized = false;
private initializationPromise: Promise<void>;

constructor () {
// Load nft items from storage and set up storage change listener
this.initializationPromise = this.loadFromStorage();
chrome.storage.onChanged.addListener(this.handleStorageChange);
}

// Wait for initialization to complete
public async waitForInitialization (): Promise<void> {
return this.initializationPromise;
}

// Notify all listeners about initialization
private notifyInitializationListeners (): void {
this.initializationListeners.forEach((listener) => {
try {
listener();
} catch (error) {
console.error('Error in initialization listener:', error);
}
});
this.initializationListeners.clear();
}

// Load nft items from chrome storage
private async loadFromStorage (): Promise<void> {
try {
const result = await chrome.storage.local.get(this.STORAGE_KEY);

this.nfts = result[this.STORAGE_KEY] as NftItemsType || {};
this.isInitialized = true;

this.notifyInitializationListeners();
this.notifyListeners();
} catch (error) {
console.error('Failed to load NFT items from storage:', error);
throw new NftManagerError('Failed to load NFT items from storage');
}
}

// Save nft items to chrome storage with debouncing
private saveToStorage = (() => {
let timeoutId: ReturnType<typeof setTimeout> | null = null;

return () => {
if (timeoutId) {
clearTimeout(timeoutId);
}

// eslint-disable-next-line @typescript-eslint/no-misused-promises
timeoutId = setTimeout(async () => {
try {
await chrome.storage.local.set({ [this.STORAGE_KEY]: this.nfts });
} catch (error) {
console.error('Failed to save NFT items to storage:', error);
throw new NftManagerError('Failed to save NFT items to storage');
}
}, 1000); // Debounce for 1 second
};
})();

// Handle changes in chrome storage
private handleStorageChange = (changes: Record<string, chrome.storage.StorageChange>, areaName: string) => {
if (areaName === 'local' && changes[this.STORAGE_KEY]) {
this.nfts = changes[this.STORAGE_KEY].newValue as NftItemsType;
this.notifyListeners();
}
};

// Notify all listeners about nfts items changes
private notifyListeners (): void {
if (!this.isInitialized) {
return;
}

Object.entries(this.nfts).forEach(([address, nftItemsInformation]) => {
this.listeners.forEach((listener) => {
try {
listener(address, nftItemsInformation);
} catch (error) {
console.error('Error in listener:', error);
}
});
});
}

// Get nft items for a specific
get (address: string): ItemInformation[] | null | undefined {
if (!address) {
throw new NftManagerError('Address is required');
}

return address in this.nfts && this.nfts[address].length === 0
? null
: this.nfts?.[address];
}

// Get all nft items
getAll (): NftItemsType | null | undefined {
return this.nfts;
}

// Set on-chain nft item for a specific address
setOnChainItemsInfo (data: NftItemsType) {
if (!data) {
throw new NftManagerError('NFT items information are required to set on-chain information');
}

for (const address in data) {
if (!this.nfts[address]) {
this.nfts[address] = [];
}

const nftItemsInfo = data[address];

const existingItems = new Set(
this.nfts[address].map((item) => this.getItemKey(item))
);

const newItems = nftItemsInfo.filter(
(item) => !existingItems.has(this.getItemKey(item))
);

if (newItems.length > 0) {
this.nfts[address].push(...newItems);
this.saveToStorage();
this.notifyListeners();
}
}
}

private getItemKey (item: ItemOnChainInfo): string {
return `${item.chainName}-${item.collectionId}-${item.itemId}-${item.isNft}`;
}

// Set nft item detail for a specific address and item
setItemDetail (address: string, nftItemInfo: ItemInformation, nftItemDetail: ItemMetadata | null) {
if (!address || !nftItemInfo || nftItemDetail === undefined) {
throw new NftManagerError('Address, NFT item info, and detail are required');
}

if (!this.nfts[address]) {
return;
}

const itemIndex = this.nfts[address].findIndex(
(item) => this.getItemKey(item) === this.getItemKey(nftItemInfo)
);

if (itemIndex === -1) {
return;
}

this.nfts[address][itemIndex] = {
...this.nfts[address][itemIndex],
...(nftItemDetail ?? { noData: true })
};

this.saveToStorage();
this.notifyListeners();
}

// Subscribe a listener to endpoint changes
subscribe (listener: Listener) {
this.listeners.add(listener);
}

// Unsubscribe a listener from endpoint changes
unsubscribe (listener: Listener) {
this.listeners.delete(listener);
}

// Cleanup method to remove listeners and clear data
public destroy (): void {
chrome.storage.onChanged.removeListener(this.handleStorageChange);
this.listeners.clear();
this.nfts = {};
}
}
4 changes: 3 additions & 1 deletion packages/extension-polkagate/src/components/InputFilter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,11 @@ interface Props {
placeholder: string;
value?: string;
withReset?: boolean;
disabled?: boolean;
theme: Theme;
}

export default function InputFilter ({ autoFocus = true, label, onChange, placeholder, theme, value, withReset = false }: Props) {
export default function InputFilter ({ autoFocus = true, disabled, label, onChange, placeholder, theme, value, withReset = false }: Props) {
const inputRef: React.RefObject<HTMLInputElement> | null = useRef(null);

const onChangeFilter = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
Expand All @@ -41,6 +42,7 @@ export default function InputFilter ({ autoFocus = true, label, onChange, placeh
autoCapitalize='off'
autoCorrect='off'
autoFocus={autoFocus}
disabled={disabled}
onChange={onChangeFilter}
placeholder={placeholder}
ref={inputRef}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,8 @@ function AOC ({ accountAssets, address, hideNumbers, mode = 'Detail', onclick, s
}
}, [accountAssets]);

const shouldShowCursor = useMemo(() => (mode === 'Detail' && accountAssets && accountAssets.length > 5) || (mode !== 'Detail' && accountAssets && accountAssets.length > 6), [accountAssets, mode]);

return (
<Grid container item>
<Typography fontSize='18px' fontWeight={400} mt='13px' px='10px' width='fit-content'>
Expand All @@ -159,7 +161,7 @@ function AOC ({ accountAssets, address, hideNumbers, mode = 'Detail', onclick, s
</Collapse>
</Grid>
{!!accountAssets?.length &&
<Grid alignItems='center' container item justifyContent='center' onClick={toggleAssets} sx={{ cursor: 'pointer', width: '65px' }}>
<Grid alignItems='center' container item justifyContent='center' onClick={toggleAssets} sx={{ cursor: shouldShowCursor ? 'pointer' : 'default', width: '65px' }}>
{mode === 'Detail'
? accountAssets.length > 5 &&
<>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ export default function AccountSetting ({ address, setDisplayPopup }: Props): Re
/>
<TaskButton
disabled={proxyDisable}
icon={<VaadinIcon icon='vaadin:sitemap' style={{ height: '30px', color: `${proxyDisable ? theme.palette.text.disabled : theme.palette.text.primary}` }} />}
icon={<VaadinIcon icon='vaadin:sitemap' style={{ color: `${proxyDisable ? theme.palette.text.disabled : theme.palette.text.primary}`, height: '30px' }} />}
onClick={onManageProxies}
secondaryIconType='page'
text={t('Manage proxies')}
Expand All @@ -116,26 +116,26 @@ export default function AccountSetting ({ address, setDisplayPopup }: Props): Re
/>
<TaskButton
disabled={hardwareOrExternalAccount}
icon={<VaadinIcon icon='vaadin:download-alt' style={{ height: '30px', color: `${hardwareOrExternalAccount ? theme.palette.text.disabled : theme.palette.text.primary}` }} />}
icon={<VaadinIcon icon='vaadin:download-alt' style={{ color: `${hardwareOrExternalAccount ? theme.palette.text.disabled : theme.palette.text.primary}`, height: '30px' }} />}
onClick={onExportAccount}
secondaryIconType='popup'
text={t('Export account')}
/>
<TaskButton
disabled={hardwareOrExternalAccount}
icon={<VaadinIcon icon='vaadin:road-branch' style={{ height: '30px', color: `${hardwareOrExternalAccount ? theme.palette.text.disabled : theme.palette.text.primary}` }} />}
icon={<VaadinIcon icon='vaadin:road-branch' style={{ color: `${hardwareOrExternalAccount ? theme.palette.text.disabled : theme.palette.text.primary}`, height: '30px' }} />}
onClick={goToDeriveAcc}
secondaryIconType='popup'
text={t('Derive new account')}
/>
<TaskButton
icon={<VaadinIcon icon='vaadin:edit' style={{ height: '30px', color: `${theme.palette.text.primary}` }} />}
icon={<VaadinIcon icon='vaadin:edit' style={{ color: `${theme.palette.text.primary}`, height: '30px' }} />}
onClick={onRenameAccount}
secondaryIconType='popup'
text={t('Rename')}
/>
<TaskButton
icon={<VaadinIcon icon='vaadin:file-remove' style={{ height: '30px', color: `${theme.palette.text.primary}` }} />}
icon={<VaadinIcon icon='vaadin:file-remove' style={{ color: `${theme.palette.text.primary}`, height: '30px' }} />}
noBorderButton
onClick={onForgetAccount}
secondaryIconType='popup'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import type { BalancesInfo } from 'extension-polkagate/src/util/types';
import type { FetchedBalance } from '../../../hooks/useAssetsBalances';

import { faCoins, faHistory, faPaperPlane, faVoteYea } from '@fortawesome/free-solid-svg-icons';
import { faCoins, faGem, faHistory, faPaperPlane, faVoteYea } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { ArrowForwardIosRounded as ArrowForwardIosRoundedIcon, Boy as BoyIcon, QrCode2 as QrCodeIcon } from '@mui/icons-material';
import { Divider, Grid, Typography, useTheme } from '@mui/material';
Expand Down Expand Up @@ -55,7 +55,6 @@ export const openOrFocusTab = (relativeUrl: string, closeCurrentTab?: boolean):
return tab.url === tabUrl;
});


if (existingTab?.id) {
chrome.tabs.update(existingTab.id, { active: true }).catch(console.error);
} else {
Expand Down Expand Up @@ -161,6 +160,10 @@ export default function CommonTasks ({ address, assetId, balance, genesisHash, s
address && !stakingDisabled && openOrFocusTab(`/poolfs/${address}/`);
}, [address, stakingDisabled]);

const onNFTAlbum = useCallback(() => {
address && openOrFocusTab(`/nft/${address}`);
}, [address]);

const goToHistory = useCallback(() => {
address && genesisHash && setDisplayPopup(popupNumbers.HISTORY);
}, [address, genesisHash, setDisplayPopup]);
Expand Down Expand Up @@ -252,6 +255,19 @@ export default function CommonTasks ({ address, assetId, balance, genesisHash, s
show={(hasSoloStake || hasPoolStake) && !stakingDisabled}
text={t('Stake in Pool')}
/>
<TaskButton
disabled={false} // We check NFTs across all supported chains, so this feature is not specific to the current chain and should not be disabled.
icon={
<FontAwesomeIcon
color={theme.palette.text.primary}
fontSize='28px'
icon={faGem}
/>
}
onClick={onNFTAlbum}
secondaryIconType='page'
text={t('NFT album')}
/>
<TaskButton
disabled={!genesisHash}
icon={
Expand Down
Loading
Loading