Pārlūkot izejas kodu

Add pokemon lookup

Kirk Trombley 3 gadi atpakaļ
vecāks
revīzija
dc09d10c9e
2 mainītis faili ar 138 papildinājumiem un 42 dzēšanām
  1. 57 8
      nearest.html
  2. 81 34
      nearest.js

+ 57 - 8
nearest.html

@@ -3,14 +3,16 @@
     <head>
         <meta charset="utf-8" />
         <title>Pokemon By Color</title>
-        <script src="https://www.unpkg.com/jquery@3.6.0/dist/jquery.min.js"></script>
+        <script src="https://unpkg.com/jquery@3.6.0/dist/jquery.min.js"></script>
         <script src="https://unpkg.com/colorspaces@0.1.5/colorspaces.js"></script>
+        <script src="https://unpkg.com/fuse.js@6.5.3/dist/fuse.js"></script>
         <script src="database.js"></script>
         <script src="nearest.js"></script>
         <script lang="javascript">window.onload = () => { onUpdate(); }</script>
         <style>
             body {
                 width: 100vw;
+                padding-top: 4px;
             }
 
             .container {
@@ -47,7 +49,6 @@
 
             .bycolor {
                 flex: 2;
-                border-left: 4px solid #222;
             }
 
             .bycolor_l1 {
@@ -64,19 +65,59 @@
                 height: 32px;
                 align-items: flex-end;
             }
+
+            .pokemon {
+                display: flex;
+                flex-flow: row nowrap;
+                justify-content: space-between;
+                align-items: flex-end;
+                width: 400px;
+                height: 50px;
+            }
+
+            .color-tile {
+                width: 50px; 
+                height: 32px; 
+                font-size: 10px;
+                text-align: center;
+                display: inline-flex;
+                align-items: center;
+                margin-right: 4px;
+            }
+
+            .pokemon_text {
+                flex: 1;
+                padding-left: 4px;
+                text-align: left;
+            }
+
+            .pkmn-list {
+                list-style-type: none;
+                padding: 0;
+                margin: 0;
+                margin-top: 16px;
+            }
+
+            .bypkmn {
+                border-top: 4px solid #222;
+                border-right: 4px solid #222;
+                margin-top: 32px;
+                padding-top: 8px;
+                padding-right: 8px;
+            }
         </style>
     </head>
     <body>
-        <noscript>Requires javascript</noscript>
+        <noscript>Requires javascript</noscript>    
         <div class="container">
             <div id="left-panel" class="padded panel">
                 <div>
                     Minimizing:
                     <span id="x-term">X(P)</span>
-                    <span> -</span>
+                    <span>-</span>
                     <span id="c-value">2</span>
                     <span id="q-vec">q</span>
-                    <span> · </span>
+                    <span>·</span>
                     <span id="y-vec">Y(P)</span>
                 </div>
                 <form class="panel" onsubmit="onUpdate()" action="#">
@@ -97,9 +138,17 @@
 
                     <div class="container control">
                         <div>Color Space: <span id="color-space">CIELUV</span></div>
-                        <button id="space-toggle" onclick="onToggleSpace()">Swap to RGB</button>
+                        <button id="space-toggle" type="button" onclick="onToggleSpace()">Swap to RGB</button>
                     </div>
                 </form>
+
+                <div class="panel bypkmn">
+                    <form class="container control" onsubmit="onUpdate()" action="#">
+                        <label for="pokemon-name">Search By Pokemon</label>
+                        <input id="pokemon-name" size="15" oninput="onUpdate()">
+                    </form>
+                    <ul id="search-list" class="pkmn-list"></ul>
+                </div>
             </div>
             <div class="padded panel bycolor">
                 <div>
@@ -108,7 +157,7 @@
                 <form class="panel" onsubmit="onUpdate()" action="#">
                     <div class="container bycolor_l1">
                         <button class="padded" type="button" onclick="onRandomColor()">Random color</button>
-                        <input class="margined" size="7" maxlength="7" id="color-input" onchange="onUpdate()" value="#ffffff" />
+                        <input class="margined" size="7" maxlength="7" id="color-input" oninput="onUpdate()" value="#ffffff" />
                         <img src="https://img.pokemondb.net/sprites/sword-shield/icon/bulbasaur.png" />
                     </div>
 
@@ -117,7 +166,7 @@
                         <input type="range" min="1" max="100" value="10" oninput="onUpdate()" id="num-poke">
                     </div>
                 </form>
-                <ul id="best-list"></ul>
+                <ul id="best-list" class="pkmn-list"></ul>
             </div>
         </div>
     </body>

+ 81 - 34
nearest.js

@@ -1,9 +1,13 @@
 const getSprite = pokemon => `https://img.pokemondb.net/sprites/sword-shield/icon/${pokemon}.png`
 
+const titleCase = s => s.charAt(0).toUpperCase() + s.substr(1);
+
 const vectorDot = (u, v) => u.map((x, i) => x * v[i]).reduce((x, y) => x + y);
 
 const vectorMag = v => Math.sqrt(vectorDot(v, v));
 
+const pokemonLookup = new Fuse(database, { keys: [ "name" ] });
+
 // hex codes already include leading # in these functions
 // rgb values are [0, 1] in these functions
 const hex2luv = $.colorspaces.converter("hex", "CIELUV");
@@ -20,6 +24,43 @@ const getNormedScorer = (c, q) => {
 };
 const getUnnormedScorer = (c, q) => yVec => c * vectorDot(q, yVec);
 
+// create a tile of a given hex color
+const createTile = hexColor => {
+  const tile = document.createElement("div");
+  tile.setAttribute("class", "color-tile");
+  tile.setAttribute("style", `background-color: ${hexColor};`)
+  tile.textContent = hexColor;
+  return tile;      
+}
+
+const createPokemon = ({ name, score, yRGB, yLUV }) => {
+  const img = document.createElement("img");
+  img.setAttribute("src", getSprite(name));
+
+  const titleName = titleCase(name);
+  const text = score ? `${titleName}: ${score.toFixed(3)}` : titleName
+
+  const pkmn = document.createElement("div");
+  pkmn.setAttribute("class", "pokemon");
+  pkmn.appendChild(img);
+  const textSpan = document.createElement("span");
+  textSpan.textContent = text;
+  textSpan.setAttribute("class", "pokemon_text");
+  pkmn.appendChild(textSpan);
+  pkmn.appendChild(createTile(rgb2hex(yRGB.map(x => x / 255))));
+  pkmn.appendChild(createTile(luv2hex(yLUV)));
+  return pkmn;
+}
+
+let lastColorSearch = null;
+let lastPkmnSearch = null;
+
+const paramsChanged = (...args) => {
+  const old = lastColorSearch;
+  lastColorSearch = args;
+  return old === null || old.filter((p, i) => p !== args[i]).length > 0
+}
+
 const onUpdate = () => {
   // Configuration Loading
   const includeX = document.getElementById("include-x")?.checked ?? false;
@@ -27,7 +68,7 @@ const onUpdate = () => {
   const closeCoeff = document.getElementById("close-coeff")?.value ?? 2;
   const useRGB = document.getElementById("color-space")?.textContent === "RGB";
   const numPoke = document.getElementById("num-poke")?.value ?? 20;
-  const pokemonName = document.getElementById("pokemon-name")?.value ?? "";
+  const pokemonName = document.getElementById("pokemon-name")?.value?.toLowerCase() ?? "";
   const targetColor = "#" + (document.getElementById("color-input")?.value?.replace("#", "") ?? "FFFFFF");
   const targetRGB = hex2rgb(targetColor).map(x => x * 255);
 
@@ -39,37 +80,44 @@ const onUpdate = () => {
   document.getElementById("close-coeff-display").innerHTML = closeCoeff;
   document.getElementById("num-poke-display").textContent = numPoke;
 
-  // calculate luminance to determine if text should be dark or light
-  const textColor = vectorDot(targetRGB, [0.3, 0.6, 0.1]) >= 128 ? "#222" : "#ddd";
-  document.querySelector("body").setAttribute("style", `background: ${targetColor}; color: ${textColor}`);
-
-  const bestList = document.getElementById("best-list");
-  bestList.innerHTML = ''; // do the lazy thing
-
-  // determine metrics from configuration
-  const targetInSpace = useRGB ? targetRGB : rgb2luv(targetRGB.map(x => x / 255));
-  const xSelector = includeX ? (useRGB ? ({ xRGB }) => xRGB : ({ xLUV }) => xLUV) : () => 0;
-  const ySelector = useRGB ? ({ yRGB }) => yRGB : ({ yLUV }) => yLUV;
-  const yScorer = (normQY ? getNormedScorer : getUnnormedScorer)(closeCoeff, targetInSpace);
-
-  // actually score pokemon
-  database
-    .map(info => ({ ...info, score: xSelector(info) - yScorer(ySelector(info)) }))
-    .sort((a, b) => a.score - b.score)
-    .slice(0, numPoke)
-    .forEach(({ name, score, yRGB }) => {
-      const li = document.createElement("li");
-      const img = document.createElement("img");
-      const tile = document.createElement("div");
-      const hexColor = rgb2hex(yRGB.map(x => x / 255));
-      tile.setAttribute("style", `width: 25px; height: 25px; background-color: ${hexColor}`)
-      img.setAttribute("src", getSprite(name));
-      li.appendChild(img)
-      li.appendChild(document.createTextNode(`${name}: ${score.toFixed(3)}`));
-      li.appendChild(tile)
-      li.setAttribute("style", "display: flex; flex-flow: row nowrap; justify-content: space-between; width: 320px")
-      bestList.appendChild(li);
-    });
+  if (targetColor.length === 7 && paramsChanged(includeX, normQY, closeCoeff, useRGB, numPoke, targetColor)) {
+    // calculate luminance to determine if text should be dark or light
+    const textColor = vectorDot(targetRGB, [0.3, 0.6, 0.1]) >= 128 ? "#222" : "#ddd";
+    document.querySelector("body").setAttribute("style", `background: ${targetColor}; color: ${textColor}`);
+
+    const bestList = document.getElementById("best-list");
+    bestList.innerHTML = ''; // do the lazy thing
+  
+    // determine metrics from configuration
+    const targetInSpace = useRGB ? targetRGB : rgb2luv(targetRGB.map(x => x / 255));
+    const xSelector = includeX ? (useRGB ? ({ xRGB }) => xRGB : ({ xLUV }) => xLUV) : () => 0;
+    const ySelector = useRGB ? ({ yRGB }) => yRGB : ({ yLUV }) => yLUV;
+    const yScorer = (normQY ? getNormedScorer : getUnnormedScorer)(closeCoeff, targetInSpace);
+  
+    // actually score pokemon
+    database
+      .map(info => ({ ...info, score: xSelector(info) - yScorer(ySelector(info)) }))
+      .sort((a, b) => a.score - b.score)
+      .slice(0, numPoke)
+      .forEach(info => {
+        const li = document.createElement("li");
+        li.appendChild(createPokemon(info))
+        bestList.appendChild(li);
+      });
+  }
+
+  if (pokemonName.length > 0 && lastPkmnSearch !== pokemonName) {
+    lastPkmnSearch = pokemonName;
+    // lookup by pokemon too
+    const searchList = document.getElementById("search-list");
+    searchList.innerHTML = '';
+    pokemonLookup.search(pokemonName, { limit: 10 })
+      .forEach(({ item }) => {
+        const li = document.createElement("li");
+        li.appendChild(createPokemon(item))
+        searchList.appendChild(li);
+      });
+  }
 };
 
 const onRandomColor = () => {
@@ -83,5 +131,4 @@ const onToggleSpace = () => {
   element.textContent = current === "RGB" ? "CIELUV" : "RGB";
   document.getElementById("space-toggle").textContent = `Swap to ${current}`
   onUpdate();
-}
-
+};