feat(home): add skeleton loaders to homepage

This commit is contained in:
SethBurkart123
2024-12-02 09:26:49 +11:00
parent fcd95f6823
commit a2b4f81b86
2 changed files with 102 additions and 101 deletions
+74 -99
View File
@@ -2158,8 +2158,7 @@ export async function loadHomePage() {
// Create root container first // Create root container first
const homeRoot = stringToHTML(` const homeRoot = stringToHTML(`
<div class="home-root"> <div id="home-root" class="home-root">
<div class="home-container" id="home-container"></div>
</div>`) </div>`)
// Clear main and add home root // Clear main and add home root
@@ -2167,91 +2166,72 @@ export async function loadHomePage() {
main.appendChild(homeRoot?.firstChild!) main.appendChild(homeRoot?.firstChild!)
// Get reference to home container for all subsequent additions // Get reference to home container for all subsequent additions
const homeContainer = document.getElementById('home-container') const homeContainer = document.getElementById('home-root')
if (!homeContainer) return if (!homeContainer) return
// Add initial style to prevent flash const skeletonStructure = stringToHTML(`
if (settingsState.animations) { <div class="home-container" id="home-container">
const style = document.createElement('style') <div class="shortcut-container border">
style.textContent = ` <div class="shortcuts border" id="shortcuts"></div>
.home-container > div { </div>
opacity: 0; <div class="timetable-container border">
transform: translateY(10px) scale(0.99); <div class="home-subtitle">
} <h2 id="home-lesson-subtitle">Today's Lessons</h2>
` <div class="timetable-arrows">
document.head.appendChild(style) <svg width="24" height="24" viewBox="0 0 24 24" style="transform: scale(-1,1)" id="home-timetable-back">
} <g style="fill: currentcolor;"><path d="M8.578 16.359l4.594-4.594-4.594-4.594 1.406-1.406 6 6-6 6z"></path></g>
</svg>
// Use DocumentFragment for batch DOM updates inside home-container <svg width="24" height="24" viewBox="0 0 24 24" id="home-timetable-forward">
const fragment = document.createDocumentFragment() <g style="fill: currentcolor;"><path d="M8.578 16.359l4.594-4.594-4.594-4.594 1.406-1.406 6 6-6 6z"></path></g>
</svg>
// Batch state updates </div>
const date = new Date() </div>
currentSelectedDate = new Date() <div class="day-container loading" id="day-container">
const TodayFormatted = formatDate(date)
// Create shortcuts section
const shortcutContainer = stringToHTML(`
<div class="shortcut-container border">
<div class="shortcuts border" id="shortcuts"></div>
</div>`)
fragment.appendChild(shortcutContainer?.firstChild!)
// Create timetable section with optimized structure
const timetable = stringToHTML(`
<div class="timetable-container border">
<div class="home-subtitle">
<h2 id="home-lesson-subtitle">Today's Lessons</h2>
<div class="timetable-arrows">
<svg width="24" height="24" viewBox="0 0 24 24" style="transform: scale(-1,1)" id="home-timetable-back">
<g style="fill: currentcolor;"><path d="M8.578 16.359l4.594-4.594-4.594-4.594 1.406-1.406 6 6-6 6z"></path></g>
</svg>
<svg width="24" height="24" viewBox="0 0 24 24" id="home-timetable-forward">
<g style="fill: currentcolor;"><path d="M8.578 16.359l4.594-4.594-4.594-4.594 1.406-1.406 6 6-6 6z"></path></g>
</svg>
</div> </div>
</div> </div>
<div class="day-container" id="day-container"></div> <div class="upcoming-container border">
</div>`) <div class="upcoming-title">
fragment.appendChild(timetable?.firstChild!) <h2 class="home-subtitle">Upcoming Assessments</h2>
<div class="upcoming-filters" id="upcoming-filters"></div>
// Create upcoming assessments section </div>
const upcomingContainer = document.createElement('div') <div class="upcoming-items loading" id="upcoming-items">
upcomingContainer.classList.add('upcoming-container', 'border') </div>
</div>
const upcomingTitleDiv = CreateElement('div', 'upcoming-title') <div class="notices-container border">
const upcomingTitle = document.createElement('h2') <div style="display: flex; justify-content: space-between">
upcomingTitle.classList.add('home-subtitle') <h2 class="home-subtitle">Notices</h2>
upcomingTitle.innerText = 'Upcoming Assessments' <input type="date" />
upcomingTitleDiv.append(upcomingTitle) </div>
<div class="notice-container upcoming-items loading" id="notice-container">
const upcomingFilterDiv = CreateElement('div', 'upcoming-filters', 'upcoming-filters') </div>
upcomingTitleDiv.append(upcomingFilterDiv)
upcomingContainer.append(upcomingTitleDiv)
const upcomingItems = document.createElement('div')
upcomingItems.id = 'upcoming-items'
upcomingItems.classList.add('upcoming-items')
upcomingContainer.append(upcomingItems)
fragment.appendChild(upcomingContainer)
// Create notices section
const notices = stringToHTML(`
<div class="notices-container border">
<div style="display: flex; justify-content: space-between">
<h2 class="home-subtitle">Notices</h2>
<input type="date" value="${TodayFormatted}" />
</div> </div>
<div class="notice-container" id="notice-container"></div>
</div>`) </div>`)
fragment.appendChild(notices?.firstChild!)
// Single DOM update to home-container // Add skeleton structure
homeContainer.appendChild(fragment) homeContainer.appendChild(skeletonStructure.firstChild!)
// Run animations if enabled
if (settingsState.animations) {
animate(
'.home-container > div',
{ opacity: [0, 1], y: [10, 0], scale: [0.99, 1] },
{
delay: stagger(0.15, { startDelay: 0.1 }),
type: 'spring',
stiffness: 341,
damping: 20,
mass: 1
}
)
}
// Setup event listeners with cleanup // Setup event listeners with cleanup
const cleanup = setupTimetableListeners() const cleanup = setupTimetableListeners()
// Initialize shortcuts immediately
addShortcuts(settingsState.shortcuts)
AddCustomShortcutsToPage()
// Parallel data fetching // Parallel data fetching
const [assessments, classes, prefs] = await Promise.all([ const [assessments, classes, prefs] = await Promise.all([
GetUpcomingAssessments(), GetUpcomingAssessments(),
@@ -2272,10 +2252,20 @@ export async function loadHomePage() {
.sort(comparedate) .sort(comparedate)
// Initialize components // Initialize components
addShortcuts(settingsState.shortcuts) const date = new Date()
AddCustomShortcutsToPage() const TodayFormatted = formatDate(date)
// Load timetable
await callHomeTimetable(TodayFormatted, true) await callHomeTimetable(TodayFormatted, true)
await CreateUpcomingSection(currentAssessments, activeSubjects)
// Load upcoming assessments
const upcomingItems = document.getElementById('upcoming-items')
if (upcomingItems) {
await CreateUpcomingSection(currentAssessments, activeSubjects)
delay(100)
upcomingItems.classList.remove('loading')
console.log('Upcoming assessments created')
}
// Setup notices // Setup notices
const labelArray = prefs.payload const labelArray = prefs.payload
@@ -2283,32 +2273,17 @@ export async function loadHomePage() {
.map((item: any) => item.value) .map((item: any) => item.value)
if (labelArray.length > 0) { if (labelArray.length > 0) {
setupNotices(labelArray[0].split(' '), TodayFormatted) const noticeContainer = document.getElementById('notice-container')
if (noticeContainer) {
noticeContainer.classList.remove('loading')
setupNotices(labelArray[0].split(' '), TodayFormatted)
}
} }
if (settingsState.notificationcollector) { if (settingsState.notificationcollector) {
enableNotificationCollector() enableNotificationCollector()
} }
// Setup animations
if (settingsState.animations) {
// Remove the initial style
document.head.querySelector('style:last-child')?.remove()
// Animate with motion
animate(
'.home-container > div',
{ opacity: [0, 1], y: [10, 0], scale: [0.99, 1] },
{
delay: stagger(0.15, { startDelay: 0.1 }),
type: 'spring',
stiffness: 341,
damping: 20,
mass: 1
}
)
}
return cleanup return cleanup
} }
+28 -2
View File
@@ -2724,7 +2724,7 @@ li.MessageList__unread___3imtO {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
color: var(--text-primary); color: var(--text-primary);
transition: 200ms; transition: 200ms, background-color 0s;
border-radius: 16px; border-radius: 16px;
} }
.dark .upcoming-items { .dark .upcoming-items {
@@ -3093,4 +3093,30 @@ li.MessageList__unread___3imtO {
aspect-ratio: 16/9; aspect-ratio: 16/9;
object-fit: cover; object-fit: cover;
margin-bottom: 12px; margin-bottom: 12px;
} }
@keyframes shimmer {
0% {
background-position: -1000px 0;
}
100% {
background-position: 1000px 0;
}
}
.loading {
&.upcoming-items,
&.day-container {
background: linear-gradient(90deg,
var(--background-primary) 0%,
var(--background-secondary) 50%,
var(--background-primary) 100%
);
background-size: 1000px 100%;
animation: shimmer 2s infinite linear;
}
&.upcoming-items {
height: 35em;
}
}