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: implement random sort and dynamic timeline sorting via dropdown #13

Merged
merged 10 commits into from
Jun 5, 2024
Merged
25 changes: 25 additions & 0 deletions db.js
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,31 @@ export class ActivityPubDB extends EventTarget {
await tx.done
}

async * searchNotesRandom ({ limit = DEFAULT_LIMIT } = {}) {
const tx = this.db.transaction(NOTES_STORE, 'readonly')
const store = tx.objectStore(NOTES_STORE)
const totalNotes = await store.count()

RangerMauve marked this conversation as resolved.
Show resolved Hide resolved
for (let i = 0; i < limit; i++) {
const randomSkip = Math.floor(Math.random() * totalNotes)
let cursor = await store.openCursor()
RangerMauve marked this conversation as resolved.
Show resolved Hide resolved
if (cursor) {
if (randomSkip > 0) { // Only advance if randomSkip is greater than 0
await cursor.advance(randomSkip)
if (cursor) {
RangerMauve marked this conversation as resolved.
Show resolved Hide resolved
yield cursor.value
cursor = await cursor.continue()
}
} else {
// If randomSkip is 0, yield the first item directly
yield cursor.value
cursor = await cursor.continue() // Continue to the next for proper iteration
}
}
}
await tx.done
}

async ingestActor (url, isInitial = false) {
console.log(`Starting ingestion for actor from URL: ${url}`)
const actor = await this.getActor(url)
Expand Down
12 changes: 11 additions & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,17 @@
</style>
<div class="container">
<sidebar-nav></sidebar-nav>
<reader-timeline></reader-timeline>
<div class="reader-container">
<div class="sort-container">
RangerMauve marked this conversation as resolved.
Show resolved Hide resolved
<label for="sortOrder">Sort Timeline:</label>
<select id="sortOrder" class="sort-dropdown">
<option value="latest">Newest</option>
<option value="oldest">Oldest</option>
<option value="random">Random</option>
</select>
</div>
<reader-timeline></reader-timeline>
</div>
<div class="right-column">
<!-- This is an empty column to balance the layout -->
</div>
Expand Down
2 changes: 1 addition & 1 deletion theme-selector.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ class ThemeSelector extends HTMLElement {
const style = document.createElement('style')
style.textContent = `
select {
padding: 4px;
padding: 2px;
margin: 6px 0;
border: 1px solid var(--rdp-border-color);
border-radius: 4px;
Expand Down
34 changes: 33 additions & 1 deletion timeline.css
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,35 @@ body {
background: var(--bg-color);
}

.reader-container {
margin-top: 20px;
display: flex;
flex-direction: column;
align-items: flex-start;
}

.sort-container {
width: 100%;
display: flex;
justify-content: flex-start;
}

.sort-container label {
color: var(--rdp-text-color);
font-size: 0.875rem;
margin-right: 4px;
display: flex;
align-items: center;
margin-left: 48px;
}

.sort-dropdown {
padding: 2px;
border: 1px solid var(--rdp-border-color);
border-radius: 4px;
width: 75px;
}

reader-timeline {
flex: 1;
max-width: 600px;
Expand All @@ -17,9 +46,12 @@ reader-timeline {
}

@media screen and (max-width: 768px) {
.reader-container {
margin-top: 160px;
}

reader-timeline {
width: 100%;
max-width: 100%;
margin-top: 150px;
}
}
95 changes: 79 additions & 16 deletions timeline.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,15 @@ class ReaderTimeline extends HTMLElement {
skip = 0
limit = 32
hasMoreItems = true
sort = 'latest'
loadMoreBtn = null
randomNotes = [] // Cache for random notes
randomIndex = 0 // Current index in the random notes cache

constructor () {
super()
this.loadMoreBtn = document.createElement('button')
this.loadMoreBtn.textContent = 'Load More..'
this.loadMoreBtn.textContent = 'Load More...'
this.loadMoreBtn.className = 'load-more-btn'

this.loadMoreBtnWrapper = document.createElement('div')
Expand All @@ -22,9 +25,42 @@ class ReaderTimeline extends HTMLElement {
}

connectedCallback () {
this.initializeSortOrder()
this.initializeDefaultFollowedActors().then(() => this.initTimeline())
}

initializeSortOrder () {
const params = new URLSearchParams(window.location.search)
this.sort = params.get('sort') || 'latest'

const sortOrderSelect = document.getElementById('sortOrder')
if (sortOrderSelect) {
sortOrderSelect.value = this.sort
sortOrderSelect.addEventListener('change', (event) => {
this.sort = event.target.value
this.updateURL()
this.resetTimeline()
})
}
}

updateURL () {
const url = new URL(window.location)
url.searchParams.set('sort', this.sort)
window.history.pushState({}, '', url)
}

async resetTimeline () {
this.skip = 0
this.randomIndex = 0
this.randomNotes = []
this.hasMoreItems = true
while (this.firstChild) {
this.removeChild(this.firstChild)
}
this.loadMore()
}

async initializeDefaultFollowedActors () {
const defaultActors = [
'https://social.distributed.press/v1/@announcements@social.distributed.press/',
Expand All @@ -35,14 +71,9 @@ class ReaderTimeline extends HTMLElement {
// "https://staticpub.mauve.moe/about.jsonld",
]

// Check if followed actors have already been initialized
const hasFollowedActors = await db.hasFollowedActors()
if (!hasFollowedActors) {
await Promise.all(
defaultActors.map(async (actorUrl) => {
await db.followActor(actorUrl)
})
)
await Promise.all(defaultActors.map(actorUrl => db.followActor(actorUrl)))
}
}

Expand All @@ -56,20 +87,52 @@ class ReaderTimeline extends HTMLElement {
}

async loadMore () {
// Remove the button before loading more items
this.loadMoreBtnWrapper.remove()

let count = 0
for await (const note of db.searchNotes({}, { skip: this.skip, limit: this.limit })) {
count++
this.appendNoteElement(note)

if (this.sort === 'random' && this.randomNotes.length === 0) {
const allNotes = []
for await (const note of db.searchNotesRandom(this.limit)) {
allNotes.push(note)
}
this.randomNotes = allNotes.sort(() => Math.random() - 0.5)
RangerMauve marked this conversation as resolved.
Show resolved Hide resolved
}

const notesToShow = this.sort === 'random'
? this.randomNotes.slice(this.randomIndex, this.randomIndex + this.limit)
: await this.fetchSortedNotes()

for (const note of notesToShow) {
if (note) {
this.appendNoteElement(note)
count++
}
}

// Update skip value and determine if there are more items
this.skip += this.limit
this.hasMoreItems = count === this.limit
this.updateIndexes(count)
this.appendLoadMoreIfNeeded()
}

async fetchSortedNotes () {
const notesGenerator = db.searchNotes({}, { skip: this.skip, limit: this.limit, sort: this.sort === 'oldest' ? 1 : -1 })
const notes = []
for await (const note of notesGenerator) {
notes.push(note)
}
return notes
}

updateIndexes (count) {
RangerMauve marked this conversation as resolved.
Show resolved Hide resolved
if (this.sort === 'random') {
this.randomIndex += this.limit
this.hasMoreItems = this.randomIndex < this.randomNotes.length
} else {
this.skip += this.limit
this.hasMoreItems = count === this.limit
}
}

// Append the button at the end if there are more items
appendLoadMoreIfNeeded () {
if (this.hasMoreItems) {
this.appendChild(this.loadMoreBtnWrapper)
}
Expand Down
Loading