Show spinner while loading ZIM content in viewer iframe

- Implemented a spinner to improve user experience while ZIM content is loading in the viewer iframe.
- Added .loader and .spinner styles in kiwix.css.
- The iframe content is initially hidden (visibility: hidden) and will be displayed once loading completes.
- Used CSS animations (@keyframes spin) for a smooth rotating effect.
This commit is contained in:
Naman Pahwa 2025-02-28 14:05:40 +05:30
parent ad58a501b0
commit c7e86c9dbb
8 changed files with 59 additions and 8 deletions

View File

@ -78,4 +78,5 @@
, "book-category.wikivoyage": "Wikivoyage"
, "book-category.wiktionary": "Wiktionary"
, "book-category.other": "Other"
, "text-loading-content": "Loading Content"
}

View File

@ -80,5 +80,6 @@
"book-category.wikiversity": "Name for the category of Wikiversity books",
"book-category.wikivoyage": "Name for the category of Wikivoyage books",
"book-category.wiktionary": "Name for the category of Wiktionary books",
"book-category.other": "Books not belonging to any special category are listed under this one"
"book-category.other": "Books not belonging to any special category are listed under this one",
"text-loading-content": "Text displayed while content is being loaded"
}

View File

@ -65,4 +65,5 @@
, "book-category.wikivoyage": "[I18N] Wikivoyage [TESTING]"
, "book-category.wiktionary": "[I18N] Wiktionary [TESTING]"
, "book-category.other": "[I18N] Other [TESTING]"
, "text-loading-content": "[I18N TESTING] Loading content"
}

View File

@ -15,6 +15,35 @@ body {
box-sizing: border-box;
}
.loader {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 24px;
background: rgba(255, 255, 255, 0.8);
padding: 10px 20px;
border-radius: 5px;
display: flex;
align-items: center;
gap: 10px;
white-space: nowrap; /* Prevents text from wrapping */
min-width: fit-content; /* Ensures the container doesnt shrink */
}
.spinner {
width: 20px;
height: 20px;
border: 3px solid #ccc;
border-top: 3px solid #007bff;
border-radius: 50%;
animation: spin 1s linear infinite;
}
.hidden {
visibility: hidden;
}
::selection {
background-color: #00b4e4;
color: white;
@ -96,6 +125,11 @@ body {
height: 30px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
@font-face {
font-family: "poppins";
src: url("../skin/fonts/Poppins.ttf?KIWIXCACHEID") format("truetype");

View File

@ -424,6 +424,10 @@ function setup_external_link_blocker() {
let viewerSetupComplete = false;
function on_content_load() {
const loader = document.getElementById("kiwix__loader");
contentIframe.classList.remove("hidden");
loader.style.display = "none";
if ( viewerSetupComplete ) {
handle_content_url_change();
}
@ -612,6 +616,12 @@ function updateUIText() {
setTitle(document.getElementById("kiwix_serve_taskbar_random_button"),
$t("random-page-button-text"));
// Add translation for loading text as soon as translations are available
const loadingTextElement = document.getElementById("kiwix__loading_text");
if (loadingTextElement) {
loadingTextElement.textContent = $t("text-loading-content");
}
}
function finishViewerSetupOnceTranslationsAreLoaded()

View File

@ -33,6 +33,10 @@
</head>
<body style="margin:0" onload="setupViewer()">
<div class="loader" id="kiwix__loader">
<div class="spinner"></div>
<span id="kiwix__loading_text">Loading content...</span>
</div>
<div class="kiwix" style="display:none" id="kiwixtoolbarwrapper">
<div id="kiwixtoolbar" class="ui-widget-header">
<div class="kiwix_centered">
@ -78,7 +82,7 @@
</div>
</div>
<iframe id="content_iframe"
<iframe id="content_iframe" class="hidden"
referrerpolicy="no-referrer"
onload="on_content_load()"
src="./skin/blank.html?KIWIXCACHEID" title="ZIM content" width="100%"

View File

@ -1189,7 +1189,7 @@ TEST_F(LibraryServerTest, no_name_mapper_catalog_v2_individual_entry_access)
" <title>Welcome to Kiwix Server</title>\n" \
" <link\n" \
" type=\"text/css\"\n" \
" href=\"/ROOT%23%3F/skin/kiwix.css?cacheid=2158fad9\"\n" \
" href=\"/ROOT%23%3F/skin/kiwix.css?cacheid=3948b846\"\n" \
" rel=\"Stylesheet\"\n" \
" />\n" \
" <link\n" \

View File

@ -69,13 +69,13 @@ const ResourceCollection resources200Compressible{
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/isotope.pkgd.min.js" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/isotope.pkgd.min.js?cacheid=2e48d392" },
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/kiwix.css" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/kiwix.css?cacheid=2158fad9" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/kiwix.css?cacheid=3948b846" },
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/mustache.min.js" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/mustache.min.js?cacheid=bd23c4fb" },
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/taskbar.css" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/taskbar.css?cacheid=80d56607" },
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/viewer.js" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/viewer.js?cacheid=787e61d4" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/viewer.js?cacheid=d6f747f5" },
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/fonts/Poppins.ttf" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/fonts/Poppins.ttf?cacheid=af705837" },
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/fonts/Roboto.ttf" },
@ -280,7 +280,7 @@ TEST_F(ServerTest, CacheIdsOfStaticResources)
const std::vector<UrlAndExpectedResult> testData{
{
/* url */ "/ROOT%23%3F/",
R"EXPECTEDRESULT( href="/ROOT%23%3F/skin/kiwix.css?cacheid=2158fad9"
R"EXPECTEDRESULT( href="/ROOT%23%3F/skin/kiwix.css?cacheid=3948b846"
href="/ROOT%23%3F/skin/index.css?cacheid=ae79e41a"
<link rel="apple-touch-icon" sizes="180x180" href="/ROOT%23%3F/skin/favicon/apple-touch-icon.png?cacheid=f86f8df3">
<link rel="icon" type="image/png" sizes="32x32" href="/ROOT%23%3F/skin/favicon/favicon-32x32.png?cacheid=79ded625">
@ -321,13 +321,13 @@ R"EXPECTEDRESULT( <img src="${root}/skin/download-white.svg?cac
},
{
/* url */ "/ROOT%23%3F/viewer",
R"EXPECTEDRESULT( <link type="text/css" href="./skin/kiwix.css?cacheid=2158fad9" rel="Stylesheet" />
R"EXPECTEDRESULT( <link type="text/css" href="./skin/kiwix.css?cacheid=3948b846" rel="Stylesheet" />
<link type="text/css" href="./skin/taskbar.css?cacheid=80d56607" rel="Stylesheet" />
<link type="text/css" href="./skin/autoComplete/css/autoComplete.css?cacheid=f2d376c4" rel="Stylesheet" />
<script type="text/javascript" src="./skin/polyfills.js?cacheid=a0e0343d"></script>
<script type="module" src="./skin/i18n.js?cacheid=071abc9a" defer></script>
<script type="text/javascript" src="./skin/languages.js?cacheid=ee7d95b5" defer></script>
<script type="text/javascript" src="./skin/viewer.js?cacheid=787e61d4" defer></script>
<script type="text/javascript" src="./skin/viewer.js?cacheid=d6f747f5" defer></script>
<script type="text/javascript" src="./skin/autoComplete/autoComplete.min.js?cacheid=1191aaaf"></script>
const blankPageUrl = root + "/skin/blank.html?cacheid=6b1fa032";
<label for="kiwix_button_show_toggle"><img src="./skin/caret.png?cacheid=22b942b4" alt=""></label>