« get me outta code hell

client: search progress bar - hsmusic-wiki - HSMusic - static wiki software cataloguing collaborative creation
about summary refs log tree commit diff
diff options
context:
space:
mode:
author(quasar) nebula <qznebula@protonmail.com>2024-05-17 21:18:20 -0300
committer(quasar) nebula <qznebula@protonmail.com>2024-05-31 12:11:55 -0300
commit46297d244e3e0d83bf2dabf2582482c278287f6f (patch)
treeb64d52147abbd9e17205d2bae080181aa40a34d5
parent079509cd5b17aedbcec514ee33d63433ead993c4 (diff)
client: search progress bar
-rw-r--r--src/content/dependencies/generateSearchSidebarBox.js9
-rw-r--r--src/static/css/site.css25
-rw-r--r--src/static/js/client.js208
-rw-r--r--src/strings-default.yaml4
4 files changed, 231 insertions, 15 deletions
diff --git a/src/content/dependencies/generateSearchSidebarBox.js b/src/content/dependencies/generateSearchSidebarBox.js
index 22c5c773..cc215783 100644
--- a/src/content/dependencies/generateSearchSidebarBox.js
+++ b/src/content/dependencies/generateSearchSidebarBox.js
@@ -20,6 +20,15 @@ export default {
           },
           {type: 'search'}),
 
+        html.tag('template', {class: 'wiki-search-preparing-string'},
+          language.$('misc.search.preparing')),
+
+        html.tag('template', {class: 'wiki-search-loading-data-string'},
+          language.$('misc.search.loadingData')),
+
+        html.tag('template', {class: 'wiki-search-searching-string'},
+          language.$('misc.search.searching')),
+
         html.tag('template', {class: 'wiki-search-no-results-string'},
           language.$('misc.search.noResults')),
 
