Compare commits

...
5 Commits
Author SHA1 Message Date
Chris Troutner a93241f880 Merge pull request #17 from Permissionless-Software-Foundation/dh-global-feed
feat(nostr): Created Nostr Global Feed page
2025-06-09 17:11:38 -07:00
Daniel Gonzalez 76dcd45d16 feat(nostr): Created Nostr Global Feed page 2025-06-09 19:32:55 -04:00
Chris Troutner acb39ceff8 Merge pull request #15 from Permissionless-Software-Foundation/dh-cache-data
feat(cache): caching mutable data
2025-06-06 20:38:34 -07:00
Daniel Gonzalez 63b7bad1d1 Fixed merge conflicts 2025-06-06 21:24:09 -04:00
Daniel Gonzalez 36c81e7ce9 feat(cache): caching mutable data 2025-06-03 19:34:57 -04:00
7 changed files with 183 additions and 24 deletions
+2
View File
@@ -25,6 +25,7 @@ import SignMessage from './sign/index.js'
import ServerSelectView from './configuration/select-server-view'
import NostrPost from './nostr/nostr-post/index.js'
import NostrRead from './nostr/nostr-read/index.js'
import GlobalFeed from './nostr/global-feed/index.js'
import UserDataReview from './user-data-review'
function AppBody (props) {
// Dependency injection through props
@@ -47,6 +48,7 @@ function AppBody (props) {
<Route path='/configuration' element={<ServerSelectView appData={appData} />} />
<Route path='/nostr-post' element={<NostrPost appData={appData} />} />
<Route path='/nostr-read' element={<NostrRead appData={appData} />} />
<Route path='/global-feed' element={<GlobalFeed appData={appData} />} />
<Route path='/user-data/:cid' element={<UserDataReview appData={appData} />} />
</Routes>
{/** Show in all paths except the servers view */}
+30 -12
View File
@@ -102,8 +102,6 @@ function NftsForSale (props) {
if (tokenData) {
// Set data to the token object , this can be used to display the token name in the token card component.
thisToken.tokenData = tokenData
// Update the cache data providing the tokenId to update and the data
appData.updateNFTCacheData(thisToken.tokenId, { tokenData })
}
// Mark token to prevent fetch token data again.
@@ -163,8 +161,6 @@ function NftsForSale (props) {
// Set the icon url to the token , this can be used to display the icon in the token card component.
thisToken.icon = iconUrl
thisToken.tokenData.userData = userData
// Update the cache data providing the tokenId to update and the data
appData.updateNFTCacheData(thisToken.tokenId, { tokenIcon: iconUrl })
}
// Mark token to prevent fetch token icon again.
@@ -177,14 +173,15 @@ function NftsForSale (props) {
}
}, [fetchTokenMutableData, appData])
// Load tokens data from the cache
const loadCacheData = useCallback(async (offers) => {
const cacheData = appData.nftForSaleData
// Check if exiist token data in the cache and add it to the tokens object
const reviewNftCachedData = useCallback(async (offers) => {
const cacheData = appData.nftForSaleCacheData
console.log(`Token data from cache: ${JSON.stringify(cacheData, null, 2)}`)
// Map all offers and add cache data to the offers if it exists
// Map all offers
for (let i = 0; i < offers.length; i++) {
const thisToken = offers[i]
const cacheToken = cacheData[thisToken.tokenId]
// If cache data exists, add it to the token object
if (cacheToken) {
thisToken.tokenData = cacheToken.tokenData
thisToken.icon = cacheToken.tokenIcon
@@ -192,6 +189,22 @@ function NftsForSale (props) {
}
}, [appData])
// Check if exiist token data in the cache and add it to the tokens object
const updateNFTCachedData = useCallback(async (offers) => {
// Map all offers
for (let i = 0; i < offers.length; i++) {
const thisToken = offers[i]
// save token icon and token data in cache
const newTokenCacheData = {
tokenIcon: thisToken.icon,
tokenData: thisToken.tokenData
}
console.log('newTokenCacheData: ', newTokenCacheData)
appData.updateNFTCachedData(thisToken.tokenId, newTokenCacheData)
}
}, [appData])
// Main function to load NFT offers
const loadNftOffers = useCallback(async () => {
try {
@@ -199,18 +212,23 @@ function NftsForSale (props) {
// Get tokens in offers
const offers = await getNftOffers()
console.log('offers: ', offers)
await loadCacheData(offers)
// Review cached data to populate token data from cached data
await reviewNftCachedData(offers)
// set state to start displaying tokens cards.
setOffers(offers)
// Load tokens data
// Load tokens data for any updates to the token data
await lazyLoadTokenData(offers)
// Load tokens icons
// Load tokens icons from mutable data
await lazyLoadMutableData(offers)
// Update cached data with the latest retrieved data
await updateNFTCachedData(offers)
setIsLoading(false)
} catch (err) {
setIsLoading(false)
console.error('Error loading NFT offers:', err)
}
}, [lazyLoadTokenData, lazyLoadMutableData, getNftOffers, loadCacheData])
}, [lazyLoadTokenData, lazyLoadMutableData, getNftOffers, reviewNftCachedData, updateNFTCachedData])
// Effect to load NFTs on component mount
useEffect(() => {
@@ -0,0 +1,98 @@
/**
* Component for reading global nostr posts
*/
// Global npm libraries
import React, { useEffect, useState } from 'react'
import { Container, Card, Spinner } from 'react-bootstrap'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faUser } from '@fortawesome/free-solid-svg-icons'
import { RelayPool } from 'nostr'
function Feed (props) {
const { bchWalletState } = props.appData
const [posts, setPosts] = useState([])
const [loaded, setLoaded] = useState(false)
useEffect(() => {
const start = () => {
const psf = 'wss://nostr-relay.psfoundation.info'
const pool = RelayPool([psf])
pool.on('open', relay => {
relay.subscribe('REQ', { limit: 10, kinds: [1], '#t': ['slpdex-socialmedia'] })
})
pool.on('eose', relay => {
console.log('Closing Relay')
setLoaded(true)
relay.close()
})
pool.on('event', (relay, subId, ev) => {
console.log('Received event:', ev)
// const profile = JSON.parse(ev.content)
setPosts(currentPosts => [...currentPosts, ev])
})
}
if (!loaded) {
start()
}
}, [bchWalletState, loaded])
return (
<Container className='mt-4 mb-5' style={{ marginBottom: '50px' }}>
<div>
{posts.map((post, index) => (
<Card key={index} className='mb-4 bg-light rounded-4 shadow-sm border-0'>
<Card.Body className='p-3'>
<div className='d-flex align-items-center gap-3 mb-3'>
<div
className='rounded-circle bg-gradient shadow d-flex align-items-center justify-content-center'
style={{
width: '48px',
height: '48px',
background: 'linear-gradient(45deg, #6c757d, #495057)'
}}
>
<FontAwesomeIcon icon={faUser} size='1x' color='#7c7c7d' />
</div>
<div className='flex-grow-1'>
<div className='fw-bold mb-1'>
{post.pubkey.slice(0, 8) + '...'}
</div>
<small className='text-muted'>
{`${post.pubkey.slice(0, 8)}...${post.pubkey.slice(-5)}`}
</small>
</div>
<small className='text-muted'>
{new Date(post.created_at * 1000).toLocaleString()}
</small>
</div>
<div className='mb-4'>
<div className='fs-5 ms-5 text-dark' style={{ lineHeight: '1.3' }}>
{post.content}
</div>
</div>
</Card.Body>
</Card>
))}
{!posts.length && loaded && (
<div className='text-center text-muted py-5 bg-light rounded-4 shadow-sm'>
<i className='bi bi-inbox-fill fs-1 mb-3 d-block opacity-50' />
<div>No posts found</div>
</div>
)}
{!loaded && (
<div className='text-center text-muted py-5 bg-light rounded-4 shadow-sm'>
<Spinner animation='border' />
</div>
)}
</div>
</Container>
)
}
export default Feed
@@ -0,0 +1,31 @@
/*
Component for reading the nostr global feed.
*/
// Global npm libraries
import React from 'react'
import { Container } from 'react-bootstrap'
import Feed from './feed'
function GlobalFeed (props) {
const { appData } = props
return (
<>
<Container>
<h2 style={{
textAlign: 'center',
margin: '20px 0 50px 0',
padding: '10px',
borderBottom: '2px solid #ccc'
}}
>
Global Feed
</h2>
<Feed appData={appData} />
</Container>
</>
)
}
export default GlobalFeed
@@ -3,7 +3,7 @@
*/
// Global npm libraries
import React, { useEffect, useState } from 'react'
import { Container, Card } from 'react-bootstrap'
import { Container, Card, Spinner } from 'react-bootstrap'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faUser } from '@fortawesome/free-solid-svg-icons'
@@ -28,6 +28,7 @@ function PublicRead (props) {
pool.on('eose', relay => {
console.log('Closing Relay')
setLoaded(true)
relay.close()
})
@@ -35,8 +36,6 @@ function PublicRead (props) {
console.log('Received event:', ev)
setPosts(currentPosts => [...currentPosts, ev])
})
setLoaded(true)
}
if (!loaded) {
@@ -85,6 +84,11 @@ function PublicRead (props) {
<div>No posts found</div>
</div>
)}
{!loaded && (
<div className='text-center text-muted py-5 bg-light rounded-4 shadow-sm'>
<Spinner animation='border' />
</div>
)}
</div>
</Container>
)
+7
View File
@@ -114,6 +114,13 @@ function NavMenu (props) {
>
Nostr Profile
</NavLink>
<NavLink
className={currentPath === '/global-feed' ? 'nav-link-active' : 'nav-link-inactive'}
to='/global-feed'
onClick={handleClickEvent}
>
Global Feed
</NavLink>
</Nav>
</Navbar.Collapse>
</Navbar>
+8 -9
View File
@@ -37,8 +37,8 @@ function useAppState () {
const [hideSpinner, setHideSpinner] = useState(false)
const [denyClose, setDenyClose] = useState(false)
// NFTs for sale state data
const [nftForSaleData, setNftForSaleData] = useState({})
// NFTs for sale stored data to improve performance
const [nftForSaleCacheData, setNftForSaleCacheData] = useState({})
// The wallet state makes this a true progressive web app (PWA). As
// balances, UTXOs, and tokens are retrieved, this state is updated.
@@ -100,14 +100,13 @@ function useAppState () {
}
// Update NFT for sale tokens data
function updateNFTCacheData (tokenId, data) {
const allCacheData = nftForSaleData // Get all cache data
function updateNFTCachedData (tokenId, data) {
const allCacheData = nftForSaleCacheData // Get all cache data
const cacheData = allCacheData[tokenId] || {} // Get cache data for the tokenId
const newCacheData = Object.assign({}, cacheData, data) // Merge the new data with the cache data
console.log('newCacheData: ', newCacheData)
allCacheData[tokenId] = newCacheData // Update the cache data
setNftForSaleData(allCacheData) // Update the state
setNftForSaleCacheData(allCacheData) // Update the state
}
return {
@@ -146,9 +145,9 @@ function useAppState () {
setDexLib,
nostr,
setNostr,
nftForSaleData,
setNftForSaleData,
updateNFTCacheData
nftForSaleCacheData,
setNftForSaleCacheData,
updateNFTCachedData
}
}