@team-monolith/cds 1.117.20 → 1.118.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/CdsProvider.d.ts +2 -2
- package/dist/CdsProvider.js +1 -11
- package/dist/i18n/i18n.d.ts +13 -3
- package/dist/i18n/i18n.js +31 -33
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/patterns/LexicalEditor/components/FileSelectInput.js +7 -8
- package/dist/patterns/LexicalEditor/components/UploadFileDialog/UploadFileDialog.js +3 -1
- package/dist/patterns/LexicalEditor/nodes/ProblemInputNode/InputComponent.js +2 -2
- package/dist/patterns/LexicalEditor/nodes/ProblemInputNode/SettingForm/FormPlaceholder.js +3 -1
- package/dist/patterns/LexicalEditor/nodes/ProblemInputNode/SettingForm/SettingForm.js +1 -1
- package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/SelectBox/SelectBoxView.js +15 -14
- package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/SelectComponent.js +2 -1
- package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/SettingForm/SettingForm.js +3 -3
- package/dist/patterns/LexicalEditor/nodes/SelfEvaluationNode/EvaluationComponent/Evaluation/SettingForm/FormIconAndLabel/FormIconAndLabel.js +2 -2
- package/dist/patterns/LexicalEditor/nodes/SelfEvaluationNode/EvaluationComponent/Evaluation/SettingForm/FormIconAndLabel/FormLabel.js +5 -3
- package/dist/patterns/LexicalEditor/nodes/SelfEvaluationNode/EvaluationComponent/Evaluation/SettingForm/FormQuestion.js +1 -1
- package/dist/patterns/LexicalEditor/nodes/SelfEvaluationNode/EvaluationComponent/Evaluation/SettingForm/SettingForm.js +1 -1
- package/dist/patterns/LexicalEditor/nodes/SelfEvaluationNode/EvaluationComponent/EvaluationComponent.js +1 -1
- package/dist/patterns/LexicalEditor/nodes/SelfEvaluationNode/iconData.d.ts +2 -1
- package/dist/patterns/LexicalEditor/nodes/SelfEvaluationNode/iconData.js +4 -5
- package/dist/patterns/LexicalEditor/nodes/SheetInputNode/InputComponent.js +1 -1
- package/dist/patterns/LexicalEditor/nodes/SheetInputNode/SettingForm.js +2 -2
- package/dist/patterns/LexicalEditor/nodes/SheetSelectNode/SelectComponent/SelectComponent.js +2 -1
- package/dist/patterns/LexicalEditor/nodes/SheetSelectNode/SelectComponent/SettingForm/SettingForm.js +3 -3
- package/dist/patterns/LexicalEditor/plugins/ComponentAdderPlugin/ComponentAdderPlugin.js +1 -0
- package/dist/patterns/LexicalEditor/plugins/ComponentAdderPlugin/useContextMenuOptions.js +58 -56
- package/dist/patterns/LexicalEditor/plugins/ComponentPickerMenuPlugin/ComponentPickerMenuPlugin.d.ts +2 -0
- package/dist/patterns/LexicalEditor/plugins/ComponentPickerMenuPlugin/ComponentPickerMenuPlugin.js +29 -33
- package/dist/patterns/LexicalEditor/plugins/DragDropPastePlugin/index.js +4 -2
- package/dist/texts.d.ts +3 -1
- package/dist/texts.js +11 -11
- package/package.json +1 -1
package/dist/CdsProvider.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Theme } from "@emotion/react";
|
|
2
|
-
import {
|
|
2
|
+
import { i18n } from "i18next";
|
|
3
3
|
export declare const light: Theme;
|
|
4
4
|
export declare const dark: Theme;
|
|
5
5
|
interface CdsContext {
|
|
@@ -8,11 +8,11 @@ interface CdsContext {
|
|
|
8
8
|
uploadByFile: (file: File) => Promise<string>;
|
|
9
9
|
showFileError: (type: "upload" | "download", message: string) => void;
|
|
10
10
|
};
|
|
11
|
-
i18nextOption?: i18nextOption;
|
|
12
11
|
}
|
|
13
12
|
export declare const CdsContext: import("react").Context<CdsContext>;
|
|
14
13
|
export declare function CdsProvider(props: {
|
|
15
14
|
children: React.ReactNode;
|
|
16
15
|
fontFamily?: string;
|
|
16
|
+
i18n: i18n;
|
|
17
17
|
} & CdsContext): React.ReactElement;
|
|
18
18
|
export {};
|
package/dist/CdsProvider.js
CHANGED
|
@@ -5,7 +5,6 @@ import { COLOR } from "./foundation/color";
|
|
|
5
5
|
import { FONT } from "./foundation/font";
|
|
6
6
|
import { createTheme } from "@mui/material";
|
|
7
7
|
import { createContext } from "react";
|
|
8
|
-
import { initI18nInstance, i18n } from "./i18n/i18n";
|
|
9
8
|
import { I18nextProvider } from "react-i18next";
|
|
10
9
|
export const light = {
|
|
11
10
|
fontFamily: {
|
|
@@ -311,15 +310,6 @@ export const dark = {
|
|
|
311
310
|
};
|
|
312
311
|
export const CdsContext = createContext({});
|
|
313
312
|
export function CdsProvider(props) {
|
|
314
|
-
// AIDEV-NOTE:
|
|
315
|
-
// CdsProvider가 렌더링 될 때 i18n 인스턴스가 초기화되지 않았다면, 초기화합니다.
|
|
316
|
-
// 여기에 useEffect를 사용한다면, 마운트 시에 i18n 초기화가 되어있지 않을 때
|
|
317
|
-
// react-i18next가 suspense를 사용하게 되어 컴포넌트가 언마운트 되므로,
|
|
318
|
-
// useEffect가 실행이 되지 않는 문제가 있습니다.
|
|
319
|
-
// 따라서 useEffect를 사용하지 않고, 동기적으로 i18n 초기화를 수행합니다.
|
|
320
|
-
if (!i18n.isInitialized && !i18n.isInitializing) {
|
|
321
|
-
initI18nInstance(props.i18nextOption);
|
|
322
|
-
}
|
|
323
313
|
const muiTheme = createTheme({
|
|
324
314
|
typography: {
|
|
325
315
|
fontFamily: props.fontFamily,
|
|
@@ -332,7 +322,7 @@ export function CdsProvider(props) {
|
|
|
332
322
|
},
|
|
333
323
|
},
|
|
334
324
|
});
|
|
335
|
-
const { theme = light } = props;
|
|
325
|
+
const { theme = light, i18n } = props;
|
|
336
326
|
return (
|
|
337
327
|
/**
|
|
338
328
|
* AIDEV-NOTE:
|
package/dist/i18n/i18n.d.ts
CHANGED
|
@@ -1,8 +1,18 @@
|
|
|
1
|
-
|
|
1
|
+
import { i18n, TFunction } from "i18next";
|
|
2
|
+
type i18nextOption = {
|
|
2
3
|
environment: string;
|
|
3
4
|
mainLanguage: string;
|
|
4
5
|
projectId: string;
|
|
5
6
|
apiKey?: string;
|
|
6
7
|
};
|
|
7
|
-
|
|
8
|
-
|
|
8
|
+
/**
|
|
9
|
+
* i18n 인스턴스를 생성하고 초기화합니다.
|
|
10
|
+
* @returns
|
|
11
|
+
* - i18n: i18n 인스턴스
|
|
12
|
+
* - ready: i18n 초기화가 완료된 후에 resolve 되는 Promise
|
|
13
|
+
*/
|
|
14
|
+
export declare function createAndInitI18n(option?: i18nextOption): {
|
|
15
|
+
i18n: i18n;
|
|
16
|
+
ready: Promise<TFunction>;
|
|
17
|
+
};
|
|
18
|
+
export {};
|
package/dist/i18n/i18n.js
CHANGED
|
@@ -1,12 +1,3 @@
|
|
|
1
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
-
});
|
|
9
|
-
};
|
|
10
1
|
import i18next from "i18next";
|
|
11
2
|
import LanguageDetector from "i18next-browser-languagedetector";
|
|
12
3
|
import Backend from "i18next-locize-backend";
|
|
@@ -24,28 +15,35 @@ const CONTAINS_KOREAN = typeof DETECTED_LANGUAGE === "string"
|
|
|
24
15
|
: Array.isArray(DETECTED_LANGUAGE)
|
|
25
16
|
? DETECTED_LANGUAGE.some((lang) => lang.startsWith("ko"))
|
|
26
17
|
: false;
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
18
|
+
/**
|
|
19
|
+
* i18n 인스턴스를 생성하고 초기화합니다.
|
|
20
|
+
* @returns
|
|
21
|
+
* - i18n: i18n 인스턴스
|
|
22
|
+
* - ready: i18n 초기화가 완료된 후에 resolve 되는 Promise
|
|
23
|
+
*/
|
|
24
|
+
export function createAndInitI18n(option) {
|
|
25
|
+
const i18n = i18next
|
|
26
|
+
.createInstance()
|
|
27
|
+
.use(languageDetector)
|
|
28
|
+
.use(initReactI18next);
|
|
29
|
+
if (!option) {
|
|
30
|
+
// option이 없는 경우, backend 플러그인 없이 기본 i18next 설정을 사용하여,
|
|
31
|
+
// fallbackValue(=key)를 항상 사용하게 됩니다.
|
|
32
|
+
return {
|
|
33
|
+
i18n,
|
|
34
|
+
ready: i18n.init(Object.assign(Object.assign({}, I18N_COMMON_CONFIGS), { fallbackLng: false })),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* AIDEV-NOTE:
|
|
39
|
+
* Locize의 사용량을 최적화하기 위해, Locize를 다음과 같은 상황에서만 활성화합니다:
|
|
40
|
+
* 1. 프로덕션 환경이 아닐 때 (개발 or 로컬 환경) 또는,
|
|
41
|
+
* 2. 언어 설정이 한국어가 아닐 때
|
|
42
|
+
*/
|
|
43
|
+
const LOCIZE_ENABLED = option.environment !== "production" || !CONTAINS_KOREAN;
|
|
44
|
+
return {
|
|
45
|
+
i18n,
|
|
46
|
+
ready: (LOCIZE_ENABLED ? i18n.use(Backend).use(locizePlugin) : i18n).init(Object.assign(Object.assign({}, I18N_COMMON_CONFIGS), { debug: option.environment !== "production",
|
|
49
47
|
// AIDEV-NOTE:
|
|
50
48
|
// - 유저의 언어가 Korean인 경우:
|
|
51
49
|
// fallbackLng를 false로 설정하여, 번역이 없는 경우 key 자체를 반환하도록 합니다.
|
|
@@ -58,6 +56,6 @@ export function initI18nInstance(option) {
|
|
|
58
56
|
projectId: option.projectId,
|
|
59
57
|
apiKey: option.apiKey,
|
|
60
58
|
referenceLng: "ko",
|
|
61
|
-
} }))
|
|
62
|
-
}
|
|
59
|
+
} })),
|
|
60
|
+
};
|
|
63
61
|
}
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -16,20 +16,19 @@ import { css, useTheme } from "@emotion/react";
|
|
|
16
16
|
import styled from "@emotion/styled";
|
|
17
17
|
import moment from "moment";
|
|
18
18
|
import { useTranslation } from "react-i18next";
|
|
19
|
-
import { i18n } from "../../../i18n/i18n";
|
|
20
19
|
import { getTexts } from "../../../texts";
|
|
21
|
-
const getFileType = (fileType) => ({
|
|
20
|
+
const getFileType = (fileType, t) => ({
|
|
22
21
|
image: {
|
|
23
22
|
accept: "image/*",
|
|
24
|
-
errorStr:
|
|
23
|
+
errorStr: t("업로드할 수 없는 파일입니다. 이미지 파일을 업로드해주세요."),
|
|
25
24
|
},
|
|
26
25
|
pdf: {
|
|
27
26
|
accept: ".pdf",
|
|
28
|
-
errorStr:
|
|
27
|
+
errorStr: t("업로드할 수 없는 파일입니다. PDF 파일을 업로드해주세요."),
|
|
29
28
|
},
|
|
30
29
|
file: {
|
|
31
30
|
accept: "*",
|
|
32
|
-
errorStr:
|
|
31
|
+
errorStr: t("업로드할 수 없는 파일입니다. 모든 파일을 업로드해주세요."),
|
|
33
32
|
},
|
|
34
33
|
})[fileType];
|
|
35
34
|
const MAX_FILE_SIZE = 1 * 1024 * 1024 * 1024; // 1GB
|
|
@@ -42,17 +41,17 @@ export function FileSelectInput(props) {
|
|
|
42
41
|
const { onChange, fileType, onFileSubmit, bold = false } = props;
|
|
43
42
|
const [errorMessage, setErrorMessage] = useState(null);
|
|
44
43
|
const inputRef = useRef(null);
|
|
44
|
+
const { t } = useTranslation();
|
|
45
|
+
const { accept, errorStr } = getFileType(fileType, t);
|
|
45
46
|
const cdsContext = useContext(CdsContext);
|
|
46
|
-
const { accept, errorStr } = getFileType(fileType);
|
|
47
47
|
const theme = useTheme();
|
|
48
|
-
const { t } = useTranslation();
|
|
49
48
|
const handleFileChange = (event) => __awaiter(this, void 0, void 0, function* () {
|
|
50
49
|
var _a, _b, _c;
|
|
51
50
|
const file = (_a = event.target.files) === null || _a === void 0 ? void 0 : _a[0];
|
|
52
51
|
if (!file)
|
|
53
52
|
return;
|
|
54
53
|
if (file.size >= MAX_FILE_SIZE) {
|
|
55
|
-
setErrorMessage(getTexts("errorFileTooLarge"));
|
|
54
|
+
setErrorMessage(getTexts(t, "errorFileTooLarge"));
|
|
56
55
|
event.target.value = "";
|
|
57
56
|
return;
|
|
58
57
|
}
|
|
@@ -14,12 +14,14 @@ import moment from "moment";
|
|
|
14
14
|
import { CdsContext } from "../../../../CdsProvider";
|
|
15
15
|
import styled from "@emotion/styled";
|
|
16
16
|
import { getTexts } from "../../../../texts";
|
|
17
|
+
import { useTranslation } from "react-i18next";
|
|
17
18
|
/**
|
|
18
19
|
* OS 파일 다이얼로그를 열어 파일을 선택하고 업로드하는 컴포넌트입니다.
|
|
19
20
|
*/
|
|
20
21
|
export function UploadFileDialog({ open, onClose, onChange, }) {
|
|
21
22
|
const inputRef = useRef(null);
|
|
22
23
|
const cdsContext = useContext(CdsContext);
|
|
24
|
+
const { t } = useTranslation();
|
|
23
25
|
const handleFileChange = (event) => __awaiter(this, void 0, void 0, function* () {
|
|
24
26
|
var _a, _b, _c;
|
|
25
27
|
const showFileError = (_a = cdsContext.lexical) === null || _a === void 0 ? void 0 : _a.showFileError;
|
|
@@ -30,7 +32,7 @@ export function UploadFileDialog({ open, onClose, onChange, }) {
|
|
|
30
32
|
}
|
|
31
33
|
const MAX_FILE_SIZE = 1 * 1024 * 1024 * 1024; // 1GB
|
|
32
34
|
if (file.size >= MAX_FILE_SIZE) {
|
|
33
|
-
showFileError === null || showFileError === void 0 ? void 0 : showFileError("upload", getTexts("errorFileTooLarge"));
|
|
35
|
+
showFileError === null || showFileError === void 0 ? void 0 : showFileError("upload", getTexts(t, "errorFileTooLarge"));
|
|
34
36
|
event.target.value = "";
|
|
35
37
|
onClose();
|
|
36
38
|
return;
|
|
@@ -54,9 +54,9 @@ export function InputComponent(props) {
|
|
|
54
54
|
// 학생 view
|
|
55
55
|
if (!isEditable) {
|
|
56
56
|
if (showCharacterNumber) {
|
|
57
|
-
return (_jsx(SegmentedInput, { readOnly: freezeProblemNode, isCorrect: isCorrect, answerFormat: answerFormat, placeholder: placeholder || getTexts("placeholderEnterHere"), value: answerInput, onChange: handleChange }));
|
|
57
|
+
return (_jsx(SegmentedInput, { readOnly: freezeProblemNode, isCorrect: isCorrect, answerFormat: answerFormat, placeholder: placeholder || getTexts(t, "placeholderEnterHere"), value: answerInput, onChange: handleChange }));
|
|
58
58
|
}
|
|
59
|
-
return (_jsx(TextInput, { readOnly: freezeProblemNode, isCorrect: isCorrect, size: "small", color: "default", placeholder: placeholder || getTexts("placeholderEnterHere"), value: answerInput, onChange: (e) => {
|
|
59
|
+
return (_jsx(TextInput, { readOnly: freezeProblemNode, isCorrect: isCorrect, size: "small", color: "default", placeholder: placeholder || getTexts(t, "placeholderEnterHere"), value: answerInput, onChange: (e) => {
|
|
60
60
|
handleChange(e.target.value);
|
|
61
61
|
}, fullWidth: true }));
|
|
62
62
|
}
|
|
@@ -2,7 +2,9 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
2
2
|
import { Controller } from "react-hook-form";
|
|
3
3
|
import { Input } from "../../../../../components/Input";
|
|
4
4
|
import { getTexts } from "../../../../../texts";
|
|
5
|
+
import { useTranslation } from "react-i18next";
|
|
5
6
|
export function FormPlaceholder(props) {
|
|
6
7
|
const { control } = props;
|
|
7
|
-
|
|
8
|
+
const { t } = useTranslation();
|
|
9
|
+
return (_jsx(Controller, { name: "placeholder", control: control, render: ({ field: { value, onChange } }) => (_jsx(Input, { size: "small", color: "default", value: value, onChange: onChange, placeholder: getTexts(t, "exampleEnterHere") })) }));
|
|
8
10
|
}
|
|
@@ -83,7 +83,7 @@ export function SettingForm(props) {
|
|
|
83
83
|
` }) })] }), _jsx(FormSegmentedControl, { control: control, trigger: trigger, name: "showCharacterNumber", items: [
|
|
84
84
|
{ value: false, label: t("한 칸으로") },
|
|
85
85
|
{ value: true, label: numCharOptionStr },
|
|
86
|
-
] })] }), _jsxs(FormArea, { children: [_jsxs(Label, { children: [t("자리 표시자", { context: "렉시컬 주관식 설정창" }), _jsx(Tooltip, { text: getTexts("descriptionDefaultInputText"), placement: "top", children: _jsx(QuestionFillIcon, { css: css `
|
|
86
|
+
] })] }), _jsxs(FormArea, { children: [_jsxs(Label, { children: [t("자리 표시자", { context: "렉시컬 주관식 설정창" }), _jsx(Tooltip, { text: getTexts(t, "descriptionDefaultInputText"), placement: "top", children: _jsx(QuestionFillIcon, { css: css `
|
|
87
87
|
width: 12px;
|
|
88
88
|
height: 12px;
|
|
89
89
|
` }) })] }), _jsx(FormPlaceholder, { control: control })] }), _jsxs(FormArea, { children: [_jsx(Label, { children: t("띄어쓰기") }), _jsx(FormSegmentedControl, { control: control, trigger: trigger, name: "ignoreWhitespace", items: [
|
|
@@ -6,7 +6,6 @@ import { SelectBoxComponent } from "./SelectBoxComponent";
|
|
|
6
6
|
import { Tag } from "../../../../../components/Tag";
|
|
7
7
|
import { useRef } from "react";
|
|
8
8
|
import { useEventListener } from "usehooks-ts";
|
|
9
|
-
import { i18n } from "../../../../../i18n/i18n";
|
|
10
9
|
import { useTranslation } from "react-i18next";
|
|
11
10
|
const TYPE_TO_INDEX_ICON = (type) => ({
|
|
12
11
|
primary: (_jsx(CheckFillIcon, { css: css `
|
|
@@ -26,40 +25,42 @@ const TYPE_TO_INDEX_ICON = (type) => ({
|
|
|
26
25
|
* isSelected, isAnswer, onClick에 따른 type, description 내용
|
|
27
26
|
* /images/2025-05-05-01-25-06.png
|
|
28
27
|
*/
|
|
29
|
-
function getTypeAndDescription(isSelected, isAnswer, onClick) {
|
|
28
|
+
function getTypeAndDescription(isSelected, isAnswer, onClick, t) {
|
|
30
29
|
if (isAnswer === undefined) {
|
|
31
30
|
return {
|
|
32
31
|
type: isSelected ? "primary" : undefined,
|
|
33
|
-
description: isSelected
|
|
34
|
-
? i18n.t("선택됨")
|
|
35
|
-
: onClick
|
|
36
|
-
? i18n.t("선택하기")
|
|
37
|
-
: "",
|
|
32
|
+
description: isSelected ? t("선택됨") : onClick ? t("선택하기") : "",
|
|
38
33
|
};
|
|
39
34
|
}
|
|
40
35
|
if (isAnswer) {
|
|
41
36
|
return {
|
|
42
37
|
type: isSelected ? "success" : undefined,
|
|
43
38
|
description: isSelected
|
|
44
|
-
?
|
|
39
|
+
? t("선택됨, 정답", {
|
|
40
|
+
context: "렉시컬 선택지 하나의 상태",
|
|
41
|
+
})
|
|
45
42
|
: onClick
|
|
46
|
-
?
|
|
47
|
-
|
|
43
|
+
? t("선택하기, 정답", {
|
|
44
|
+
context: "렉시컬 선택지 하나의 상태",
|
|
45
|
+
})
|
|
46
|
+
: t("정답", { context: "렉시컬 선택지 하나의 상태" }),
|
|
48
47
|
};
|
|
49
48
|
}
|
|
50
49
|
return {
|
|
51
50
|
type: isSelected ? "danger" : undefined,
|
|
52
51
|
description: isSelected
|
|
53
|
-
?
|
|
52
|
+
? t("선택됨, 오답", { context: "렉시컬 선택지 하나의 상태" })
|
|
54
53
|
: onClick
|
|
55
|
-
?
|
|
56
|
-
|
|
54
|
+
? t("선택하기, 오답", {
|
|
55
|
+
context: "렉시컬 선택지 하나의 상태",
|
|
56
|
+
})
|
|
57
|
+
: t("오답", { context: "렉시컬 선택지 하나의 상태" }),
|
|
57
58
|
};
|
|
58
59
|
}
|
|
59
60
|
export function SelectBoxView(props) {
|
|
60
61
|
const { multipleSelectionsEnabled, index, isSelected, isAnswer, image, text, onClick, } = props;
|
|
61
62
|
const { t } = useTranslation();
|
|
62
|
-
const { type, description } = getTypeAndDescription(isSelected, isAnswer, onClick);
|
|
63
|
+
const { type, description } = getTypeAndDescription(isSelected, isAnswer, onClick, t);
|
|
63
64
|
const boxRef = useRef(null);
|
|
64
65
|
const handleKeyDown = (e) => {
|
|
65
66
|
// onClick이 없으면 아무것도 하지 않음.
|
|
@@ -13,6 +13,7 @@ import { SettingForm } from "./SettingForm";
|
|
|
13
13
|
import styled from "@emotion/styled";
|
|
14
14
|
import { LexicalCustomConfigContext } from "../../LexicalCustomConfigContext";
|
|
15
15
|
import { useTranslation } from "react-i18next";
|
|
16
|
+
import { getTexts } from "../../../../texts";
|
|
16
17
|
export function SelectComponent(props) {
|
|
17
18
|
const { selected, hasMultipleSolutions, selections, nodeKey } = props;
|
|
18
19
|
const [editor] = useLexicalComposerContext();
|
|
@@ -35,7 +36,7 @@ export function SelectComponent(props) {
|
|
|
35
36
|
`, children: [multipleSelectionsEnabled && (_jsxs(Alert, { children: [_jsx(AlarmWarningFillIcon, { css: css `
|
|
36
37
|
width: 14px;
|
|
37
38
|
height: 14px;
|
|
38
|
-
` }), t
|
|
39
|
+
` }), getTexts(t, "multipleChoicesProblem")] })), selections.map((selection, index) => (_jsx(SelectBoxView, { multipleSelectionsEnabled: multipleSelectionsEnabled, index: index + 1, isAnswer: showQuizSolution && "isAnswer" in selection
|
|
39
40
|
? selection.isAnswer
|
|
40
41
|
: undefined, isSelected: selected.includes(selection.value), image: selection.show.image, text: selection.show.text, onClick: freezeProblemNode
|
|
41
42
|
? undefined
|
|
@@ -44,7 +44,7 @@ export function SettingForm(props) {
|
|
|
44
44
|
function validateRequired(value) {
|
|
45
45
|
if (value.show.image || value.show.text)
|
|
46
46
|
return true;
|
|
47
|
-
return getTexts("errorRequiredField");
|
|
47
|
+
return getTexts(t, "errorRequiredField");
|
|
48
48
|
}
|
|
49
49
|
function validateDuplicated(value, formValues) {
|
|
50
50
|
const shows = formValues.selections.map((selection) => selection.show);
|
|
@@ -57,7 +57,7 @@ export function SettingForm(props) {
|
|
|
57
57
|
});
|
|
58
58
|
if (duplicatedIndexes.length < 2)
|
|
59
59
|
return true;
|
|
60
|
-
return getTexts("errorDuplicateChoice")(duplicatedIndexes.map((i) => i + 1).join(","));
|
|
60
|
+
return getTexts(t, "errorDuplicateChoice")(duplicatedIndexes.map((i) => i + 1).join(","));
|
|
61
61
|
}
|
|
62
62
|
const answersCount = watch("selections").filter((selection) => selection.isAnswer).length;
|
|
63
63
|
const hasMultipleAnswers = answersCount > 1;
|
|
@@ -76,7 +76,7 @@ export function SettingForm(props) {
|
|
|
76
76
|
}
|
|
77
77
|
: undefined }, field.uid)))] }), _jsx(Button, { color: "grey", size: "small", startIcon: _jsx(AddFillIcon, {}), label: fields.length < 9
|
|
78
78
|
? t("선택지 추가", { context: "버튼, 렉시컬 도구 설정창" })
|
|
79
|
-
: getTexts("errorMaxChoicesExceeded"), onClick: () => {
|
|
79
|
+
: getTexts(t, "errorMaxChoicesExceeded"), onClick: () => {
|
|
80
80
|
if (fields.length >= 9)
|
|
81
81
|
return;
|
|
82
82
|
append({
|
|
@@ -13,7 +13,7 @@ export function FormIconAndLabel(props) {
|
|
|
13
13
|
const theme = useTheme();
|
|
14
14
|
const { t } = useTranslation();
|
|
15
15
|
return (_jsx(Controller, { control: control, name: "iconType", render: ({ field: { value, onChange } }) => {
|
|
16
|
-
const { startIcon, title, icons } = getIconData(value, theme);
|
|
16
|
+
const { startIcon, title, icons } = getIconData(value, theme, t);
|
|
17
17
|
return (_jsxs(_Fragment, { children: [_jsx(Dropdown, { label: t("아이콘: {{title}}", { title }), size: "small", color: "grey", closeOnItemClick: true, bold: true, startIcon: startIcon, endIcon: _jsx(ArrowDownSFillIcon, {}), menuProps: {
|
|
18
18
|
menuCss: css `
|
|
19
19
|
width: 216px;
|
|
@@ -27,7 +27,7 @@ export function FormIconAndLabel(props) {
|
|
|
27
27
|
horizontal: "center",
|
|
28
28
|
},
|
|
29
29
|
}, children: ICON_TYPES.map((type, index) => {
|
|
30
|
-
const { title, startIcon } = getIconData(type, theme);
|
|
30
|
+
const { title, startIcon } = getIconData(type, theme, t);
|
|
31
31
|
return (_jsx(DropdownItem, { index: index, label: title, preserveIconColor: true, startIcon: startIcon, onClick: () => {
|
|
32
32
|
onChange(type);
|
|
33
33
|
} }, index));
|
|
@@ -4,14 +4,16 @@ import styled from "@emotion/styled";
|
|
|
4
4
|
import { css } from "@emotion/react";
|
|
5
5
|
import { FormInput } from "../../../../../Form";
|
|
6
6
|
import { getTexts } from "../../../../../../../../texts";
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
import { useTranslation } from "react-i18next";
|
|
8
|
+
const getLabelRules = (t) => ({
|
|
9
|
+
required: getTexts(t, "errorRequiredField"),
|
|
9
10
|
maxLength: 12,
|
|
10
11
|
});
|
|
11
12
|
/** 레이블을 설정하는 Form입니다. */
|
|
12
13
|
export function FormLabel(props) {
|
|
13
14
|
const { control, index } = props;
|
|
14
|
-
const
|
|
15
|
+
const { t } = useTranslation();
|
|
16
|
+
const labelRules = getLabelRules(t);
|
|
15
17
|
// endIcon에 들어갈 value, error를 사용하기 위해 useController를 사용합니다.
|
|
16
18
|
const { field: { value }, fieldState: { error }, } = useController({
|
|
17
19
|
control,
|
|
@@ -10,7 +10,7 @@ export function FormQuestion(props) {
|
|
|
10
10
|
const { control, index, onDelete } = props;
|
|
11
11
|
const { t } = useTranslation();
|
|
12
12
|
return (_jsxs(Item, { children: [_jsx(Index, { children: index + 1 }), _jsx(FormInput, { control: control, name: `evaluations.${index}.question.text`, rules: {
|
|
13
|
-
required: getTexts("errorRequiredField"),
|
|
13
|
+
required: getTexts(t, "errorRequiredField"),
|
|
14
14
|
}, size: "small", placeholder: t("{{index}}번 평가 항목", { index: index + 1 }), multiline: true, fullWidth: true }), onDelete && (_jsx(SquareButton, { color: "white", size: "xsmall", icon: _jsx(DeleteBinLineIcon, {}), onClick: onDelete, "aria-label": t("삭제", {
|
|
15
15
|
context: "스퀘어버튼, 렉시컬 3단계 평가 입력칸 도구 설정창",
|
|
16
16
|
}) }))] }));
|
|
@@ -50,7 +50,7 @@ export function SettingForm(props) {
|
|
|
50
50
|
}
|
|
51
51
|
: undefined }, field.uid))), _jsx(Button, { color: "grey", size: "small", startIcon: _jsx(AddFillIcon, {}), label: t("평가 항목 추가"), onClick: () => {
|
|
52
52
|
append({
|
|
53
|
-
question: { text: getTexts("placeholderEvaluationItem") },
|
|
53
|
+
question: { text: getTexts(t, "placeholderEvaluationItem") },
|
|
54
54
|
selectedLabelIndex: null,
|
|
55
55
|
});
|
|
56
56
|
} })] }), _jsxs(Right, { children: [_jsx(Label, { children: t("아이콘, 레이블") }), _jsx(FormIconAndLabel, { control: control, labelsLength: labels.length })] })] }), _jsxs(Buttons, { children: [_jsx(Button, { color: "grey", size: "xsmall", label: t("닫기", { context: "렉시컬 도구 설정창" }), onClick: onClose }), _jsx(Button, { color: "primary", size: "xsmall", label: t("이대로 넣기", { context: "렉시컬 도구 설정창" }), bold: true, type: "submit" })] })] }));
|
|
@@ -23,7 +23,7 @@ export function EvaluationComponent(props) {
|
|
|
23
23
|
const { freezeProblemNode } = useContext(LexicalCustomConfigContext);
|
|
24
24
|
const { t } = useTranslation();
|
|
25
25
|
const [settingOpen, setSettingOpen] = useState(false);
|
|
26
|
-
const { icons } = getIconData(iconType, theme);
|
|
26
|
+
const { icons } = getIconData(iconType, theme, t);
|
|
27
27
|
// label과 icons를 매핑하여 ToggelButtonData로 변환합니다.
|
|
28
28
|
// 만약 둘의 길이가 같지 않다면 짧은 길이에 맞춰집니다.
|
|
29
29
|
const data = _.zipWith(labels, icons, (label, { onIcon, offIcon }) => ({
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { IconType } from "./SelfEvaluationNode";
|
|
3
3
|
import { Theme } from "@emotion/react";
|
|
4
|
+
import { TFunction } from "i18next";
|
|
4
5
|
interface IconData {
|
|
5
6
|
startIcon: React.ReactNode;
|
|
6
7
|
title: string;
|
|
@@ -10,5 +11,5 @@ interface IconData {
|
|
|
10
11
|
}[];
|
|
11
12
|
}
|
|
12
13
|
export declare const ICON_TYPES: IconType[];
|
|
13
|
-
export declare const getIconData: (iconType: IconType, theme: Theme) => IconData;
|
|
14
|
+
export declare const getIconData: (iconType: IconType, theme: Theme, t: TFunction) => IconData;
|
|
14
15
|
export {};
|
|
@@ -2,16 +2,15 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
2
2
|
import { CustomEmoGoodIcon, CustomEmoGreatIcon, CustomEmoNeutralIcon, } from "../../../../icons/custom/default";
|
|
3
3
|
import { CustomEmoGoodColorIcon, CustomEmoGreatColorIcon, CustomEmoNeutralColorIcon, } from "../../../../icons/custom/colored";
|
|
4
4
|
import { Number1Icon, Number2Icon, Number3Icon } from "../../../../icons";
|
|
5
|
-
import { i18n } from "../../../../i18n/i18n";
|
|
6
5
|
export const ICON_TYPES = [
|
|
7
6
|
"emoji",
|
|
8
7
|
"ascendingNumber",
|
|
9
8
|
"descendingNumber",
|
|
10
9
|
];
|
|
11
|
-
export const getIconData = (iconType, theme) => ({
|
|
10
|
+
export const getIconData = (iconType, theme, t) => ({
|
|
12
11
|
emoji: {
|
|
13
12
|
startIcon: _jsx(CustomEmoGreatColorIcon, {}),
|
|
14
|
-
title:
|
|
13
|
+
title: t("이모지"),
|
|
15
14
|
icons: [
|
|
16
15
|
{
|
|
17
16
|
onIcon: _jsx(CustomEmoGreatColorIcon, {}),
|
|
@@ -26,7 +25,7 @@ export const getIconData = (iconType, theme) => ({
|
|
|
26
25
|
},
|
|
27
26
|
ascendingNumber: {
|
|
28
27
|
startIcon: _jsx(Number1Icon, {}),
|
|
29
|
-
title:
|
|
28
|
+
title: t("숫자, 1-2-3"),
|
|
30
29
|
icons: [
|
|
31
30
|
{
|
|
32
31
|
onIcon: _jsx(Number1Icon, { color: theme.color.background.primary }),
|
|
@@ -44,7 +43,7 @@ export const getIconData = (iconType, theme) => ({
|
|
|
44
43
|
},
|
|
45
44
|
descendingNumber: {
|
|
46
45
|
startIcon: _jsx(Number3Icon, {}),
|
|
47
|
-
title:
|
|
46
|
+
title: t("숫자, 3-2-1"),
|
|
48
47
|
icons: [
|
|
49
48
|
{
|
|
50
49
|
onIcon: _jsx(Number3Icon, { color: theme.color.background.primary }),
|
|
@@ -49,7 +49,7 @@ export function InputComponent(props) {
|
|
|
49
49
|
: {
|
|
50
50
|
onFocus: () => setFocus(true),
|
|
51
51
|
onBlur: () => setFocus(false),
|
|
52
|
-
}, size: "small", color: focus ? "activePrimary" : "default", placeholder: placeholder || getTexts("placeholderEnterHere"), value: valueInput, onChange: (e) => {
|
|
52
|
+
}, size: "small", color: focus ? "activePrimary" : "default", placeholder: placeholder || getTexts(t, "placeholderEnterHere"), value: valueInput, onChange: (e) => {
|
|
53
53
|
handleChange(e.target.value);
|
|
54
54
|
}, fullWidth: true }));
|
|
55
55
|
}
|
|
@@ -36,10 +36,10 @@ export function SettingForm(props) {
|
|
|
36
36
|
height: 12px;
|
|
37
37
|
` }), multiline
|
|
38
38
|
? t("서술형 입력 칸", { context: "렉시컬 단답형/서술형 도구" })
|
|
39
|
-
: t("단답형 입력 칸", { context: "렉시컬 단답형/서술형 도구" })] }), _jsx(Content, { children: _jsxs(FormArea, { children: [_jsxs(Label, { children: [t("자리 표시자", { context: "렉시컬 단답형/서술형 도구" }), _jsx(Tooltip, { text: getTexts("descriptionDefaultInputText"), placement: "top", children: _jsx(QuestionFillIcon, { css: css `
|
|
39
|
+
: t("단답형 입력 칸", { context: "렉시컬 단답형/서술형 도구" })] }), _jsx(Content, { children: _jsxs(FormArea, { children: [_jsxs(Label, { children: [t("자리 표시자", { context: "렉시컬 단답형/서술형 도구" }), _jsx(Tooltip, { text: getTexts(t, "descriptionDefaultInputText"), placement: "top", children: _jsx(QuestionFillIcon, { css: css `
|
|
40
40
|
width: 12px;
|
|
41
41
|
height: 12px;
|
|
42
|
-
` }) })] }), _jsx(Controller, { name: "placeholder", control: control, render: ({ field: { value, onChange } }) => (_jsx(Input, { size: "small", color: "default", value: value, onChange: onChange, placeholder: getTexts("exampleEnterHere") })) })] }) }), _jsxs(Buttons, { children: [_jsx(Button, { color: "grey", size: "xsmall", label: t("닫기", { context: "렉시컬 도구 설정창" }), onClick: onClose }), _jsx(Button, { color: "primary", size: "xsmall", label: t("이대로 넣기", { context: "렉시컬 도구 설정창" }), bold: true, type: "submit" })] })] }));
|
|
42
|
+
` }) })] }), _jsx(Controller, { name: "placeholder", control: control, render: ({ field: { value, onChange } }) => (_jsx(Input, { size: "small", color: "default", value: value, onChange: onChange, placeholder: getTexts(t, "exampleEnterHere") })) })] }) }), _jsxs(Buttons, { children: [_jsx(Button, { color: "grey", size: "xsmall", label: t("닫기", { context: "렉시컬 도구 설정창" }), onClick: onClose }), _jsx(Button, { color: "primary", size: "xsmall", label: t("이대로 넣기", { context: "렉시컬 도구 설정창" }), bold: true, type: "submit" })] })] }));
|
|
43
43
|
}
|
|
44
44
|
const Form = styled.form(({ theme }) => css `
|
|
45
45
|
display: flex;
|
package/dist/patterns/LexicalEditor/nodes/SheetSelectNode/SelectComponent/SelectComponent.js
CHANGED
|
@@ -13,6 +13,7 @@ import { SquareButton } from "../../../../../components/SquareButton";
|
|
|
13
13
|
import { SettingForm } from "./SettingForm";
|
|
14
14
|
import { SelectBoxEdit, SelectBoxView } from "./SelectBox";
|
|
15
15
|
import { useTranslation } from "react-i18next";
|
|
16
|
+
import { getTexts } from "../../../../../texts";
|
|
16
17
|
export function SelectComponent(props) {
|
|
17
18
|
const { selections, selected, allowMultipleAnswers, nodeKey } = props;
|
|
18
19
|
const [editor] = useLexicalComposerContext();
|
|
@@ -25,7 +26,7 @@ export function SelectComponent(props) {
|
|
|
25
26
|
return (_jsxs(_Fragment, { children: [allowMultipleAnswers && (_jsxs(Alert, { children: [_jsx(AlarmWarningFillIcon, { css: css `
|
|
26
27
|
width: 14px;
|
|
27
28
|
height: 14px;
|
|
28
|
-
` }), t
|
|
29
|
+
` }), getTexts(t, "multipleChoicesProblem")] })), selections.map((selection, index) => (_jsx(SelectBoxView, { index: index + 1, isSelected: selected.includes(selection.value), image: selection.show.image, text: selection.show.text, onClick: freezeProblemNode
|
|
29
30
|
? undefined
|
|
30
31
|
: () => {
|
|
31
32
|
const isSelected = selected.includes(selection.value);
|
package/dist/patterns/LexicalEditor/nodes/SheetSelectNode/SelectComponent/SettingForm/SettingForm.js
CHANGED
|
@@ -44,7 +44,7 @@ export function SettingForm(props) {
|
|
|
44
44
|
function validateRequired(value) {
|
|
45
45
|
if (value.image || value.text)
|
|
46
46
|
return true;
|
|
47
|
-
return getTexts("errorRequiredField");
|
|
47
|
+
return getTexts(t, "errorRequiredField");
|
|
48
48
|
}
|
|
49
49
|
function validateDuplicated(value, formValues) {
|
|
50
50
|
const shows = formValues.selections.map((selection) => selection.show);
|
|
@@ -56,7 +56,7 @@ export function SettingForm(props) {
|
|
|
56
56
|
});
|
|
57
57
|
if (duplicatedIndexes.length < 2)
|
|
58
58
|
return true;
|
|
59
|
-
return getTexts("errorDuplicateChoice")(duplicatedIndexes.map((i) => i + 1).join(","));
|
|
59
|
+
return getTexts(t, "errorDuplicateChoice")(duplicatedIndexes.map((i) => i + 1).join(","));
|
|
60
60
|
}
|
|
61
61
|
return (_jsxs(Form, { onSubmit: handleSubmit(onSettingSubmit), children: [_jsxs(Title, { children: [_jsx(ListRadioIcon, { css: css `
|
|
62
62
|
width: 12px;
|
|
@@ -72,7 +72,7 @@ export function SettingForm(props) {
|
|
|
72
72
|
}
|
|
73
73
|
: undefined }, field.uid)))] }), _jsx(Button, { color: "grey", size: "small", startIcon: _jsx(AddFillIcon, {}), label: fields.length < 9
|
|
74
74
|
? t("선택지 추가", { context: "버튼, 렉시컬 도구 설정창" })
|
|
75
|
-
: getTexts("errorMaxChoicesExceeded"), onClick: () => {
|
|
75
|
+
: getTexts(t, "errorMaxChoicesExceeded"), onClick: () => {
|
|
76
76
|
if (fields.length >= 9)
|
|
77
77
|
return;
|
|
78
78
|
append({
|