github.com/pelicanplatform/pelican@v1.0.5/web_ui/frontend/app/(login)/components/CodeInput.tsx (about) 1 /*************************************************************** 2 * 3 * Copyright (C) 2023, Pelican Project, Morgridge Institute for Research 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); you 6 * may not use this file except in compliance with the License. You may 7 * obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 * 17 ***************************************************************/ 18 19 import {ChangeEvent, ClipboardEvent, KeyboardEvent, useRef} from "react"; 20 import {Grid, TextField} from "@mui/material"; 21 import {AppProps} from "next/app"; 22 23 interface CodeInputProps { 24 length: number; 25 setCode: Function; 26 submitFunction?: () => void; 27 } 28 29 export default function CodeInput({length, setCode, submitFunction}: CodeInputProps) { 30 31 const inputRefs = useRef<HTMLInputElement[] | null[] | never[]>([]) 32 33 /** 34 * Set the code in the input blocks 35 * @param code: Array of digits to set 36 * @param offset: Offset to start setting the code at, if code is exact length this is set to 0 37 */ 38 function setInputs(code: number[], offset: number) { 39 40 if(code.length == length){ 41 offset = 0 42 } 43 44 Array.from(Array(code.length).keys()).forEach(index => { 45 if(index + offset < inputRefs.current.length) { 46 inputRefs.current[index + offset]!.value = code[index].toString() 47 } 48 }) 49 50 // Set the code 51 setCode(getValue()) 52 } 53 54 /** 55 * Get the value of the input blocks 56 */ 57 function getValue() { 58 return Number(inputRefs.current.map(input => input!.value).join("")) 59 } 60 61 /** 62 * Handle change in one of the input blocks 63 * - Checks that the input is an integer 64 * @param e 65 * @param index 66 */ 67 const onChange = (e: ChangeEvent, index: number) => { 68 69 // Check that the input was a legal one 70 const currentInput = inputRefs.current[index] 71 if (!Number.isInteger(Number(currentInput!.value))) { 72 currentInput!.value = "" 73 return 74 } 75 76 // Set the code 77 setCode(getValue()) 78 79 // If the current input is not the last advance focus 80 if(index != inputRefs.current.length - 1) { 81 const nextInput = inputRefs.current[index + 1] 82 nextInput!.focus() 83 } 84 } 85 86 /** 87 * Handle pasting into one of the input blocks 88 * 89 * Maps a ClipboardEvent to setCode function 90 * 91 * @param e 92 * @param index: Index of the input block event was captured in 93 */ 94 const onPaste = (e: ClipboardEvent, index: number) => { 95 let code = e.clipboardData.getData('Text').split("").map(x => Number(x)) 96 97 setInputs(code, index) 98 } 99 100 /** 101 * Handle backspace in one of the input blocks 102 * @param e 103 * @param index: Index of the input block event was captured in 104 */ 105 const onKeyDown = (e: KeyboardEvent, index: number) => { 106 if(["Backspace"].includes(e.code)) { 107 const currentInput = inputRefs.current[index] 108 109 // If the current input is the first one, clear it 110 if(index == 0) { 111 currentInput!.value = "" 112 113 } else { 114 115 const previousInput = inputRefs.current[index - 1] 116 117 // If the current input is empty, focus the previous one 118 if(currentInput!.value == "") { 119 previousInput!.focus() 120 121 // Empty the current input 122 } else { 123 currentInput!.value = "" 124 } 125 } 126 e.preventDefault() 127 } 128 } 129 130 return ( 131 <Grid container spacing={1}> 132 { 133 Array.from(Array(length).keys()).map((index) => { 134 135 return ( 136 <Grid item key={index} textAlign={"center"}> 137 <TextField 138 inputProps={{ 139 sx: { 140 width: "50px", 141 borderWidth: "3px", 142 fontSize: "3rem", 143 textAlign: "center", 144 padding: ".5rem", 145 backgroundColor: "secondary.main", 146 }, 147 maxLength: 1, 148 ref: (el : HTMLInputElement) => inputRefs.current[index] = el 149 }} 150 variant="outlined" 151 onKeyDown={(e) => onKeyDown(e, index)} 152 onChange={(e) => onChange(e, index)} 153 onPaste={(e) => onPaste(e, index)} 154 /> 155 </Grid> 156 ) 157 }) 158 } 159 </Grid> 160 ) 161 162 }