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 }