[Feat]: Add loading animation on init

PRevents errors on very early inbox load (mostly by bots but oh well... perfectionTM)
This commit is contained in:
ClaraCrazy 2025-12-30 11:20:12 +01:00
parent 5226cb3c6b
commit 12069300d0
No known key found for this signature in database
GPG key ID: EBBC896ACB497011
5 changed files with 87 additions and 3 deletions

10
app.js
View file

@ -46,14 +46,20 @@ const mailProcessingService = new MailProcessingService(
) )
debug('Mail processing service initialized') debug('Mail processing service initialized')
// Track IMAP initialization state
let isImapReady = false
app.set('isImapReady', false)
// Put everything together: // Put everything together:
imapService.on(ImapService.EVENT_NEW_MAIL, mail => imapService.on(ImapService.EVENT_NEW_MAIL, mail =>
mailProcessingService.onNewMail(mail) mailProcessingService.onNewMail(mail)
) )
debug('Bound IMAP new mail event handler') debug('Bound IMAP new mail event handler')
imapService.on(ImapService.EVENT_INITIAL_LOAD_DONE, () => imapService.on(ImapService.EVENT_INITIAL_LOAD_DONE, () => {
mailProcessingService.onInitialLoadDone() mailProcessingService.onInitialLoadDone()
) isImapReady = true
app.set('isImapReady', true)
})
debug('Bound IMAP initial load done event handler') debug('Bound IMAP initial load done event handler')
imapService.on(ImapService.EVENT_DELETED_MAIL, mail => imapService.on(ImapService.EVENT_DELETED_MAIL, mail =>
mailProcessingService.onMailDeleted(mail) mailProcessingService.onMailDeleted(mail)

View file

@ -894,6 +894,43 @@ label {
} }
/* Loading Page */
.loading-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 80vh;
text-align: center;
}
.loading-spinner {
margin-bottom: 30px;
}
.loading-title {
color: var(--color-text-primary);
margin-bottom: 15px;
font-size: 2rem;
}
.loading-message {
color: var(--color-text-secondary);
font-size: 1.2rem;
}
.loading-submessage {
color: var(--color-text-muted);
font-size: 1rem;
margin-top: 10px;
}
body.loading-page .logo {
display: none;
}
/* Responsive Styles */ /* Responsive Styles */
@media (max-width: 768px) { @media (max-width: 768px) {

View file

@ -75,7 +75,7 @@
<script src="/javascripts/notifications.js" defer="true"></script> <script src="/javascripts/notifications.js" defer="true"></script>
</head> </head>
<body> <body{% if bodyClass %} class="{{ bodyClass }}"{% endif %}>
<main> <main>
<div class="header"> <div class="header">
<a href="/"> <a href="/">

View file

@ -0,0 +1,27 @@
{% extends "layout.twig" %}
{% set bodyClass = 'loading-page' %}
{% block header %}{% endblock %}
{% block footer %}{% endblock %}
{% block body %}
<div class="loading-container">
<div class="loading-spinner">
<svg width="80" height="80" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<circle cx="50" cy="50" r="40" stroke="var(--color-accent-purple-light)" stroke-width="8" fill="none" stroke-dasharray="63 188" stroke-linecap="round">
<animateTransform attributeName="transform" type="rotate" from="0 50 50" to="360 50 50" dur="1.5s" repeatCount="indefinite"/>
</circle>
</svg>
</div>
<h2 class="loading-title">Loading Mail Service</h2>
<p class="loading-message">Connecting to IMAP server and loading messages...</p>
<p class="loading-submessage">This may take a few moments on first startup</p>
</div>
<script>
// Auto-refresh every 2 seconds to check if IMAP is ready
setTimeout(() => {
window.location.reload();
}, 2000);
</script>
{% endblock %}

View file

@ -85,6 +85,20 @@ app.use(
) )
Twig.extendFilter('sanitizeHtml', sanitizeHtmlTwigFilter) Twig.extendFilter('sanitizeHtml', sanitizeHtmlTwigFilter)
// Middleware to show loading page until IMAP is ready
app.use((req, res, next) => {
const isImapReady = req.app.get('isImapReady')
if (!isImapReady && !req.path.startsWith('/images') && !req.path.startsWith('/javascripts') && !req.path.startsWith('/stylesheets') && !req.path.startsWith('/dependencies')) {
return res.render('loading', {
branding: config.http.branding,
purgeTime: purgeTime,
count: "NaN Emails"
})
}
next()
})
app.use('/', loginRouter) app.use('/', loginRouter)
app.use('/inbox', inboxRouter) app.use('/inbox', inboxRouter)
app.use('/error', errorRouter) app.use('/error', errorRouter)