mirror of
https://github.com/BetterSEQTA/BetterSEQTA-Plus.git
synced 2026-06-06 03:34:40 +00:00
feat(home): add skeleton loaders to homepage
This commit is contained in:
+74
-99
@@ -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
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user