MediaWiki:DailyQuestTracker.js: Difference between revisions

From CoraTO Wiki - Official Wiki
Jump to navigation Jump to search
Created page with " 'use strict'; (function() { function safeGetElementById(id) { try { return document.getElementById(id); } catch (_) { return null; } } function safeQuerySelectorAll(selector, context = document) { try { return context.querySelectorAll(selector); } catch (_) { return []; } } function safeQuerySelector(selector, context = document) { try { return context.querySelector(selector); } catch (_) { return null; } } function safeGetLocalStorage(key, de..."
 
No edit summary
Line 1: Line 1:
'use strict';
(function () {
  'use strict';


(function() {
  const STORAGE_KEY = 'cora.questTracker.dailyQuest.v1';
   function safeGetElementById(id) {
  const SELECTORS = {
     try { return document.getElementById(id); } catch (_) { return null; }
    locationSection: '.location-section',
    questCheckbox: '.quest-checkbox',
    trackerRootId: 'tab-tracker',
    totalProgress: '#total-progress',
    completionPercentage: '#completion-percentage',
    progressFill: '#progress-fill',
    resetButton: '#reset-progress'
  };
 
   function safeQuerySelector(selector, context) {
     try {
      return (context || document).querySelector(selector);
    } catch (_) {
      return null;
    }
   }
   }
   function safeQuerySelectorAll(selector, context = document) {
 
     try { return context.querySelectorAll(selector); } catch (_) { return []; }
   function safeQuerySelectorAll(selector, context) {
     try {
      return Array.from((context || document).querySelectorAll(selector));
    } catch (_) {
      return [];
    }
   }
   }
   function safeQuerySelector(selector, context = document) {
 
     try { return context.querySelector(selector); } catch (_) { return null; }
   function safeJsonParse(value) {
     try {
      return JSON.parse(value);
    } catch (_) {
      return null;
    }
   }
   }
   function safeGetLocalStorage(key, defaultValue = '') {
 
     try { return localStorage.getItem(key) || defaultValue; } catch (_) { return defaultValue; }
   function safeGetLocalStorage(key) {
     try {
      return localStorage.getItem(key);
    } catch (_) {
      return null;
    }
   }
   }
   function safeSetLocalStorage(key, value) {
   function safeSetLocalStorage(key, value) {
     try { localStorage.setItem(key, value); return true; } catch (_) { return false; }
     try {
      localStorage.setItem(key, value);
    } catch (_) {}
  }
 
  function safeRemoveLocalStorage(key) {
    try {
      localStorage.removeItem(key);
    } catch (_) {}
  }
 
  function getQuestId(checkbox, index) {
    const dataQuest = checkbox.getAttribute('data-quest');
    if (dataQuest) return dataQuest;
    const name = checkbox.getAttribute('name');
    if (name) return name;
    const id = checkbox.getAttribute('id');
    if (id) return id;
    return `quest-${index}`;
  }
 
  function collectSections(root) {
    const sections = safeQuerySelectorAll(SELECTORS.locationSection, root);
    return sections
      .map((section) => ({
        section,
        checkboxes: safeQuerySelectorAll(SELECTORS.questCheckbox, section)
      }))
      .filter((x) => x.checkboxes.length > 0);
  }
 
  function loadState(checkboxes) {
    const raw = safeGetLocalStorage(STORAGE_KEY);
    const parsed = raw ? safeJsonParse(raw) : null;
    const state = parsed && typeof parsed === 'object' ? parsed : {};
 
    checkboxes.forEach((cb, index) => {
      const key = getQuestId(cb, index);
      cb.checked = !!state[key];
    });
  }
 
  function saveState(checkboxes) {
    const out = {};
    checkboxes.forEach((cb, index) => {
      const key = getQuestId(cb, index);
      out[key] = !!cb.checked;
    });
    safeSetLocalStorage(STORAGE_KEY, JSON.stringify(out));
   }
   }


   const STORAGE_KEY = 'shamanJiaQuestProgress';
   function applySingleSelectionPerSection(sections) {
  const SELECTORS = {
     sections.forEach(({ checkboxes }) => {
     QUEST_CHECKBOX: '.quest-checkbox',
      const checked = checkboxes.filter((cb) => cb.checked);
    REGION_CHECKBOX: '.region-checkbox',
      const anyChecked = checked.length > 0;
    LOCATION_SECTION: '.location-section',
    LOCATION_TITLE: '.location-title'
  };


  const QuestTracker = {
       if (!anyChecked) {
    initialized: false,
         checkboxes.forEach((cb) => {
    init: function() {
           cb.disabled = false;
      if (this.initialized) return;
      const questCheckboxes = safeQuerySelectorAll(SELECTORS.QUEST_CHECKBOX);
       if (!questCheckboxes || questCheckboxes.length === 0) return;
      this.registerRegions();
      this.loadQuestProgress();
      this.setupEventListeners();
      this.updateProgressDisplay();
      this.initialized = true;
    },
    registerRegions: function() {
      const sections = safeQuerySelectorAll(SELECTORS.LOCATION_SECTION);
      Array.from(sections).forEach(section => {
         const titleEl = safeQuerySelector(SELECTORS.LOCATION_TITLE, section);
        const name = titleEl ? titleEl.textContent.trim() : '';
        const regionId = name ? name.toLowerCase().replace(/\s+/g, '-') : '';
        const cbs = safeQuerySelectorAll(SELECTORS.QUEST_CHECKBOX, section);
        Array.from(cbs).forEach(cb => { if (regionId) cb.setAttribute('data-region', regionId); });
        const rc = safeQuerySelector(SELECTORS.REGION_CHECKBOX, section);
        if (rc && regionId) rc.setAttribute('data-region', regionId);
      });
    },
    setupEventListeners: function() {
      const questCheckboxes = safeQuerySelectorAll(SELECTORS.QUEST_CHECKBOX);
      Array.from(questCheckboxes).forEach(cb => {
        cb.addEventListener('change', () => {
           const section = cb.closest('.location-section');
          const items = section ? safeQuerySelectorAll(SELECTORS.QUEST_CHECKBOX, section) : [];
          const arr = Array.from(items);
          if (cb.checked) {
            arr.forEach(other => { if (other !== cb) { other.checked = false; other.disabled = true; } });
          } else {
            const anyChecked = arr.some(x => x.checked);
            if (!anyChecked) arr.forEach(other => { other.disabled = false; });
          }
          this.saveQuestProgress();
          this.syncRegionToggles();
          this.applyDisabledStateAllRegions();
          this.updateProgressDisplay();
         });
         });
      });
         return;
      const regionCheckboxes = safeQuerySelectorAll(SELECTORS.REGION_CHECKBOX);
      Array.from(regionCheckboxes).forEach(rcb => {
         rcb.addEventListener('change', () => { this.handleRegionToggle(rcb); });
      });
      const resetButton = safeGetElementById('reset-progress');
      if (resetButton) {
        resetButton.addEventListener('click', () => { this.resetAllProgress(); });
       }
       }
    },
 
    handleRegionToggle: function(regionCb) {
       const chosen = checked[0];
      const region = regionCb ? regionCb.getAttribute('data-region') : '';
       if (checked.length > 1) {
       const checked = !!(regionCb && regionCb.checked);
         checked.slice(1).forEach((cb) => {
      if (!region) return;
          cb.checked = false;
      this.setRegionChecked(region, checked);
        });
    },
    setRegionChecked: function(region, checked) {
      const items = safeQuerySelectorAll(SELECTORS.QUEST_CHECKBOX + `[data-region="${region}"]`);
      const arr = Array.from(items);
       if (checked) {
         arr.forEach(cb => { cb.checked = false; cb.disabled = true; });
        if (arr.length > 0) { arr[0].checked = true; arr[0].disabled = false; }
      } else {
        arr.forEach(cb => { cb.checked = false; cb.disabled = false; });
       }
       }
      this.saveQuestProgress();
 
       this.syncRegionToggles();
       checkboxes.forEach((cb) => {
      this.applyDisabledStateAllRegions();
         cb.disabled = cb !== chosen;
      this.updateProgressDisplay();
    },
    collectRegions: function() {
      const result = {};
      const questCheckboxes = safeQuerySelectorAll(SELECTORS.QUEST_CHECKBOX);
      Array.from(questCheckboxes).forEach(cb => {
         const region = cb.getAttribute('data-region') || 'global';
        if (!result[region]) result[region] = [];
        result[region].push(cb);
       });
       });
      return result;
    });
    },
  }
    loadQuestProgress: function() {
 
      const saved = safeGetLocalStorage(STORAGE_KEY);
  function updateProgress(sections, root) {
      if (saved) {
    const totalSections = 12;
        try {
    const completedSectionsRaw = sections.filter(({ checkboxes }) => checkboxes.some((cb) => cb.checked)).length;
          const data = JSON.parse(saved);
    const completedSections = Math.min(completedSectionsRaw, totalSections);
          Object.keys(data).forEach(id => {
    const percentage = totalSections > 0 ? Math.round((completedSections / totalSections) * 100) : 0;
            const cb = safeQuerySelector(`[data-quest="${id}"]`);
 
            if (cb) cb.checked = !!data[id];
    const totalProgressEl = safeQuerySelector(SELECTORS.totalProgress, root);
          });
    const completionEl = safeQuerySelector(SELECTORS.completionPercentage, root);
        } catch (_) {}
    const fillEl = safeQuerySelector(SELECTORS.progressFill, root);
      }
 
      this.enforceSingleSelectionPerRegion();
     if (totalProgressEl) totalProgressEl.textContent = `${completedSections}/${totalSections}`;
      this.syncRegionToggles();
     if (completionEl) completionEl.textContent = `${percentage}%`;
      this.applyDisabledStateAllRegions();
    if (fillEl) fillEl.style.width = `${percentage}%`;
     },
  }
     saveQuestProgress: function() {
 
      const questCheckboxes = safeQuerySelectorAll(SELECTORS.QUEST_CHECKBOX);
  function wireEvents(sections, allCheckboxes, root) {
      const out = {};
    allCheckboxes.forEach((cb) => {
      Array.from(questCheckboxes).forEach(cb => {
      cb.addEventListener('change', () => {
        const id = cb.getAttribute('data-quest');
        applySingleSelectionPerSection(sections);
         if (id) out[id] = !!cb.checked;
         saveState(allCheckboxes);
        updateProgress(sections, root);
       });
       });
      try { safeSetLocalStorage(STORAGE_KEY, JSON.stringify(out)); } catch (_) {}
     });
     },
 
    updateProgressDisplay: function() {
     const resetBtn = safeQuerySelector(SELECTORS.resetButton, root);
      const regions = this.collectRegions();
     if (resetBtn) {
      const keys = Object.keys(regions).filter(r => r !== 'global');
       resetBtn.addEventListener('click', () => {
      const regionCount = keys.length;
         allCheckboxes.forEach((cb) => {
      const completedRegions = keys.filter(r => regions[r].some(cb => cb.checked)).length;
          cb.checked = false;
      const completionPercentage = regionCount > 0 ? Math.round((completedRegions / regionCount) * 100) : 0;
           cb.disabled = false;
      const totalProgressElement = safeGetElementById('total-progress');
         });
      const completionPercentageElement = safeGetElementById('completion-percentage');
        safeRemoveLocalStorage(STORAGE_KEY);
      const progressFillElement = safeGetElementById('progress-fill');
        updateProgress(sections, root);
      if (totalProgressElement) totalProgressElement.textContent = `${completedRegions}/${regionCount}`;
      if (completionPercentageElement) completionPercentageElement.textContent = `${completionPercentage}%`;
      if (progressFillElement) progressFillElement.style.width = `${completionPercentage}%`;
     },
    resetAllProgress: function() {
      const questCheckboxes = safeQuerySelectorAll(SELECTORS.QUEST_CHECKBOX);
      Array.from(questCheckboxes).forEach(cb => { cb.checked = false; cb.disabled = false; });
      try { localStorage.removeItem(STORAGE_KEY); } catch (_) {}
      this.syncRegionToggles();
      this.applyDisabledStateAllRegions();
      this.updateProgressDisplay();
     },
    syncRegionToggles: function() {
       const regionCheckboxes = safeQuerySelectorAll(SELECTORS.REGION_CHECKBOX);
      Array.from(regionCheckboxes).forEach(rc => {
        const region = rc.getAttribute('data-region');
        if (!region) return;
        const items = safeQuerySelectorAll(SELECTORS.QUEST_CHECKBOX + `[data-region="${region}"]`);
         const arr = Array.from(items);
        rc.checked = arr.some(cb => cb.checked);
      });
    },
    enforceSingleSelectionPerRegion: function() {
      const sections = safeQuerySelectorAll(SELECTORS.LOCATION_SECTION);
      Array.from(sections).forEach(section => {
        const items = Array.from(safeQuerySelectorAll(SELECTORS.QUEST_CHECKBOX, section));
        const checked = items.filter(cb => cb.checked);
        if (checked.length > 1) {
           checked.slice(1).forEach(cb => { cb.checked = false; });
         }
      });
      this.saveQuestProgress();
    },
    applyDisabledStateAllRegions: function() {
      const sections = safeQuerySelectorAll(SELECTORS.LOCATION_SECTION);
      Array.from(sections).forEach(section => {
        const items = Array.from(safeQuerySelectorAll(SELECTORS.QUEST_CHECKBOX, section));
        const selected = items.find(cb => cb.checked);
        if (selected) {
          items.forEach(cb => { cb.disabled = cb !== selected; });
        } else {
          items.forEach(cb => { cb.disabled = false; });
        }
       });
       });
     }
     }
   };
   }
 
  function init() {
    const root = document.getElementById(SELECTORS.trackerRootId);
    if (!root) return;
 
    const allCheckboxes = safeQuerySelectorAll(SELECTORS.questCheckbox, root);
    if (allCheckboxes.length === 0) return;
 
    const sections = collectSections(root);
    if (sections.length === 0) return;
 
    loadState(allCheckboxes);
    applySingleSelectionPerSection(sections);
    saveState(allCheckboxes);
    updateProgress(sections, root);
    wireEvents(sections, allCheckboxes, root);
  }


  window.QuestTracker = QuestTracker;
  window.initQuestTracker = QuestTracker.init.bind(QuestTracker);
  window.loadQuestProgress = QuestTracker.loadQuestProgress.bind(QuestTracker);
  window.saveQuestProgress = QuestTracker.saveQuestProgress.bind(QuestTracker);
  window.updateProgressDisplay = QuestTracker.updateProgressDisplay.bind(QuestTracker);
  window.resetAllProgress = QuestTracker.resetAllProgress.bind(QuestTracker);
   if (document.readyState === 'loading') {
   if (document.readyState === 'loading') {
     document.addEventListener('DOMContentLoaded', function() { QuestTracker.init(); });
     document.addEventListener('DOMContentLoaded', init);
   } else {
   } else {
     QuestTracker.init();
     init();
   }
   }
})();
})();

