Zum Inhalt springen

MediaWiki:Gadget-LabelScan.js: Unterschied zwischen den Versionen

Aus ADOS Wiki
Keine Bearbeitungszusammenfassung
Keine Bearbeitungszusammenfassung
Zeile 1: Zeile 1:
// --- Index laden (robust, mit Fallback, ohne Cache) ------------------------
async function loadLabelIndex() {
  const page = 'MediaWiki:Gadget-LabelScan-index.json';
  // Primär: canonical raw-URL
  const url1 = mw.util.getUrl(page, {
    action: 'raw',
    ctype: 'application/json',
    maxage: 0,
    smaxage: 0
  });
  // Fallback: wgScript (index.php) + Cachebuster
  const url2 = (mw.config.get('wgScript') || '/index.php') +
    '?title=' + encodeURIComponent(page) +
    '&action=raw&ctype=application/json&_=' + Date.now();
  const tried = [];
  async function tryFetch(url) {
    tried.push(url);
    const r = await fetch(url, { cache: 'no-store' });
    if (!r.ok) throw new Error('HTTP ' + r.status);
    let text = await r.text();
    // BOM entfernen, falls vorhanden
    if (text.charCodeAt(0) === 0xFEFF) text = text.slice(1);
    let data;
    try { data = JSON.parse(text); }
    catch (e) {
      // Manchmal kommt <pre>…</pre> o.Ä. – hart strippen:
      const stripped = text.replace(/^.*?\[/s, '[').replace(/\].*$/s, ']');
      data = JSON.parse(stripped);
    }
    if (!Array.isArray(data)) throw new Error('Kein Array im Index');
    return data;
  }
  try {
    try {
      return await tryFetch(url1);
    } catch (e1) {
      console.warn('[LabelScan] Primäre Raw-URL fehlgeschlagen:', e1?.message);
      return await tryFetch(url2);
    }
  } catch (e) {
    console.error('[LabelScan] Konnte Index nicht laden:', e?.message, '\nVersucht:', tried);
    throw e;
  }
}
// Beim Start laden:
loadLabelIndex().then(list => {
  window.ADOS_LABEL_INDEX = list;
  console.log(`[LabelScan] Index geladen: ${list.length} Einträge`);
}).catch(() => {
  const box = document.getElementById('ados-scan-results');
  if (box) box.innerHTML =
    '<div class="ados-hit">Index konnte nicht geladen werden. Bitte Seite neu laden (Strg+F5) oder Administrator informieren.</div>';
});
/* global mw */
/* global mw */
(function () {
(function () {

Version vom 8. November 2025, 15:42 Uhr

// --- Index laden (robust, mit Fallback, ohne Cache) ------------------------
async function loadLabelIndex() {
  const page = 'MediaWiki:Gadget-LabelScan-index.json';

  // Primär: canonical raw-URL
  const url1 = mw.util.getUrl(page, {
    action: 'raw',
    ctype: 'application/json',
    maxage: 0,
    smaxage: 0
  });

  // Fallback: wgScript (index.php) + Cachebuster
  const url2 = (mw.config.get('wgScript') || '/index.php') +
    '?title=' + encodeURIComponent(page) +
    '&action=raw&ctype=application/json&_=' + Date.now();

  const tried = [];
  async function tryFetch(url) {
    tried.push(url);
    const r = await fetch(url, { cache: 'no-store' });
    if (!r.ok) throw new Error('HTTP ' + r.status);
    let text = await r.text();
    // BOM entfernen, falls vorhanden
    if (text.charCodeAt(0) === 0xFEFF) text = text.slice(1);
    let data;
    try { data = JSON.parse(text); }
    catch (e) {
      // Manchmal kommt <pre>…</pre> o.Ä. – hart strippen:
      const stripped = text.replace(/^.*?\[/s, '[').replace(/\].*$/s, ']');
      data = JSON.parse(stripped);
    }
    if (!Array.isArray(data)) throw new Error('Kein Array im Index');
    return data;
  }

  try {
    try {
      return await tryFetch(url1);
    } catch (e1) {
      console.warn('[LabelScan] Primäre Raw-URL fehlgeschlagen:', e1?.message);
      return await tryFetch(url2);
    }
  } catch (e) {
    console.error('[LabelScan] Konnte Index nicht laden:', e?.message, '\nVersucht:', tried);
    throw e;
  }
}

// Beim Start laden:
loadLabelIndex().then(list => {
  window.ADOS_LABEL_INDEX = list;
  console.log(`[LabelScan] Index geladen: ${list.length} Einträge`);
}).catch(() => {
  const box = document.getElementById('ados-scan-results');
  if (box) box.innerHTML =
    '<div class="ados-hit">Index konnte nicht geladen werden. Bitte Seite neu laden (Strg+F5) oder Administrator informieren.</div>';
});
























/* global mw */
(function () {
  'use strict';

  // =========== UI Helpers ===========
  function $(id){ return document.getElementById(id); }
  function setStatus(t){ const el=$('ados-scan-status'); if(el) el.textContent=t||''; }
  function setProgress(p){ const bar=$('ados-scan-progress'); if(!bar) return; if(p==null){bar.hidden=true;bar.value=0;} else {bar.hidden=false;bar.value=p;} }
  function showPreview(file){
    const prev=$('ados-scan-preview'); if(!prev) return;
    const url=URL.createObjectURL(file);
    prev.innerHTML = `<img src="${url}" alt="Vorschau" style="max-width:100%; border-radius:8px; border:1px solid #ccc;">`;
    setStatus('Bild bereit.');
  }
  function esc(s){ return mw.html.escape(String(s||'')); }

  // =========== Datei-Auswahl Zustand ===========
  let PICKED_FILE = null;
  function getSelectedFile() {
    const cam=$('ados-scan-file-camera'), gal=$('ados-scan-file-gallery');
    return PICKED_FILE || (cam&&cam.files&&cam.files[0]) || (gal&&gal.files&&gal.files[0]) || null;
  }

  // =========== aHash (64-bit, 16 Hex-Zeichen) ===========
  async function fileToImage (file) {
    return new Promise((resolve, reject) => {
      const img = new Image();
      img.onload = () => resolve(img);
      img.onerror = reject;
      img.src = URL.createObjectURL(file);
    });
  }
  function computeAHash(img) {
    // auf 8x8 skalieren, grau, Durchschnitt -> 64 bits
    const S=8;
    const c=document.createElement('canvas'); c.width=c.height=S;
    const ctx=c.getContext('2d');
    ctx.drawImage(img, 0, 0, S, S);
    const data = ctx.getImageData(0,0,S,S).data;
    const gray=new Array(S*S);
    for(let i=0, j=0;i<data.length;i+=4, j++){
      gray[j]= 0.299*data[i]+0.587*data[i+1]+0.114*data[i+2];
    }
    const avg = gray.reduce((a,b)=>a+b,0)/gray.length;
    let bits = '';
    for(let k=0;k<gray.length;k++){
      bits += (gray[k] > avg) ? '1' : '0';
    }
    // 64 bits -> 16 Hex
    let hex='';
    for(let i=0;i<64;i+=4){
      hex += parseInt(bits.slice(i,i+4),2).toString(16);
    }
    return hex;
  }
  function hammingHex(h1, h2){
    const n = Math.min(h1.length, h2.length);
    let d=0;
    for(let i=0;i<n;i++){
      const x = parseInt(h1[i],16) ^ parseInt(h2[i],16);
      d += x.toString(2).replace(/0/g,'').length;
    }
    return d + (h1.length>n? (h1.length-n)*4 : 0) + (h2.length>n? (h2.length-n)*4 : 0);
  }

  // =========== Index laden ===========
  async function loadIndex(){
    const url = mw.util.getUrl('MediaWiki:Gadget-LabelScan-index.json', { action:'raw', ctype:'application/json' });
    const res = await fetch(url, { cache: 'no-store' });
    if (!res.ok) throw new Error('Index konnte nicht geladen werden: '+res.status);
    return res.json();
  }

  // =========== Ergebnisse rendern ===========
  function renderResults(items){
    const box = $('ados-scan-results');
    if (!box) return;
    box.innerHTML = '';
    if (!items || !items.length){
      box.innerHTML = '<div class="empty">Keine klaren Treffer. Bitte anderes Foto oder manuell suchen.</div>';
      return;
    }
    items.forEach(it=>{
      const href = mw.util.getUrl(it.title.replace(/ /g,'_'));
      box.insertAdjacentHTML('beforeend', `
        <div class="ados-hit" style="display:inline-block; width:170px; margin:8px; text-align:center;">
          <a href="${href}">
            <img src="${it.thumb}" alt="${esc(it.title)}" style="width:150px; border-radius:8px; border:1px solid #ccc; background:#fff;">
            <div style="margin-top:6px; font-weight:600;">${esc(it.title)}</div>
          </a>
          <div style="font-size:.85em; color:#666;">Distanz: ${it.dist}</div>
        </div>
      `);
    });
  }

  // =========== Verkettete Erkennung ===========
  async function runMatchWorkflow(){
    const file = getSelectedFile();
    if (!file) { alert('Bitte Foto aufnehmen oder Datei wählen.'); return; }

    try {
      setStatus('Bereite Bild vor …'); setProgress(null); // (keine echte Progressanzeige nötig)
      const img = await fileToImage(file);

      setStatus('Berechne Fingerabdruck …');
      const ahash = computeAHash(img);

      setStatus('Lade Index …');
      const index = await loadIndex();

      setStatus('Vergleiche …');
      const scored = index.map(it => {
        const dist = hammingHex(ahash, String(it.phash||''));
        return { ...it, dist };
      }).sort((a,b)=>a.dist - b.dist);

      // kleine Heuristik: nur „plausible“ Treffer anzeigen
      const BEST = scored.slice(0, 8);
      const THRESH = 18; // ~ gut unterscheidbar bei 64-bit aHash
      const filtered = BEST.filter(x => x.dist <= THRESH);
      renderResults(filtered.length ? filtered : BEST);
      setStatus('Fertig.');
    } catch (e) {
      console.error('[LabelScan]', e);
      setStatus('Fehler bei Erkennung.');
    } finally {
      setProgress(null);
    }
  }

  // =========== Dropzone, Buttons, Aktionen ===========
  function wireUI(){
    // Buttons → Inputs
    const btnCam=$('ados-scan-btn-camera'), inCam=$('ados-scan-file-camera');
    const btnGal=$('ados-scan-btn-gallery'), inGal=$('ados-scan-file-gallery');

    if (btnCam && inCam){
      btnCam.addEventListener('click', e=>{ e.preventDefault(); e.stopPropagation(); inCam.click(); });
      inCam.addEventListener('change', ()=>{ if(inCam.files && inCam.files[0]) { PICKED_FILE=inCam.files[0]; showPreview(PICKED_FILE); }});
    }
    if (btnGal && inGal){
      btnGal.addEventListener('click', e=>{ e.preventDefault(); e.stopPropagation(); inGal.click(); });
      inGal.addEventListener('change', ()=>{ if(inGal.files && inGal.files[0]) { PICKED_FILE=inGal.files[0]; showPreview(PICKED_FILE); }});
    }

    // Dropzone
    const drop = $('ados-scan-drop');
    if (drop){
      const stop = ev => { ev.preventDefault(); ev.stopPropagation(); };
      ['dragenter','dragover','dragleave','drop'].forEach(evt => drop.addEventListener(evt, stop));
      drop.addEventListener('drop', ev=>{
        const f = ev.dataTransfer && ev.dataTransfer.files && ev.dataTransfer.files[0];
        if (f){ PICKED_FILE=f; showPreview(f); }
      });
    }

    // Run
    const run=$('ados-scan-run');
    if (run) run.addEventListener('click', e=>{ e.preventDefault(); runMatchWorkflow(); });

    // Reset
    const reset=$('ados-scan-reset');
    if (reset) reset.addEventListener('click', ()=>{
      PICKED_FILE=null;
      if ($('ados-scan-file-camera'))  $('ados-scan-file-camera').value='';
      if ($('ados-scan-file-gallery')) $('ados-scan-file-gallery').value='';
      if ($('ados-scan-preview')) $('ados-scan-preview').innerHTML='<div class="note">Noch keine Vorschau. Wähle ein Foto.</div>';
      if ($('ados-scan-results')) $('ados-scan-results').innerHTML='<div class="empty">Hier erscheinen passende Abfüllungen mit Link ins Wiki.</div>';
      setStatus('Bereit.');
      setProgress(null);
    });
  }

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