diff --git a/frontend/src/hooks/index.ts b/frontend/src/hooks/index.ts index fc7acc575..a3ded034c 100644 --- a/frontend/src/hooks/index.ts +++ b/frontend/src/hooks/index.ts @@ -1,4 +1,5 @@ export { useLeaveConfirm } from "./useLeaveConfirm"; export { usePersistentState } from "./usePersistentState"; export { usePopUp } from "./usePopUp"; +export { useSyntaxHighlight } from "./useSyntaxHighlight"; export { useToggle } from "./useToggle"; diff --git a/frontend/src/hooks/useSyntaxHighlight.tsx b/frontend/src/hooks/useSyntaxHighlight.tsx new file mode 100644 index 000000000..edb3f85cd --- /dev/null +++ b/frontend/src/hooks/useSyntaxHighlight.tsx @@ -0,0 +1,45 @@ +import { faCircle } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { useCallback } from "react"; + +const REGEX = /([$]{.*?})/g; + +export const useSyntaxHighlight = () => { + const syntaxHighlight = useCallback((text: string, isHidden?: boolean) => { + if (isHidden) { + return text + .split("") + .slice(0, 200) + .map((el, i) => + el === "\n" ? ( + el + ) : ( + + ) + ); + } + + // append a space on last new line this to show new line in ui for code component + const val = text.at(-1) === "\n" ? text.concat(" ") : text; + if (val?.length === 0) return EMPTY; + return val?.split(REGEX).map((word, i) => + word.match(REGEX) !== null ? ( + + ${ + {word.slice(2, word.length - 1)} + } + + ) : ( + + {word} + + ) + ); + }, []); + + return syntaxHighlight; +}; diff --git a/frontend/src/views/DashboardPage/components/EnvComparisonRow/EnvComparisonRow.tsx b/frontend/src/views/DashboardPage/components/EnvComparisonRow/EnvComparisonRow.tsx index 8b75eb39d..2c40e78f6 100644 --- a/frontend/src/views/DashboardPage/components/EnvComparisonRow/EnvComparisonRow.tsx +++ b/frontend/src/views/DashboardPage/components/EnvComparisonRow/EnvComparisonRow.tsx @@ -1,8 +1,9 @@ /* eslint-disable react/jsx-no-useless-fragment */ -import { useCallback, useState } from "react"; -import { faCircle, faEye, faEyeSlash, faKey, faMinus } from "@fortawesome/free-solid-svg-icons"; +import { useCallback, useRef, useState } from "react"; +import { faEye, faEyeSlash, faKey, faMinus } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { twMerge } from "tailwind-merge"; +import { useToggle } from "~/hooks/useToggle"; +import { useSyntaxHighlight } from "@app/hooks"; type Props = { secrets: any[] | undefined; @@ -12,7 +13,8 @@ type Props = { userAvailableEnvs?: any[]; }; -const REGEX = /([$]{.*?})/g; +const SEC_VAL_LINE_HEIGHT = 21; +const MAX_MULTI_LINE = 6; const DashboardInput = ({ isOverridden, @@ -25,84 +27,54 @@ const DashboardInput = ({ isReadOnly?: boolean; secret?: any; }): JSX.Element => { - const syntaxHighlight = useCallback((val: string) => { - if (val === undefined) - return ( - - - - ); - if (val?.length === 0) - return EMPTY; - return val?.split(REGEX).map((word, index) => - word.match(REGEX) !== null ? ( - - {word.slice(0, 2)} - {word.slice(2, word.length - 1)} - {word.slice(word.length - 1, word.length) === "}" ? ( - - {word.slice(word.length - 1, word.length)} - - ) : ( - - {word.slice(word.length - 1, word.length)} - - )} - - ) : ( - - {word} - - ) - ); - }, []); + const ref = useRef(null); + const [isFocused, setIsFocused] = useToggle(); + const syntaxHighlight = useSyntaxHighlight(); + + const value = isOverridden ? secret.valueOverride : secret?.value || ""; + const multilineExpandUnit = ((value?.match(/\n/g)?.length || 0) + 1) * SEC_VAL_LINE_HEIGHT; + const maxMultilineHeight = Math.min(multilineExpandUnit, 21 * MAX_MULTI_LINE); return ( - - + setIsFocused.off()} + onFocus={() => setIsFocused.on()} + onInput={(el) => { + if (ref.current) { + ref.current.scrollTop = el.currentTarget.scrollTop; + ref.current.scrollLeft = el.currentTarget.scrollLeft; + } + }} + onScroll={(el) => { + if (ref.current) { + ref.current.scrollTop = el.currentTarget.scrollTop; + ref.current.scrollLeft = el.currentTarget.scrollLeft; + } + }} /> - - {syntaxHighlight(secret?.value)} - - {isSecretValueHidden && secret?.value && ( - - - {(isOverridden ? secret.valueOverride : secret?.value || "") - ?.split("") - .map((_a: string, index: number) => ( - - ))} - {(isOverridden ? secret.valueOverride : secret?.value || "")?.split("").length === - 0 && EMPTY} - - - )} + + + {syntaxHighlight(value || "", isSecretValueHidden ? !isFocused : isSecretValueHidden)} + + ); @@ -122,14 +94,14 @@ export const EnvComparisonRow = ({ ); return ( - - - + + + - - + + {secrets![0].key || ""} { - const { register, control } = useFormContext(); + const { control } = useFormContext(); + const ref = useRef(null); + const [isFocused, setIsFocused] = useToggle(); + const syntaxHighlight = useSyntaxHighlight(); const secretValue = useWatch({ control, name: `secrets.${index}.value` }); const secretValueOverride = useWatch({ control, name: `secrets.${index}.valueOverride` }); const value = isOverridden ? secretValueOverride : secretValue; - const syntaxHighlight = useCallback((val: string) => { - if (val?.length === 0) return EMPTY; - return val?.split(REGEX).map((word, i) => - word.match(REGEX) !== null ? ( - - {word.slice(0, 2)} - {word.slice(2, word.length - 1)} - {word.slice(word.length - 1, word.length) === "}" ? ( - - {word.slice(word.length - 1, word.length)} - - ) : ( - - {word.slice(word.length - 1, word.length)} - - )} - - ) : ( - - {word} - - ) - ); - }, []); + const multilineExpandUnit = ((value?.match(/\n/g)?.length || 0) + 1) * SEC_VAL_LINE_HEIGHT; + const maxMultilineHeight = Math.min(multilineExpandUnit, 21 * MAX_MULTI_LINE); return ( - + {isOverridden ? ( - ( + setIsFocused.off()} + onFocus={() => setIsFocused.on()} + onInput={(el) => { + if (ref.current) { + ref.current.scrollTop = el.currentTarget.scrollTop; + ref.current.scrollLeft = el.currentTarget.scrollLeft; + } + }} + onScroll={(el) => { + if (ref.current) { + ref.current.scrollTop = el.currentTarget.scrollTop; + ref.current.scrollLeft = el.currentTarget.scrollLeft; + } + }} + /> )} - spellCheck="false" /> ) : ( - ( + setIsFocused.off()} + onFocus={() => setIsFocused.on()} + onInput={(el) => { + if (ref.current) { + ref.current.scrollTop = el.currentTarget.scrollTop; + ref.current.scrollLeft = el.currentTarget.scrollLeft; + } + }} + onScroll={(el) => { + if (ref.current) { + ref.current.scrollTop = el.currentTarget.scrollTop; + ref.current.scrollLeft = el.currentTarget.scrollLeft; + } + }} + /> )} - spellCheck="false" /> )} - - {syntaxHighlight(value || "")} - - - - {value?.split("").map((val, i) => ( - - ))} - {value?.split("").length === 0 && ( - EMPTY - )} - - + + + {syntaxHighlight(value || "", isSecretValueHidden ? !isFocused : isSecretValueHidden)} + + ); }; diff --git a/frontend/src/views/DashboardPage/components/SecretInputRow/SecretInputRow.tsx b/frontend/src/views/DashboardPage/components/SecretInputRow/SecretInputRow.tsx index 534f55473..274e8db2b 100644 --- a/frontend/src/views/DashboardPage/components/SecretInputRow/SecretInputRow.tsx +++ b/frontend/src/views/DashboardPage/components/SecretInputRow/SecretInputRow.tsx @@ -34,6 +34,7 @@ import { WsTag } from "@app/hooks/api/types"; import { FormData, SecretActionType } from "../../DashboardPage.utils"; import { MaskedInput } from "./MaskedInput"; +import { useToggle } from "~/hooks/useToggle"; type Props = { index: number; @@ -157,7 +158,7 @@ export const SecretInputRow = memo( } return ( - + {index + 1} @@ -198,7 +199,7 @@ export const SecretInputRow = memo( )} /> - + - - + + {secretTags.map(({ id, slug }, i) => ( )} - + {!isAddOnly && ( @@ -346,35 +347,37 @@ export const SecretInputRow = memo( - - {!isAddOnly && ( + + + {!isAddOnly && ( + + + + + + + + )} - + onSecretDelete(index, secId, idOverride)} > - + - )} - - - onSecretDelete(index, secId, idOverride)} - > - - -
+ + {syntaxHighlight(value || "", isSecretValueHidden ? !isFocused : isSecretValueHidden)} + +
+ {syntaxHighlight(value || "", isSecretValueHidden ? !isFocused : isSecretValueHidden)} +