Revision as of 05:20, 30 December 2025

(function () {
  'use strict';

  const STORAGE_KEY = 'cora.questTracker.dailyQuest.v1';
  const SELECTORS = {
    locationSection: '.location-section',
    questCheckbox: '.quest-checkbox',
    trackerRootId: 'tab-tracker',
    totalProgress: '#total-progress',
    completionPercentage: '#completion-percentage',
    progressFill: '#progress-fill',
    resetButton: '#reset-progress'
  };

  function safeQuerySelector(selector, context) {
    try {
      return (context || document).querySelector(selector);
    } catch (_) {
      return null;
    }
  }

  function safeQuerySelectorAll(selector, context) {
    try {
      return Array.from((context || document).querySelectorAll(selector));
    } catch (_) {
      return [];
    }
  }

  function safeJsonParse(value) {
    try {
      return JSON.parse(value);
    } catch (_) {
      return null;
    }
  }

  function safeGetLocalStorage(key) {
    try {
      return localStorage.getItem(key);
    } catch (_) {
      return null;
    }
  }

  function safeSetLocalStorage(key, value) {
    try {
      localStorage.setItem(key, value);
    } catch (_) {}
  }

  function safeRemoveLocalStorage(key) {
    try {
      localStorage.removeItem(key);
    } catch (_) {}
  }

  function getQuestId(checkbox, index) {
    const dataQuest = checkbox.getAttribute('data-quest');
    if (dataQuest) return dataQuest;
    const name = checkbox.getAttribute('name');
    if (name) return name;
    const id = checkbox.getAttribute('id');
    if (id) return id;
    return `quest-${index}`;
  }

  function collectSections(root) {
    const sections = safeQuerySelectorAll(SELECTORS.locationSection, root);
    return sections
      .map((section) => ({
        section,
        checkboxes: safeQuerySelectorAll(SELECTORS.questCheckbox, section)
      }))
      .filter((x) => x.checkboxes.length > 0);
  }

  function loadState(checkboxes) {
    const raw = safeGetLocalStorage(STORAGE_KEY);
    const parsed = raw ? safeJsonParse(raw) : null;
    const state = parsed && typeof parsed === 'object' ? parsed : {};

    checkboxes.forEach((cb, index) => {
      const key = getQuestId(cb, index);
      cb.checked = !!state[key];
    });
  }

  function saveState(checkboxes) {
    const out = {};
    checkboxes.forEach((cb, index) => {
      const key = getQuestId(cb, index);
      out[key] = !!cb.checked;
    });
    safeSetLocalStorage(STORAGE_KEY, JSON.stringify(out));
  }

  function applySingleSelectionPerSection(sections) {
    sections.forEach(({ checkboxes }) => {
      const checked = checkboxes.filter((cb) => cb.checked);
      const anyChecked = checked.length > 0;

      if (!anyChecked) {
        checkboxes.forEach((cb) => {
          cb.disabled = false;
        });
        return;
      }

      const chosen = checked[0];
      if (checked.length > 1) {
        checked.slice(1).forEach((cb) => {
          cb.checked = false;
        });
      }

      checkboxes.forEach((cb) => {
        cb.disabled = cb !== chosen;
      });
    });
  }

  function updateProgress(sections, root) {
    const totalSections = 12;
    const completedSectionsRaw = sections.filter(({ checkboxes }) => checkboxes.some((cb) => cb.checked)).length;
    const completedSections = Math.min(completedSectionsRaw, totalSections);
    const percentage = totalSections > 0 ? Math.round((completedSections / totalSections) * 100) : 0;

    const totalProgressEl = safeQuerySelector(SELECTORS.totalProgress, root);
    const completionEl = safeQuerySelector(SELECTORS.completionPercentage, root);
    const fillEl = safeQuerySelector(SELECTORS.progressFill, root);

    if (totalProgressEl) totalProgressEl.textContent = `${completedSections}/${totalSections}`;
    if (completionEl) completionEl.textContent = `${percentage}%`;
    if (fillEl) fillEl.style.width = `${percentage}%`;
  }

  function wireEvents(sections, allCheckboxes, root) {
    allCheckboxes.forEach((cb) => {
      cb.addEventListener('change', () => {
        applySingleSelectionPerSection(sections);
        saveState(allCheckboxes);
        updateProgress(sections, root);
      });
    });

    const resetBtn = safeQuerySelector(SELECTORS.resetButton, root);
    if (resetBtn) {
      resetBtn.addEventListener('click', () => {
        allCheckboxes.forEach((cb) => {
          cb.checked = false;
          cb.disabled = false;
        });
        safeRemoveLocalStorage(STORAGE_KEY);
        updateProgress(sections, root);
      });
    }
  }

  function init() {
    const root = document.getElementById(SELECTORS.trackerRootId);
    if (!root) return;

    const allCheckboxes = safeQuerySelectorAll(SELECTORS.questCheckbox, root);
    if (allCheckboxes.length === 0) return;

    const sections = collectSections(root);
    if (sections.length === 0) return;

    loadState(allCheckboxes);
    applySingleSelectionPerSection(sections);
    saveState(allCheckboxes);
    updateProgress(sections, root);
    wireEvents(sections, allCheckboxes, root);
  }

  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', init);
  } else {
    init();
  }
})();