diff --git a/src/static/css/site.css b/src/static/css/site.css
index d094738a..e94484e5 100644
--- a/src/static/css/site.css
+++ b/src/static/css/site.css
@@ -510,16 +510,25 @@ summary .group-name {
 .wiki-search-sidebar-box hr {
   border-color: var(--primary-color);
   border-style: none none dotted none;
+  margin-top: 3px;
+  margin-bottom: 3px;
 }
 
-.wiki-search-sidebar-box hr:nth-of-type(1) {
-  margin-top: 3px;
-  margin-bottom: 6px;
+.wiki-search-progress-container {
+  padding: 2px 6px 4px 6px;
+  display: flex;
+  flex-direction: row;
 }
 
-.wiki-search-sidebar-box hr:nth-of-type(2) {
-  margin-top: 6px;
-  margin-bottom: 0;
+.wiki-search-progress-label {
+  font-size: 0.9em;
+  font-style: oblique;
+  cursor: default;
+  margin-right: 1ch;
+}
+
+.wiki-search-progress-bar {
+  flex-grow: 1;
 }
 
 .wiki-search-results-container {
@@ -945,6 +954,10 @@ li:not(:first-child:last-child) .tooltip,
   color: var(--page-primary-color);
 }
 
+progress {
+  accent-color: var(--primary-color);
+}
+
 .content-columns {
   columns: 2;
 }
diff --git a/src/static/js/client.js b/src/static/js/client.js
index e8de0472..5f9143e9 100644
--- a/src/static/js/client.js
+++ b/src/static/js/client.js
@@ -3452,11 +3452,13 @@ const wikiSearchInfo = initInfo('wikiSearchInfo', {
   },
 
   event: {
+    whenWorkerAlive: [],
+    whenWorkerReady: [],
+    whenWorkerFailsToInitialize: [],
+
     whenDownloadBegins: [],
     whenDownloadsBegin: [],
-
     whenDownloadProgresses: [],
-
     whenDownloadEnds: [],
   },
 });
@@ -3511,21 +3513,24 @@ function handleSearchWorkerMessage(message) {
 }
 
 function handleSearchWorkerStatusMessage(message) {
-  const {state} = wikiSearchInfo;
+  const {state, event} = wikiSearchInfo;
 
   switch (message.data.status) {
     case 'alive':
       console.debug(`Search worker is alive, but not yet ready.`);
+      dispatchInternalEvent(event, 'whenWorkerAlive');
       break;
 
     case 'ready':
       console.debug(`Search worker has loaded corpuses and is ready.`);
       state.workerReadyPromiseResolvers.resolve(state.worker);
+      dispatchInternalEvent(event, 'whenWorkerReady');
       break;
 
     case 'setup-error':
       console.debug(`Search worker failed to initialize.`);
       state.workerReadyPromiseResolvers.reject('setup-error');
+      dispatchInternalEvent(event, 'whenWorkerFailsToInitialize');
       break;
 
     default:
@@ -3665,6 +3670,11 @@ const sidebarSearchInfo = initInfo('sidebarSearchInfo', {
   searchBox: null,
   searchInput: null,
 
+  progressRule: null,
+  progressContainer: null,
+  progressLabel: null,
+  progressBar: null,
+
   resultsRule: null,
   resultsContainer: null,
   results: null,
@@ -3673,6 +3683,10 @@ const sidebarSearchInfo = initInfo('sidebarSearchInfo', {
   endSearchLine: null,
   endSearchLink: null,
 
+  preparingString: null,
+  loadingDataString: null,
+  searchingString: null,
+
   noResultsString: null,
   currentResultString: null,
   endSearchString: null,
@@ -3682,7 +3696,12 @@ const sidebarSearchInfo = initInfo('sidebarSearchInfo', {
   groupResultKindString: null,
 
   state: {
+    workerStatus: null,
+    searchStage: null,
+
     stoppedTypingTimeout: null,
+
+    indexDownloadStatuses: Object.create(null),
   },
 
   session: {
@@ -3711,6 +3730,15 @@ function getSidebarSearchReferences() {
   const findString = classPart =>
     info.searchBox.querySelector(`.wiki-search-${classPart}-string`);
 
+  info.preparingString =
+    findString('preparing');
+
+  info.loadingDataString =
+    findString('loading-data');
+
+  info.searchingString =
+    findString('searching');
+
   info.noResultsString =
     findString('no-results');
 
@@ -3735,9 +3763,28 @@ function addSidebarSearchInternalListeners() {
 
   if (!info.searchBox) return;
 
-  wikiSearchInfo.event.whenDownloadsBegin.push(updateSidebarSearchStatus);
-  wikiSearchInfo.event.whenDownloadProgresses.push(updateSidebarSearchStatus);
-  wikiSearchInfo.event.whenDownloadEnds.push(updateSidebarSearchStatus);
+  wikiSearchInfo.event.whenWorkerAlive.push(
+    trackSidebarSearchWorkerAlive,
+    updateSidebarSearchStatus);
+
+  wikiSearchInfo.event.whenWorkerReady.push(
+    trackSidebarSearchWorkerReady,
+    updateSidebarSearchStatus);
+
+  wikiSearchInfo.event.whenWorkerFailsToInitialize.push(
+    trackSidebarSearchWorkerFailsToInitialize,
+    updateSidebarSearchStatus);
+
+  wikiSearchInfo.event.whenDownloadsBegin.push(
+    trackSidebarSearchDownloadsBegin,
+    updateSidebarSearchStatus);
+
+  wikiSearchInfo.event.whenDownloadProgresses.push(
+    updateSidebarSearchStatus);
+
+  wikiSearchInfo.event.whenDownloadEnds.push(
+    trackSidebarSearchDownloadEnds,
+    updateSidebarSearchStatus);
 }
 
 function mutateSidebarSearchContent() {
@@ -3745,6 +3792,39 @@ function mutateSidebarSearchContent() {
 
   if (!info.searchBox) return;
 
+  // Progress section
+
+  info.progressRule =
+    document.createElement('hr');
+
+  info.progressContainer =
+    document.createElement('div');
+
+  info.progressContainer.classList.add('wiki-search-progress-container');
+
+  cssProp(info.progressRule, 'display', 'none');
+  cssProp(info.progressContainer, 'display', 'none');
+
+  info.progressLabel =
+    document.createElement('label');
+
+  info.progressLabel.classList.add('wiki-search-progress-label');
+  info.progressLabel.htmlFor = 'wiki-search-progress-bar';
+
+  info.progressBar =
+    document.createElement('progress');
+
+  info.progressBar.classList.add('wiki-search-progress-bar');
+  info.progressBar.id = 'wiki-search-progress-bar';
+
+  info.progressContainer.appendChild(info.progressLabel);
+  info.progressContainer.appendChild(info.progressBar);
+
+  info.searchBox.appendChild(info.progressRule);
+  info.searchBox.appendChild(info.progressContainer);
+
+  // Results section
+
   info.resultsRule =
     document.createElement('hr');
 
@@ -3766,11 +3846,11 @@ function mutateSidebarSearchContent() {
   info.searchBox.appendChild(info.resultsRule);
   info.searchBox.appendChild(info.resultsContainer);
 
+  // End search section
+
   info.endSearchRule =
     document.createElement('hr');
 
-  info.searchBox.appendChild(info.endSearchRule);
-
   info.endSearchLine =
     document.createElement('p');
 
@@ -3844,6 +3924,50 @@ function initializeSidebarSearchState() {
   }
 }
 
+function trackSidebarSearchWorkerAlive() {
+  const {state} = sidebarSearchInfo;
+
+  state.workerStatus = 'alive';
+}
+
+function trackSidebarSearchWorkerReady() {
+  const {state} = sidebarSearchInfo;
+
+  state.workerStatus = 'ready';
+  state.searchStage = 'searching';
+}
+
+function trackSidebarSearchWorkerFailsToInitialize() {
+  const {state} = sidebarSearchInfo;
+
+  state.workerStatus = 'failed';
+}
+
+function trackSidebarSearchDownloadsBegin(event) {
+  const {state} = sidebarSearchInfo;
+
+  if (event.context === 'search-indexes') {
+    for (const key of event.keys) {
+      state.indexDownloadStatuses[key] = 'active';
+    }
+  }
+}
+
+function trackSidebarSearchDownloadEnds(event) {
+  const {state} = sidebarSearchInfo;
+
+  if (event.context === 'search-indexes') {
+    state.indexDownloadStatuses[event.key] = 'complete';
+
+    const statuses = Object.values(state.indexDownloadStatuses);
+    if (statuses.every(status => status === 'complete')) {
+      for (const key of Object.keys(state.indexDownloadStatuses)) {
+        delete state.indexDownloadStatuses[key];
+      }
+    }
+  }
+}
+
 async function activateSidebarSearch(query) {
   const {session, settings, state} = sidebarSearchInfo;
 
@@ -3852,8 +3976,17 @@ async function activateSidebarSearch(query) {
     state.stoppedTypingTimeout = null;
   }
 
+  state.searchStage =
+    (state.workerStatus === 'ready'
+      ? 'searching'
+      : 'preparing');
+  updateSidebarSearchStatus();
+
   const results = await searchAll(query, {enrich: true});
 
+  state.searchStage = 'complete';
+  updateSidebarSearchStatus();
+
   session.activeQuery = query;
 
   const stringifiedResults = JSON.stringify(results);
@@ -3877,6 +4010,8 @@ function clearSidebarSearch() {
 
   info.searchInput.value = '';
 
+  state.searchStage = null;
+
   session.activeQuery = '';
   session.activeQueryResults = '';
 
@@ -3890,7 +4025,62 @@ function updateSidebarSearchStatus() {
   const searchIndexDownloads =
     getSearchWorkerDownloadContext('search-indexes');
 
-  console.log('Display:', searchIndexDownloads);
+  const downloadProgressValues =
+    Object.values(searchIndexDownloads ?? {});
+
+  if (downloadProgressValues.some(v => v < 1.00)) {
+    const total = Object.keys(state.indexDownloadStatuses).length;
+    const sum = accumulateSum(downloadProgressValues);
+    showSidebarSearchProgress(
+      sum / total,
+      templateContent(info.loadingDataString));
+
+    return;
+  }
+
+  if (state.searchStage === 'preparing') {
+    showSidebarSearchProgress(
+      null,
+      templateContent(info.preparingString));
+
+    return;
+  }
+
+  if (state.searchStage === 'searching') {
+    showSidebarSearchProgress(
+      null,
+      templateContent(info.searchingString));
+
+    return;
+  }
+
+  hideSidebarSearchProgress();
+}
+
+function showSidebarSearchProgress(progress, label) {
+  const info = sidebarSearchInfo;
+
+  cssProp(info.progressRule, 'display', null);
+  cssProp(info.progressContainer, 'display', null);
+
+  if (progress === null) {
+    info.progressBar.removeAttribute('value');
+  } else {
+    info.progressBar.value = progress;
+  }
+
+  while (info.progressLabel.firstChild) {
+    info.progressLabel.firstChild.remove();
+  }
+
+  info.progressLabel.appendChild(label);
+}
+
+function hideSidebarSearchProgress() {
+  const info = sidebarSearchInfo;
+
+  cssProp(info.progressRule, 'display', 'none');
+  cssProp(info.progressContainer, 'display', 'none');
 }
 
 function showSidebarSearchResults(results) {
diff --git a/src/strings-default.yaml b/src/strings-default.yaml
index 23f67ce6..aaa74dca 100644
--- a/src/strings-default.yaml
+++ b/src/strings-default.yaml
@@ -622,6 +622,10 @@ misc:
   search:
     placeholder: "Search for anything"
 
+    preparing: "Preparing..."
+    loadingData: "Loading data..."
+    searching: "Searching..."
+
     noResults: >-
       No results for this query, sorry!
       Check spelling and use complete words.