webtools

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

page.tsx (4818B)


      1 "use client";
      2 import React, { useState, useEffect } from "react";
      3 
      4 type WasmModule = {
      5   generate_password: (length: number, asciiFlags: Uint8Array) => string;
      6 };
      7 
      8 // ASCII printable symbols: 33–126 (除外: A-Z, a-z, 0-9)
      9 const SYMBOLS = Array.from({ length: 126 - 33 + 1 }, (_, i) =>
     10   String.fromCharCode(i + 33),
     11 ).filter((c) => {
     12   const code = c.charCodeAt(0);
     13   return !(
     14     (
     15       (code >= 48 && code <= 57) || // digits
     16       (code >= 65 && code <= 90) || // upper
     17       (code >= 97 && code <= 122)
     18     ) // lower
     19   );
     20 });
     21 
     22 export default function PasswordGenerator() {
     23   const [wasm, setWasm] = useState<WasmModule | null>(null);
     24   const [password, setPassword] = useState("");
     25   const [length, setLength] = useState(12);
     26 
     27   const [useUpper, setUseUpper] = useState(true);
     28   const [useLower, setUseLower] = useState(true);
     29   const [useDigits, setUseDigits] = useState(true);
     30 
     31   const [symbolFlags, setSymbolFlags] = useState<Record<string, boolean>>(
     32     SYMBOLS.reduce((acc, s) => ({ ...acc, [s]: false }), {}),
     33   );
     34 
     35   useEffect(() => {
     36     (async () => {
     37       const wasmModule = await import("../../wasm-webtools/pkg/wasm_webtools.js");
     38       setWasm(wasmModule);
     39     })();
     40   }, []);
     41 
     42   const buildAsciiFlags = () => {
     43     const flags = new Uint8Array(128);
     44 
     45     if (useUpper) for (let i = 65; i <= 90; i++) flags[i] = 1;
     46     if (useLower) for (let i = 97; i <= 122; i++) flags[i] = 1;
     47     if (useDigits) for (let i = 48; i <= 57; i++) flags[i] = 1;
     48 
     49     for (const s of SYMBOLS) {
     50       if (symbolFlags[s]) {
     51         flags[s.charCodeAt(0)] = 1;
     52       }
     53     }
     54     return flags;
     55   };
     56 
     57   const generate = () => {
     58     if (wasm) {
     59       const flags = buildAsciiFlags();
     60       const pw = wasm.generate_password(length, flags);
     61       setPassword(pw);
     62     }
     63   };
     64 
     65   const toggleSymbol = (s: string) => {
     66     setSymbolFlags((prev) => ({ ...prev, [s]: !prev[s] }));
     67   };
     68 
     69   const setAllSymbols = (checked: boolean) => {
     70     setSymbolFlags(SYMBOLS.reduce((acc, s) => ({ ...acc, [s]: checked }), {}));
     71   };
     72 
     73   async function copyToClip(password: string) {
     74     try {
     75       await navigator.clipboard.writeText(password);
     76       alert("success copying to clipboard");
     77     } catch (error) {
     78       alert("fail to copying to clipboard" + error);
     79     }
     80   }
     81 
     82   return (
     83     <main>
     84       <h2>Password Generator</h2>
     85 
     86       <label>
     87         Length:
     88         <input
     89           type="number"
     90           value={length}
     91           min={4}
     92           max={64}
     93           onChange={(e) => setLength(parseInt(e.target.value))}
     94         />
     95       </label>
     96 
     97       <div style={{ marginTop: 12 }}>
     98         <label>
     99           <input
    100             type="checkbox"
    101             checked={useUpper}
    102             onChange={() => setUseUpper(!useUpper)}
    103           />
    104           Uppercase (A–Z)
    105         </label>
    106         <br />
    107         <label>
    108           <input
    109             type="checkbox"
    110             checked={useLower}
    111             onChange={() => setUseLower(!useLower)}
    112           />
    113           Lowercase (a–z)
    114         </label>
    115         <br />
    116         <label>
    117           <input
    118             type="checkbox"
    119             checked={useDigits}
    120             onChange={() => setUseDigits(!useDigits)}
    121           />
    122           Digits (0–9)
    123         </label>
    124       </div>
    125 
    126       <div style={{ marginTop: 12 }}>
    127         <strong>Symbols:</strong>
    128         <div style={{ marginBottom: 8 }}>
    129           <button type="button" onClick={() => setAllSymbols(true)}>
    130             Select All
    131           </button>
    132           <button
    133             type="button"
    134             onClick={() => setAllSymbols(false)}
    135             style={{ marginLeft: 8 }}
    136           >
    137             Deselect All
    138           </button>
    139         </div>
    140         <div style={{ display: "flex", flexWrap: "wrap", gap: "6px" }}>
    141           {SYMBOLS.map((s) => (
    142             <label
    143               key={s}
    144               style={{ display: "flex", alignItems: "center", gap: 2 }}
    145             >
    146               <input
    147                 type="checkbox"
    148                 checked={symbolFlags[s]}
    149                 onChange={() => toggleSymbol(s)}
    150               />
    151               <span style={{ fontFamily: "monospace" }}>{s}</span>
    152             </label>
    153           ))}
    154         </div>
    155       </div>
    156 
    157       <button
    158         type="submit"
    159         onClick={generate}
    160         disabled={!wasm}
    161         style={{ marginTop: 12 }}
    162       >
    163         Generate
    164       </button>
    165 
    166       {password && (
    167         <div style={{ marginTop: 12 }}>
    168           <strong>Generated Password:</strong>
    169           <div
    170             style={{
    171               fontFamily: "monospace",
    172               padding: 8,
    173               border: "1px solid #ddd",
    174             }}
    175           >
    176             {password}
    177           </div>
    178           <button type="button" onClick={() => copyToClip(password)}>
    179             Copy to ClipBoard
    180           </button>
    181         </div>
    182       )}
    183     </main>
    184   );
    185 }