diff --git a/.changeset/wet-cities-bake.md b/.changeset/wet-cities-bake.md new file mode 100644 index 00000000..94a2bb4d --- /dev/null +++ b/.changeset/wet-cities-bake.md @@ -0,0 +1,5 @@ +--- +'@api3/logos': minor +--- + +Adds dApp support diff --git a/helpers/utils.js b/helpers/utils.js index 4eb4489d..f83dc7ca 100644 --- a/helpers/utils.js +++ b/helpers/utils.js @@ -1,6 +1,6 @@ const fs = require('fs/promises'); const { rename } = require('fs'); -const { getChains, dapis } = require('@api3/dapi-management'); +const { getChains, dapis, api3Contracts } = require('@api3/dapi-management'); module.exports = { sanitizeName, @@ -15,9 +15,15 @@ module.exports = { getSupportedChains, getApiProviders, getSupportedFeeds, - toPascalCase + getDapps, + toPascalCase, + isStringMatch }; +function getDapps() { + return api3Contracts.DAPPS.map((dapp) => dapp.alias); +} + function getApiProviders() { const providers = [...new Set(dapis.map((dapi) => dapi.providers).flat())]; const filteredApiProviders = providers.filter((api) => !api.match(/(.*)(-mock)/)); @@ -43,8 +49,10 @@ function getSupportList(mode) { return getSupportedFeeds(); case 'api-provider': return getApiProviders(); + case 'dapp': + return getDapps(); default: - break; + return []; } } @@ -56,8 +64,10 @@ function getManualLogos(mode) { return []; case 'api-provider': return []; + case 'dapp': + return []; default: - break; + return []; } } @@ -79,14 +89,24 @@ function sanitizeName(name, suffix = '', prefix = '') { return prefix + componentName + suffix; } -function generateSwitchCase(array, prefix) { +function isStringMatch(val1, val2) { + return sanitizeName(val1).toLowerCase() === sanitizeName(val2).toLowerCase(); +} + +function generateSwitchCase(files, array, prefix) { const sanitized = array.map((item) => { return sanitizeName(item, '', ''); }); + const file_prefix = prefix === 'Chain' ? 'Chain' : ''; + const filtered = [...new Set(sanitized)]; return filtered - .map((item) => `case "${sanitizeName(item).toLowerCase()}":\n\treturn ${prefix}${sanitizeName(item, '')};\n`) + .map((item) => { + let filename = checkFile(files, item, file_prefix); + if (item !== 'Placeholder' && filename === 'Placeholder.svg') return ''; + return `case "${sanitizeName(item).toLowerCase()}":\n\treturn ${prefix}${sanitizeName(item, '')};\n`; + }) .join(''); } @@ -111,6 +131,7 @@ function generateImports(files, array, prefix, file_prefix, path, format) { return filtered .map((item) => { let filename = checkFile(files, item, file_prefix); + if (item !== 'Placeholder' && filename === 'Placeholder.svg') return ''; return formatImport(item, filename, prefix, path, format); }) .join(''); @@ -192,7 +213,8 @@ async function copySvgFiles(files, logosDir, prefix = '') { files.forEach(async (file) => { const isSupported = supportList.some( - (item) => item.toLowerCase() === file.replace('.svg', '').replace('Chain', '').toLowerCase() + (item) => + sanitizeName(item).toLowerCase() === file.replace('.svg', '').replace('Chain', '').toLowerCase() ) || exceptions.some((item) => file.includes(item)); if (!isSupported) return; await fs.copyFile(`./optimized/${prefix}/${file}`, `${logosDir}/${file}`); diff --git a/package.json b/package.json index 6ac822a4..faaca4f0 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,8 @@ "optimize-chain-logos": "rimraf ./optimized/chain & svgo -q -p 8 -f ./raw/chains -o ./optimized/chain", "optimize-symbol-logos": "rimraf ./optimized/symbol & svgo -q -p 8 -f ./raw/symbols -o ./optimized/symbol", "optimize-api-provider-logos": "rimraf ./optimized/api-provider & svgo -q -p 8 -f ./raw/api-providers -o ./optimized/api-provider", - "build": "pnpm run optimize-chain-logos && pnpm run optimize-symbol-logos && pnpm run optimize-api-provider-logos && node scripts/build-svg.js", + "optimize-dapp-logos": "rimraf ./optimized/dapp & svgo -q -p 8 -f ./raw/dapps -o ./optimized/dapp", + "build": "pnpm run optimize-chain-logos && pnpm run optimize-symbol-logos && pnpm run optimize-api-provider-logos && pnpm run optimize-dapp-logos && node scripts/build-svg.js", "fetch": "node scripts/fetch-missing.js", "version-check": "node scripts/version-check.js", "package": "pnpm run build", diff --git a/raw/dapps/Placeholder.svg b/raw/dapps/Placeholder.svg new file mode 100644 index 00000000..5ad200b3 --- /dev/null +++ b/raw/dapps/Placeholder.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/scripts/build-svg.js b/scripts/build-svg.js index 13166a09..cc0c142c 100644 --- a/scripts/build-svg.js +++ b/scripts/build-svg.js @@ -5,11 +5,12 @@ const utils = require('../helpers/utils'); const outputPath = './dist'; -const categories = ['chain', 'symbol', 'api-provider']; +const categories = ['chain', 'symbol', 'api-provider', 'dapp']; let chainLightLogos = []; let apiProviderLightLogos = []; let symbolLightLogos = []; +let dappsLightLogos = []; function getLogoList(mode) { switch (mode) { @@ -19,8 +20,10 @@ function getLogoList(mode) { return [...symbolLightLogos, ...utils.getManualLogos(mode), ...utils.getSupportedFeeds()]; case 'api-provider': return [...apiProviderLightLogos, ...utils.getManualLogos(mode), ...utils.getApiProviders()]; + case 'dapp': + return [...dappsLightLogos, ...utils.getManualLogos(mode), ...utils.getDapps()]; default: - break; + return []; } } @@ -41,8 +44,8 @@ async function buildLogos(format = 'esm', dir, mode, batchName) { await fs.appendFile(`${outDir}/${batchName}Missing.json`, JSON.stringify(getMissingLogos(files, mode)), 'utf-8'); } -function buildSwitchCase(mode) { - return utils.generateSwitchCase(getLogoList(mode), utils.toPascalCase(mode)); +function buildSwitchCase(files, mode) { + return utils.generateSwitchCase(files, getLogoList(mode), utils.toPascalCase(mode)); } function buildLogoImports(files, mode, format) { @@ -69,7 +72,7 @@ async function buildBatch(files, outDir, format = 'esm', batchName, mode) { const imports = buildLogoImports(files, mode, format); - let code = await babelTransform(format, imports, batchName, mode); + let code = await babelTransform(files, format, imports, batchName, mode); if (format === 'cjs') { code = code.replace('export default', 'module.exports ='); @@ -78,11 +81,11 @@ async function buildBatch(files, outDir, format = 'esm', batchName, mode) { await fs.writeFile(`${outDir}/${batchName}.js`, code, 'utf-8'); } -async function babelTransform(format, imports, batchName, mode) { +async function babelTransform(files, format, imports, batchName, mode) { let { code } = await babel.transformAsync( ` ${imports} - ${utils.generateFunction(batchName, buildSwitchCase(mode), mode)}`, + ${utils.generateFunction(batchName, buildSwitchCase(files, mode), mode)}`, { presets: [['@babel/preset-react', { useBuiltIns: true }]] } @@ -111,7 +114,8 @@ async function findLightLogos() { const [chainFiles, apiProviderFiles, symbolFiles] = await Promise.all([ fs.readdir('./optimized/chain', 'utf-8'), fs.readdir('./optimized/api-provider', 'utf-8'), - fs.readdir('./optimized/symbol', 'utf-8') + fs.readdir('./optimized/symbol', 'utf-8'), + fs.readdir('./optimized/dapp', 'utf-8') ]); chainLightLogos = chainFiles.filter((file) => file.includes('light')).map((file) => file.replace('Chain', '')); diff --git a/scripts/fetch-missing.js b/scripts/fetch-missing.js index 5e0c2339..a225ce95 100644 --- a/scripts/fetch-missing.js +++ b/scripts/fetch-missing.js @@ -7,7 +7,7 @@ const fetch = require('node-fetch'); let missingLogos = []; -const categories = ['chain', 'symbol', 'api-provider']; +const categories = ['chain', 'symbol', 'api-provider', 'dapp']; let dbx = null; @@ -41,6 +41,8 @@ function getLogoList(mode) { return [...utils.getManualLogos(mode), ...utils.getSupportedFeeds()]; case 'api-provider': return [...utils.getManualLogos(mode), ...utils.getApiProviders()]; + case 'dapp': + return [...utils.getManualLogos(mode), ...utils.getDapps()]; default: break; } @@ -75,10 +77,8 @@ async function searchLogos() { missingLogos.map((missingLogoCategory) => { missingLogoCategory.logos.map((missingLogo) => { foundLogos.map((foundLogo) => { - if ( - utils.sanitizeName(foundLogo.name).toLowerCase() === - `${utils.sanitizeName(missingLogo).toLowerCase()}` - ) { + const isCategoryMatch = foundLogo.path_lower.includes(missingLogoCategory.category); + if (utils.isStringMatch(foundLogo.name, missingLogo) && isCategoryMatch) { downloadLogos(missingLogoCategory.category, foundLogo); } }); @@ -93,7 +93,7 @@ async function searchLogos() { async function fetchLogos() { const dbx = await getDropbox(); try { - const response = await dbx.filesListFolder({ path: '', recursive: true }); + const response = await dbx.filesListFolder({ path: '', recursive: true, limit: 1000 }); return response.result.entries; } catch (error) { console.error(error); diff --git a/scripts/version-check.js b/scripts/version-check.js index 1dec7886..3f616b68 100644 --- a/scripts/version-check.js +++ b/scripts/version-check.js @@ -5,7 +5,7 @@ const dropbox = require('dropbox'); const fetch = require('node-fetch'); const crypto = require('crypto'); -const categories = ['chain', 'symbol', 'api-provider']; +const categories = ['chain', 'symbol', 'api-provider', 'dapp']; let dbx = null; @@ -39,6 +39,8 @@ function getLogoList(mode) { return [...utils.getManualLogos(mode), ...utils.getSupportedFeeds()]; case 'api-provider': return [...utils.getManualLogos(mode), ...utils.getApiProviders()]; + case 'dapp': + return [...utils.getManualLogos(mode), ...utils.getDapps()]; default: break; } diff --git a/viewer/src/Components/ChainsView.js b/viewer/src/Components/ChainsView.js index c6d4171f..1816031c 100644 --- a/viewer/src/Components/ChainsView.js +++ b/viewer/src/Components/ChainsView.js @@ -6,17 +6,20 @@ import { useState } from 'react'; import Title from '../Custom/Title'; import InfoView from '../Custom/InfoView'; -const getSupportedChains = (isTestnet) => { +const getSupportedChains = (isTestnet, searchArg) => { const supportedChainIds = getChains() .filter((chain) => chain.stage !== 'retired') + .filter((chain) => + searchArg ? chain.alias.includes(searchArg.toLowerCase()) || chain.id.includes(searchArg) : true + ) .map((chain) => chain.id); return api3Contracts.CHAINS.filter((chain) => supportedChainIds.includes(chain.id) && chain.testnet === isTestnet); }; -const ChainList = ({ isTestnet, chain }) => { +const ChainList = ({ isTestnet, searchArg }) => { const [selectedChain, setSelectedChain] = useState(''); - return getSupportedChains(isTestnet).map((chain, index) => { + return getSupportedChains(isTestnet, searchArg).map((chain, index) => { return ( { }; const ChainsView = () => { - const [chain, setChain] = useState(''); + const [searchArg, setSearchArg] = useState(''); return ( @@ -61,11 +64,11 @@ const ChainsView = () => { {getSupportedChains(true).length} testnet chains - + - <ChainList isTestnet={false} chain={chain} /> + <ChainList isTestnet={false} searchArg={searchArg} /> <Title header={'Testnets'} /> - <ChainList isTestnet={true} chain={chain} /> + <ChainList isTestnet={true} searchArg={searchArg} /> </Flex> ); }; diff --git a/viewer/src/Components/DappView.js b/viewer/src/Components/DappView.js new file mode 100644 index 00000000..31461d01 --- /dev/null +++ b/viewer/src/Components/DappView.js @@ -0,0 +1,66 @@ +import { Flex, Text, Image } from '@chakra-ui/react'; +import { DappLogo } from '@api3/logos'; +import SearchRow from '../Custom/SearchRow'; +import { useState } from 'react'; +import InfoView from '../Custom/InfoView'; +import { api3Contracts } from '@api3/dapi-management'; + +const DappView = () => { + const [searchArg, setSearchArg] = useState(''); + const [selectedDapp, setSelectedDapp] = useState(''); + + const getDapps = () => { + const dapps = [...new Set(api3Contracts.DAPPS)]; + return dapps.filter((dapp) => dapp.name.toLowerCase().includes(searchArg.toLowerCase())); + }; + + return ( + <Flex p={3} gap={3} bgColor={'white'} wrap={'wrap'} alignItems="center" justifyContent="center"> + <Flex width={'100%'}> + <Text fontSize="md" fontWeight="bold" ml={2}> + There is a total of {getDapps().length} dapps + </Text> + </Flex> + <SearchRow text={searchArg} setText={setSearchArg} placeholder={'Enter a dapp name'} /> + + {getDapps().map((dapp, index) => { + return ( + <Flex + p={3} + boxShadow={'md'} + width={'310px'} + height={'80px'} + bgColor={'gray.100'} + key={index} + alignItems="center" + justifyContent="left" + onMouseDown={() => setSelectedDapp(dapp.alias)} + cursor={'pointer'} + > + {selectedDapp !== dapp.alias ? ( + <> + <Image src={DappLogo(dapp.alias)} width={50} height={50} bgColor={'white'} p={2} /> + <Image + src={DappLogo(dapp.alias, true)} + width={50} + height={50} + bgColor={'black'} + p={2} + /> + <Text fontSize="md" fontWeight="bold" ml={2}> + {dapp.name} + </Text> + </> + ) : ( + <> + <InfoView method={'Dapp'} feed={dapp.alias} onExit={() => selectedDapp(null)} /> + </> + )} + </Flex> + ); + })} + </Flex> + ); +}; + +export default DappView; diff --git a/viewer/src/Components/Welcome.js b/viewer/src/Components/Welcome.js index 9128a6b7..73b43b61 100644 --- a/viewer/src/Components/Welcome.js +++ b/viewer/src/Components/Welcome.js @@ -1,6 +1,7 @@ import SymbolsView from './SymbolsView'; import ChainsView from './ChainsView'; import ApiProviderView from './ApiProviderView'; +import DappView from './DappView'; import MissingLogosView from './MissingLogosView'; import Docs from './Docs'; import { VStack } from '@chakra-ui/react'; @@ -21,6 +22,7 @@ const Welcome = () => { <ExpandableView header={'Symbols'} view={<SymbolsView />} /> <ExpandableView header={'Chains'} view={<ChainsView />} /> <ExpandableView header={'ApiProviders'} view={<ApiProviderView />} /> + <ExpandableView header={'Dapps'} view={<DappView />} /> <ExpandableView header={'Missing Logos'} view={<MissingLogosView />} /> <ExpandableView header={'How to use'} view={<Docs />} /> </VStack>