Merge remote-tracking branch 'upstream/main' into feat/39

This commit is contained in:
이강준
2022-12-06 22:07:29 +09:00
163 changed files with 2639 additions and 1327 deletions
@@ -1,131 +0,0 @@
import React, { Fragment } from "react";
import { faCircle } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import guidGenerator from "../utilities/randomId";
/**
* This function splits the input of a dashboard field into the parts that are inside and outside of ${...}
* @param {string} text - the value of the input in the Dashboard Input Field
* @returns
*/
const findReferences = (text) => {
var splitText = text.split("${");
let textArray = [splitText[0]];
for (var i = 1; i < splitText.length; i++) {
let insideBrackets = "${" + splitText[i].split("}")[0];
if (splitText[i].includes("}")) {
insideBrackets += "}";
}
textArray.push(insideBrackets);
textArray.push(splitText[i].split("}")[1]);
}
return textArray;
};
/**
* This component renders the input fields on the dashboard
* @param {object} obj - the order number of a keyPair
* @param {number} obj.index - the order number of a keyPair
* @param {function} obj.onChangeHandler - what happens when the input is modified
* @param {string} obj.type - whether the input field is for a Key Name or for a Key Value
* @param {string} obj.value - value of the InputField
* @param {boolean} obj.blurred - whether the input field should be blurred (behind the gray dots) or not; this can be turned on/off in the dashboard
* @returns
*/
const DashboardInputField = ({
index,
onChangeHandler,
type,
value,
blurred,
}) => {
if (type === "varName") {
return (
<div className="flex-col w-full">
<div
className={`group relative flex flex-col justify-center w-full max-w-2xl border border-mineshaft-500 rounded-md`}
>
<input
onChange={(e) => onChangeHandler(e.target.value, index)}
type={type}
value={value}
className="asolute z-10 peer font-mono ph-no-capture bg-bunker-800 rounded-md caret-white text-gray-400 text-md px-2 py-1.5 w-full min-w-16 outline-none focus:ring-4 focus:ring-primary/50 duration-200"
spellCheck="false"
/>
</div>
</div>
);
} else if (type === "value") {
return (
<div className="flex-col w-full">
<div
className={`group relative flex flex-col justify-center w-full max-w-2xl border border-mineshaft-500 rounded-md`}
>
<input
onChange={(e) => onChangeHandler(e.target.value, index)}
type={type}
value={value}
className={`${
blurred
? "text-transparent group-hover:text-transparent focus:text-transparent active:text-transparent"
: ""
} asolute z-10 peer font-mono ph-no-capture bg-transparent rounded-md caret-white text-transparent text-md px-2 py-1.5 w-full min-w-16 outline-none focus:ring-4 focus:ring-primary/50 duration-200`}
spellCheck="false"
/>
<div
className={`${
blurred
? "text-bunker-800 group-hover:text-gray-400 peer-focus:text-gray-400 peer-active:text-gray-400"
: ""
} flex flex-row font-mono absolute z-0 ph-no-capture bg-bunker-800 h-9 rounded-md text-gray-400 text-md px-2 py-1.5 w-full min-w-16 outline-none focus:ring-4 focus:ring-primary/50 duration-100`}
>
{findReferences(value).map((texts, id) => {
if (id % 2 == 0 || texts.length <= 2) {
return (
<span className="ph-no-capture" key={id}>
{texts}
</span>
);
}
return (
<span className="ph-no-capture text-yellow" key={id}>
{texts.slice(0, 2)}
<span className="ph-no-capture text-yellow-200/80">
{texts.slice(2, texts.length - 1)}
</span>
{texts.slice(texts.length - 1, texts.length) == "}" ? (
<span className="ph-no-capture text-yellow">
{texts.slice(texts.length - 1, texts.length)}{" "}
</span>
) : (
<span className="ph-no-capture text-yellow-400">
{texts.slice(texts.length - 1, texts.length)}{" "}
</span>
)}
</span>
);
})}
</div>
{blurred && (
<div className="z-20 peer pr-2 bg-bunker-800 group-hover:hidden peer-hover:hidden peer-focus:hidden peer-active:invisible absolute h-9 w-fit max-w-xl rounded-md flex items-center text-gray-400/50 text-clip overflow-hidden">
<p className="ml-2"></p>
{value
.split("")
.slice(0, 42)
.map(() => (
<FontAwesomeIcon
key={guidGenerator()}
className="text-xxs mx-0.5"
icon={faCircle}
/>
))}
</div>
)}
</div>
</div>
);
}
};
export default React.memo(DashboardInputField);
@@ -0,0 +1,156 @@
import React, { SyntheticEvent, useRef } from "react";
import { faCircle } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import guidGenerator from "../utilities/randomId";
const REGEX = /([$]{.*?})/g;
interface DashboardInputFieldProps {
index: number;
onChangeHandler: (value: string, index: number) => void;
value: string;
type: "varName" | "value";
blurred: boolean;
duplicates: string[];
}
/**
* This component renders the input fields on the dashboard
* @param {object} obj - the order number of a keyPair
* @param {number} obj.index - the order number of a keyPair
* @param {function} obj.onChangeHandler - what happens when the input is modified
* @param {string} obj.type - whether the input field is for a Key Name or for a Key Value
* @param {string} obj.value - value of the InputField
* @param {boolean} obj.blurred - whether the input field should be blurred (behind the gray dots) or not; this can be turned on/off in the dashboard
* @param {string[]} obj.duplicates - list of all the duplicated key names on the dashboard
* @returns
*/
const DashboardInputField = ({
index,
onChangeHandler,
type,
value,
blurred,
duplicates,
}: DashboardInputFieldProps) => {
const ref = useRef<HTMLDivElement | null>(null);
const syncScroll = (e: SyntheticEvent<HTMLDivElement>) => {
if (ref.current == null) return;
ref.current.scrollTop = e.currentTarget.scrollTop;
ref.current.scrollLeft = e.currentTarget.scrollLeft;
};
if (type === "varName") {
const startsWithNumber = !isNaN(Number(value.charAt(0))) && value != "";
const hasDuplicates = duplicates?.includes(value);
const error = startsWithNumber || hasDuplicates;
return (
<div className="flex-col w-full">
<div
className={`group relative flex flex-col justify-center w-full max-w-2xl border ${
error ? "border-red" : "border-mineshaft-500"
} rounded-md`}
>
<input
onChange={(e) =>
onChangeHandler(e.target.value.toUpperCase(), index)
}
type={type}
value={value}
className={`z-10 peer font-mono ph-no-capture bg-bunker-800 rounded-md caret-white text-gray-400 text-md px-2 py-1.5 w-full min-w-16 outline-none focus:ring-2 ${
error ? "focus:ring-red/50" : "focus:ring-primary/50"
} duration-200`}
spellCheck="false"
/>
</div>
{startsWithNumber && (
<p className="text-red text-xs mt-0.5 mx-1 mb-2 max-w-xs">
Should not start with a number
</p>
)}
{hasDuplicates && !startsWithNumber && (
<p className="text-red text-xs mt-0.5 mx-1 mb-2 max-w-xs">
Secret names should be unique
</p>
)}
</div>
);
} else if (type === "value") {
return (
<div className="flex-col w-full">
<div
className={`group relative whitespace-pre flex flex-col justify-center w-full max-w-2xl border border-mineshaft-500 rounded-md`}
>
<input
value={value}
onChange={(e) => onChangeHandler(e.target.value, index)}
onScroll={syncScroll}
className={`${
blurred
? "text-transparent group-hover:text-transparent focus:text-transparent active:text-transparent"
: ""
} z-10 peer font-mono ph-no-capture bg-transparent rounded-md caret-white text-transparent text-md px-2 py-1.5 w-full min-w-16 outline-none focus:ring-2 focus:ring-primary/50 duration-200 no-scrollbar no-scrollbar::-webkit-scrollbar`}
spellCheck="false"
/>
<div
ref={ref}
className={`${
blurred
? "text-bunker-800 group-hover:text-gray-400 peer-focus:text-gray-400 peer-active:text-gray-400"
: ""
} absolute flex flex-row whitespace-pre font-mono z-0 ph-no-capture max-w-2xl overflow-x-scroll bg-bunker-800 h-9 rounded-md text-gray-400 text-md px-2 py-1.5 w-full min-w-16 outline-none focus:ring-2 focus:ring-primary/50 duration-100 no-scrollbar no-scrollbar::-webkit-scrollbar`}
>
{value.split(REGEX).map((word, id) => {
if (word.match(REGEX) !== null) {
return (
<span className="ph-no-capture text-yellow" key={id}>
{word.slice(0, 2)}
<span className="ph-no-capture text-yellow-200/80">
{word.slice(2, word.length - 1)}
</span>
{word.slice(word.length - 1, word.length) == "}" ? (
<span className="ph-no-capture text-yellow">
{word.slice(word.length - 1, word.length)}
</span>
) : (
<span className="ph-no-capture text-yellow-400">
{word.slice(word.length - 1, word.length)}
</span>
)}
</span>
);
} else {
return (
<span key={id} className="ph-no-capture">
{word}
</span>
);
}
})}
</div>
{blurred && (
<div className="absolute flex flex-row items-center z-20 peer pr-2 bg-bunker-800 group-hover:hidden peer-hover:hidden peer-focus:hidden peer-active:invisible h-9 w-full max-w-2xl rounded-md text-gray-400/50 text-clip">
<div className="px-2 flex flex-row items-center overflow-x-scroll no-scrollbar no-scrollbar::-webkit-scrollbar">
{value.split("").map(() => (
<FontAwesomeIcon
key={guidGenerator()}
className="text-xxs mx-0.5"
icon={faCircle}
/>
))}
</div>
</div>
)}
</div>
</div>
);
}
return <>Something Wrong</>;
};
export default React.memo(DashboardInputField);
@@ -1,4 +1,4 @@
import { useState } from "react";
import { type ChangeEvent, type DragEvent, useState } from "react";
import Image from "next/image";
import useTranslation from "next-translate/useTranslation";
import { faUpload } from "@fortawesome/free-solid-svg-icons";
@@ -9,29 +9,39 @@ import Error from "../basic/Error";
import parse from "../utilities/file";
import guidGenerator from "../utilities/randomId";
interface DropZoneProps {
// TODO: change Data type from any
setData: (data: any) => void;
setErrorDragAndDrop: (hasError: boolean) => void;
createNewFile: () => void;
errorDragAndDrop: boolean;
setButtonReady: (isReady: boolean) => void;
keysExist: boolean;
numCurrentRows: number;
}
const DropZone = ({
setData,
setErrorDragAndDrop,
createNewFile,
errorDragAndDrop,
addPresetRow,
setButtonReady,
keysExist,
numCurrentRows,
}) => {
}: DropZoneProps) => {
const { t } = useTranslation();
const handleDragEnter = (e) => {
const handleDragEnter = (e: DragEvent) => {
e.preventDefault();
e.stopPropagation();
};
const handleDragLeave = (e) => {
const handleDragLeave = (e: DragEvent) => {
e.preventDefault();
e.stopPropagation();
};
const handleDragOver = (e) => {
const handleDragOver = (e: DragEvent) => {
e.preventDefault();
e.stopPropagation();
@@ -42,22 +52,25 @@ const DropZone = ({
const [loading, setLoading] = useState(false);
// This function function immediately parses the file after it is dropped
const handleDrop = async (e) => {
const handleDrop = async (e: DragEvent) => {
setLoading(true);
setTimeout(() => setLoading(false), 5000);
e.preventDefault();
e.stopPropagation();
e.dataTransfer.dropEffect = "copy";
var file = e.dataTransfer.files[0],
reader = new FileReader();
reader.onload = function (event) {
const keyPairs = parse(event.target.result);
const file = e.dataTransfer.files[0];
const reader = new FileReader();
reader.onload = (event) => {
if (event.target === null || event.target.result === null) return;
// parse function's argument looks like to be ArrayBuffer
const keyPairs = parse(event.target.result as Buffer);
const newData = Object.keys(keyPairs).map((key, index) => [
guidGenerator(),
numCurrentRows + index,
key,
keyPairs[key],
keyPairs[key as keyof typeof keyPairs],
"shared",
]);
setData(newData);
@@ -75,23 +88,28 @@ const DropZone = ({
};
// This function is used when the user manually selects a file from the in-browser dircetory (not drag and drop)
const handleFileSelect = (e) => {
const handleFileSelect = (e: ChangeEvent<HTMLInputElement>) => {
setLoading(true);
setTimeout(() => setLoading(false), 5000);
var file = e.target.files[0],
reader = new FileReader();
reader.onload = function (event) {
const newData = event.target.result
.split("\n")
.map((line, index) => [
guidGenerator(),
numCurrentRows + index,
line.split("=")[0],
line.split("=").slice(1, line.split("=").length).join("="),
"shared",
]);
setData(newData);
setButtonReady(true);
if (e.currentTarget.files === null) return;
const file = e.currentTarget.files[0];
const reader = new FileReader();
reader.onload = (event) => {
if (event.target === null || event.target.result === null) return;
const { result } = event.target;
if (typeof result === "string") {
const newData = result
.split("\n")
.map((line: string, index: number) => [
guidGenerator(),
numCurrentRows + index,
line.split("=")[0],
line.split("=").slice(1, line.split("=").length).join("="),
"shared",
]);
setData(newData);
setButtonReady(true);
}
};
reader.readAsText(file);
};
@@ -108,17 +126,17 @@ const DropZone = ({
) : keysExist ? (
<div
className="opacity-60 hover:opacity-100 duration-200 relative bg-bunker outline max-w-[calc(100%-1rem)] w-full outline-dashed outline-gray-600 rounded-md outline-2 flex flex-col items-center justify-center mb-16 mx-auto mt-1 py-8 px-2"
onDragEnter={(e) => handleDragEnter(e)}
onDragOver={(e) => handleDragOver(e)}
onDragLeave={(e) => handleDragLeave(e)}
onDrop={(e) => handleDrop(e)}
onDragEnter={handleDragEnter}
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
onDrop={handleDrop}
>
<input
id="fileSelect"
type="file"
className="opacity-0 absolute w-full h-full"
accept=".txt,.env"
onChange={(e) => handleFileSelect(e)}
onChange={handleFileSelect}
/>
{errorDragAndDrop ? (
<div className="my-3 max-w-xl opacity-80"></div>
@@ -143,10 +161,10 @@ const DropZone = ({
) : (
<div
className="opacity-80 hover:opacity-100 duration-200 relative bg-bunker outline max-w-2xl w-full outline-dashed outline-gray-700 rounded-md outline-2 flex flex-col items-center justify-center pt-16 mb-16 px-4"
onDragEnter={(e) => handleDragEnter(e)}
onDragOver={(e) => handleDragOver(e)}
onDragLeave={(e) => handleDragLeave(e)}
onDrop={(e) => handleDrop(e)}
onDragEnter={handleDragEnter}
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
onDrop={handleDrop}
>
<FontAwesomeIcon icon={faUpload} className="text-7xl mb-8" />
<p className="">{t("common:drop-zone")}</p>
@@ -155,7 +173,7 @@ const DropZone = ({
type="file"
className="opacity-0 absolute w-full h-full"
accept=".txt,.env"
onChange={(e) => handleFileSelect(e)}
onChange={handleFileSelect}
/>
<div className="flex flex-row w-full items-center justify-center mb-6 mt-5">
<div className="border-t border-gray-700 w-1/5"></div>