From 9528bb1ad6bfe55413564c50815abfc8672b48e9 Mon Sep 17 00:00:00 2001 From: "oura.a" Date: Thu, 13 Jul 2023 00:02:40 +0000 Subject: [PATCH] =?UTF-8?q?Merged=20PR=20226:=20=E3=83=86=E3=82=AD?= =?UTF-8?q?=E3=82=B9=E3=83=88=E3=83=9C=E3=83=83=E3=82=AF=E3=82=B9=E5=88=86?= =?UTF-8?q?=E5=89=B2=E5=AE=9F=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 概要 [Task2168: テキストボックス分割実装](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/2168) タスク 2168: テキストボックス分割実装 ライセンスキー入力のボックスを5分割に変更。 細かい画面レイアウトについては後々デザイナーさんに依頼することになるかと思いますので、レビュー対象外でお願いします。 ## レビューポイント 入力時の挙動に過不足がないか。 ## UIの変更 https://ndstokyo.sharepoint.com/:f:/r/sites/Piranha/Shared%20Documents/General/OMDS/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88/Task2168?csf=1&web=1&e=oRgAOK ## 動作確認状況 - ローカルで動作確認済み ## 補足 なし --- .../licenseCardActivateSlice.ts | 14 +- .../license/licenseCardActivate/selectors.ts | 14 - .../license/licenseCardActivate/state.ts | 1 - .../LicensePage/cardLicenseActivatePopup.tsx | 337 ++++++++++++++---- 4 files changed, 274 insertions(+), 92 deletions(-) diff --git a/dictation_client/src/features/license/licenseCardActivate/licenseCardActivateSlice.ts b/dictation_client/src/features/license/licenseCardActivate/licenseCardActivateSlice.ts index e06838c..f4530e7 100644 --- a/dictation_client/src/features/license/licenseCardActivate/licenseCardActivateSlice.ts +++ b/dictation_client/src/features/license/licenseCardActivate/licenseCardActivateSlice.ts @@ -1,10 +1,8 @@ -import { PayloadAction, createSlice } from "@reduxjs/toolkit"; +import { createSlice } from "@reduxjs/toolkit"; import { LicenseCardActivateState } from "./state"; -import { activateCardLicenseAsync } from "./operations"; const initialState: LicenseCardActivateState = { apps: { - keyLicense: "", isLoading: false, }, }; @@ -12,20 +10,12 @@ export const licenseCardActivateSlice = createSlice({ name: "licenseCardActivate", initialState, reducers: { - changeKeyLicense: ( - state, - action: PayloadAction<{ keyLicense: string }> - ) => { - const { keyLicense } = action.payload; - state.apps.keyLicense = keyLicense.toUpperCase(); - }, cleanupApps: (state) => { state.apps = initialState.apps; }, }, }); -export const { changeKeyLicense, cleanupApps } = - licenseCardActivateSlice.actions; +export const { cleanupApps } = licenseCardActivateSlice.actions; export default licenseCardActivateSlice.reducer; diff --git a/dictation_client/src/features/license/licenseCardActivate/selectors.ts b/dictation_client/src/features/license/licenseCardActivate/selectors.ts index 8ff2f38..812160f 100644 --- a/dictation_client/src/features/license/licenseCardActivate/selectors.ts +++ b/dictation_client/src/features/license/licenseCardActivate/selectors.ts @@ -1,18 +1,4 @@ import { RootState } from "../../../app/store"; -export const selectInputValidationErrors = (state: RootState) => { - const { keyLicense } = state.licenseCardActivate.apps; - const hasErrorIncorrectKeyNumber = checkErrorIncorrectKeyNumber(keyLicense); - return { - hasErrorIncorrectKeyNumber, - }; -}; -export const checkErrorIncorrectKeyNumber = (keyLicense: string): boolean => - // // 20+4(20文字+space4個)以外の場合はエラー - keyLicense.length !== 24; - -export const selectKeyLicense = (state: RootState) => - state.licenseCardActivate.apps.keyLicense; - export const selectIsLoading = (state: RootState) => state.licenseCardActivate.apps.isLoading; diff --git a/dictation_client/src/features/license/licenseCardActivate/state.ts b/dictation_client/src/features/license/licenseCardActivate/state.ts index 4ade669..ba541a2 100644 --- a/dictation_client/src/features/license/licenseCardActivate/state.ts +++ b/dictation_client/src/features/license/licenseCardActivate/state.ts @@ -3,6 +3,5 @@ export interface LicenseCardActivateState { } export interface Apps { - keyLicense: string; isLoading: boolean; } diff --git a/dictation_client/src/pages/LicensePage/cardLicenseActivatePopup.tsx b/dictation_client/src/pages/LicensePage/cardLicenseActivatePopup.tsx index 5bc654b..bdc265b 100644 --- a/dictation_client/src/pages/LicensePage/cardLicenseActivatePopup.tsx +++ b/dictation_client/src/pages/LicensePage/cardLicenseActivatePopup.tsx @@ -8,11 +8,8 @@ import { getTranslationID } from "../../translation"; import close from "../../assets/images/close.svg"; import { activateCardLicenseAsync, - selectKeyLicense, cleanupApps, selectIsLoading, - changeKeyLicense, - selectInputValidationErrors, } from "../../features/license/licenseCardActivate/index"; import progress_activit from "../../assets/images/progress_activit.svg"; @@ -26,9 +23,18 @@ export const CardLicenseActivatePopup: React.FC< const { onClose } = props; const { t } = useTranslation(); const dispatch: AppDispatch = useDispatch(); - const cardLicenseKey = useSelector(selectKeyLicense); - const [keyNumber, setKeyNumber] = useState(cardLicenseKey); const isLoading = useSelector(selectIsLoading); + const [keyNumber1, setKeyNumber1] = useState(""); + const [keyNumber2, setKeyNumber2] = useState(""); + const [keyNumber3, setKeyNumber3] = useState(""); + const [keyNumber4, setKeyNumber4] = useState(""); + const [keyNumber5, setKeyNumber5] = useState(""); + const TEXTAREASIZE = 4; + const ref1 = useRef(null); + const ref2 = useRef(null); + const ref3 = useRef(null); + const ref4 = useRef(null); + const ref5 = useRef(null); // ポップアップを閉じる処理 const closePopup = useCallback(() => { @@ -67,17 +73,114 @@ export const CardLicenseActivatePopup: React.FC< const [isPushActivateButton, setIsPushActivateButton] = useState(false); - // エラー宣言 - const { hasErrorIncorrectKeyNumber } = useSelector( - selectInputValidationErrors - ); + // 値を画面に分割して入れる + const inputValueOnTextarea = (input: string, startArea: number) => { + let roopCount = startArea; + while (input.length !== 0 || roopCount === startArea) { + switch (roopCount) { + case 1: + setKeyNumber1(input.slice(0, TEXTAREASIZE)); + input = input.substring(TEXTAREASIZE); + break; + case 2: + setKeyNumber2(input.slice(0, TEXTAREASIZE)); + input = input.substring(TEXTAREASIZE); + break; + case 3: + setKeyNumber3(input.slice(0, TEXTAREASIZE)); + input = input.substring(TEXTAREASIZE); + break; + case 4: + setKeyNumber4(input.slice(0, TEXTAREASIZE)); + input = input.substring(TEXTAREASIZE); + break; + case 5: + setKeyNumber5(input.slice(0, TEXTAREASIZE)); + input = input.substring(TEXTAREASIZE); + break; + default: + input = ""; + } + roopCount += 1; + } + }; + + // テキストエリア入力時 + const changeTextarea = ( + e: React.ChangeEvent, + areaNum: number + ) => { + const input = e.target.value + .toUpperCase() + .replace(/[^A-Z0-9]/g, "") + .substring(0, 20); + inputValueOnTextarea(input, areaNum); + if (e.target.value.includes("\n")) { + setTimeout(() => { + onKeyDownEnter(); + }, 0); + } + moveTextarea(input.length, areaNum); + }; + + // フォーカスを移動する + const moveFocus = (target: string) => { + // stateの反映を同期してから実施 + setTimeout(() => { + const obj = document.getElementById(target); + if (obj) { + obj.focus(); + } + }, 0); + }; + + // フォーカス移動判定 + const moveTextarea = (length: number, areaNum: number) => { + if (length === TEXTAREASIZE && areaNum !== 5) { + moveFocus(`textarea${(areaNum + 1).toString()}`); + } + }; + + // キー入力時判定 + const keyDown = ( + e: React.KeyboardEvent, + areaNum: number + ) => { + if (e.key === "Enter") { + e.preventDefault(); + onKeyDownEnter(); + } else if (e.key === "ArrowRight") { + if (e.target.selectionStart === e.target.value.length) { + moveFocus(`textarea${(areaNum + 1).toString()}`); + } + } else if (e.key === "ArrowLeft") { + if (e.target.selectionStart === 0) { + moveFocus(`textarea${(areaNum - 1).toString()}`); + } + } else if (/^[a-zA-Z0-9]$/.test(e.key)) { + if (e.target.value.length === TEXTAREASIZE) { + e.preventDefault(); + } + } else if (/^[!-/:-@[-`{-~]$/.test(e.key)) { + e.preventDefault(); + } + }; + + // Enterキー押下時の処理 + const onKeyDownEnter = () => { + const button = document.getElementById("button"); + if (button) { + button.focus(); + button.click(); + } + }; // activateボタン押下時 const onActivateLicense = useCallback(async () => { setIsPushActivateButton(true); - - if (keyNumber.length !== 24) { - const inputBox = document.getElementById("inputBox"); + const keyNumber = `${keyNumber1}${keyNumber2}${keyNumber3}${keyNumber4}${keyNumber5}`; + if (keyNumber.length !== 20) { + const inputBox = document.getElementById("textarea5"); // カーソルをテキストボックスに戻す if (inputBox) { inputBox.focus(); @@ -86,23 +189,26 @@ export const CardLicenseActivatePopup: React.FC< } // activateAPIの呼び出し - const cardLicenseKeyWithoutSpaces = keyNumber.replace(/\s/g, ""); const { meta } = await dispatch( - activateCardLicenseAsync({ cardLicenseKey: cardLicenseKeyWithoutSpaces }) + activateCardLicenseAsync({ cardLicenseKey: keyNumber }) ); setIsPushActivateButton(false); // カーソルをテキストボックスに戻す - const inputBox = document.getElementById("inputBox"); + const inputBox = document.getElementById("textarea1"); if (inputBox) { inputBox.focus(); } if (meta.requestStatus === "fulfilled") { dispatch(cleanupApps()); - setKeyNumber(""); + setKeyNumber1(""); + setKeyNumber2(""); + setKeyNumber3(""); + setKeyNumber4(""); + setKeyNumber5(""); } - }, [keyNumber, dispatch]); + }, [keyNumber1, keyNumber2, keyNumber3, keyNumber4, keyNumber5, dispatch]); // HTML return ( @@ -128,62 +234,163 @@ export const CardLicenseActivatePopup: React.FC<
- { - let input = e.target.value.toUpperCase(); - input = input.replace(/[^A-Z0-9]/g, ""); - // _.chunk関数で、配列の要素を一定の要素数ごとに分ける - input = _.chunk(input, 4) - .map((a) => a.join("")) - .join(" "); - setKeyNumber(input); - if (input.includes("\n")) { - dispatch( - changeKeyLicense({ - keyLicense: e.target.value.toUpperCase(), - }) - ); - onActivateLicense(); - } - }} - onBlur={(e) => { - dispatch( - changeKeyLicense({ - keyLicense: e.target.value.toUpperCase(), - }) - ); + let cursorPos = 0; + cursorPos = e.target.selectionStart; + changeTextarea(e, 1); + setTimeout(() => { + if (ref1.current) { + ref1.current.selectionStart = cursorPos; + ref1.current.selectionEnd = cursorPos; + } + }, 0); }} onKeyDown={(e) => { - if (e.key === "Enter") { - e.preventDefault(); - const input = e.target.value.toUpperCase(); - setKeyNumber(input); - const button = document.getElementById("button"); - if (button) { - button.focus(); - button.click(); - } + keyDown(e, 1); + }} + onFocus={(e) => { + if (ref1.current) { + ref1.current.selectionStart = e.target.value.length; + ref1.current.selectionEnd = e.target.value.length; } }} /> - {isPushActivateButton && hasErrorIncorrectKeyNumber && ( - - {t( - getTranslationID( - "cardLicenseActivatePopupPage.label.keyNumberIncorrectError" - ) - )} - - )} +