@team-monolith/cds 1.8.8 → 1.9.1
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/patterns/LexicalEditor/LexicalEditor.js +2 -0
- package/dist/patterns/LexicalEditor/Plugins.js +2 -1
- package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/ProblemSelectNode.d.ts +43 -0
- package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/ProblemSelectNode.js +72 -0
- package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/SelectBox.d.ts +11 -0
- package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/SelectBox.js +73 -0
- package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/SelectComponent.d.ts +8 -0
- package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/SelectComponent.js +71 -0
- package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/SettingForm/FormSelection.d.ts +11 -0
- package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/SettingForm/FormSelection.js +95 -0
- package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/SettingForm/InsertImageDialog.d.ts +7 -0
- package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/SettingForm/InsertImageDialog.js +96 -0
- package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/SettingForm/InsertImageUploadedDialogBody.d.ts +9 -0
- package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/SettingForm/InsertImageUploadedDialogBody.js +51 -0
- package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/SettingForm/InsertImageUriDialogBody.d.ts +9 -0
- package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/SettingForm/InsertImageUriDialogBody.js +20 -0
- package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/SettingForm/SettingForm.d.ts +15 -0
- package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/SettingForm/SettingForm.js +119 -0
- package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/SettingForm/index.d.ts +1 -0
- package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/SettingForm/index.js +1 -0
- package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/index.d.ts +2 -0
- package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/index.js +2 -0
- package/dist/patterns/LexicalEditor/plugins/ComponentPickerMenuPlugin/ComponentPickerMenuPlugin.js +13 -1
- package/dist/patterns/LexicalEditor/plugins/ProblemSelectPlugin/index.d.ts +5 -0
- package/dist/patterns/LexicalEditor/plugins/ProblemSelectPlugin/index.js +20 -0
- package/dist/patterns/LexicalEditor/theme.d.ts +1 -0
- package/dist/patterns/LexicalEditor/theme.js +7 -0
- package/package.json +1 -1
|
@@ -11,6 +11,7 @@ import { getTheme } from "./theme";
|
|
|
11
11
|
import { useTheme } from "@emotion/react";
|
|
12
12
|
import Plugins from "./Plugins";
|
|
13
13
|
import { ColoredQuoteNode, ProblemInputNode } from "./nodes";
|
|
14
|
+
import { ProblemSelectNode } from "./nodes/ProblemSelectNode";
|
|
14
15
|
function validateValue(value) {
|
|
15
16
|
var _a, _b;
|
|
16
17
|
if (value && typeof value !== "object") {
|
|
@@ -35,6 +36,7 @@ export function LexicalEditor(props) {
|
|
|
35
36
|
onError: (error) => console.error(error),
|
|
36
37
|
nodes: [
|
|
37
38
|
ProblemInputNode,
|
|
39
|
+
ProblemSelectNode,
|
|
38
40
|
HeadingNode,
|
|
39
41
|
ListNode,
|
|
40
42
|
ListItemNode,
|
|
@@ -28,6 +28,7 @@ import useLexicalEditable from "@lexical/react/useLexicalEditable";
|
|
|
28
28
|
import ListMaxIndentLevelPlugin from "./plugins/ListMaxIndentLevelPlugin";
|
|
29
29
|
import styled from "@emotion/styled";
|
|
30
30
|
import ProblemInputPlugin from "./plugins/ProblemInputPlugin";
|
|
31
|
+
import ProblemSelectPlugin from "./plugins/ProblemSelectPlugin";
|
|
31
32
|
export default function Plugins(props) {
|
|
32
33
|
const { className, onChange } = props;
|
|
33
34
|
const isEditable = useLexicalEditable();
|
|
@@ -42,7 +43,7 @@ export default function Plugins(props) {
|
|
|
42
43
|
onChange === null || onChange === void 0 ? void 0 : onChange(editorState.toJSON());
|
|
43
44
|
},
|
|
44
45
|
// ignore 하지 않으면 Form에서 수정하지 않았는데 Dirty로 처리됨.
|
|
45
|
-
ignoreSelectionChange: true }), _jsx(AutoFocusPlugin, {}), isEditable && (_jsxs(_Fragment, { children: [_jsx(TabIndentationPlugin, {}), _jsx(ComponentPickerMenuPlugin, {}), _jsx(MarkdownShortcutPlugin, { transformers: CODLE_TRANSFORMERS }), _jsx(HistoryPlugin, {})] })), floatingAnchorElem && isEditable && (_jsxs(_Fragment, { children: [_jsx(ComponentAdderPlugin, { anchorElem: floatingAnchorElem }), _jsx(FloatingTextFormatToolbarPlugin, { anchorElem: floatingAnchorElem }), _jsx(FloatingLinkEditorPlugin, { anchorElem: floatingAnchorElem, isLinkEditMode: isLinkEditMode, setIsLinkEditMode: setIsLinkEditMode })] })), !isEditable && _jsx(LexicalClickableLinkPlugin, {}), _jsx(ListPlugin, {}), _jsx(HorizontalRulePlugin, {}), _jsx(ImagesPlugin, {}), _jsx(TablePlugin, {}), _jsx(LinkPlugin, {}), _jsx(ListMaxIndentLevelPlugin, { maxDepth: 5 }), _jsx(ProblemInputPlugin, {})] }));
|
|
46
|
+
ignoreSelectionChange: true }), _jsx(AutoFocusPlugin, {}), isEditable && (_jsxs(_Fragment, { children: [_jsx(TabIndentationPlugin, {}), _jsx(ComponentPickerMenuPlugin, {}), _jsx(MarkdownShortcutPlugin, { transformers: CODLE_TRANSFORMERS }), _jsx(HistoryPlugin, {})] })), floatingAnchorElem && isEditable && (_jsxs(_Fragment, { children: [_jsx(ComponentAdderPlugin, { anchorElem: floatingAnchorElem }), _jsx(FloatingTextFormatToolbarPlugin, { anchorElem: floatingAnchorElem }), _jsx(FloatingLinkEditorPlugin, { anchorElem: floatingAnchorElem, isLinkEditMode: isLinkEditMode, setIsLinkEditMode: setIsLinkEditMode })] })), !isEditable && _jsx(LexicalClickableLinkPlugin, {}), _jsx(ListPlugin, {}), _jsx(HorizontalRulePlugin, {}), _jsx(ImagesPlugin, {}), _jsx(TablePlugin, {}), _jsx(LinkPlugin, {}), _jsx(ListMaxIndentLevelPlugin, { maxDepth: 5 }), _jsx(ProblemInputPlugin, {}), _jsx(ProblemSelectPlugin, {})] }));
|
|
46
47
|
}
|
|
47
48
|
const ScrollArea = styled.div `
|
|
48
49
|
min-height: 150px;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { DecoratorNode, EditorConfig, LexicalNode, NodeKey, SerializedLexicalNode, Spread } from "lexical";
|
|
2
|
+
import { ReactNode } from "react";
|
|
3
|
+
export interface ImageProps {
|
|
4
|
+
src: string;
|
|
5
|
+
altText: string;
|
|
6
|
+
}
|
|
7
|
+
export interface Selection {
|
|
8
|
+
isAnswer: boolean;
|
|
9
|
+
show: {
|
|
10
|
+
image?: ImageProps;
|
|
11
|
+
text: string;
|
|
12
|
+
};
|
|
13
|
+
value: string;
|
|
14
|
+
}
|
|
15
|
+
export interface ProblemSelectPayload {
|
|
16
|
+
selections: Selection[];
|
|
17
|
+
selected: string[];
|
|
18
|
+
key?: NodeKey;
|
|
19
|
+
}
|
|
20
|
+
export type SerializedProblemSelectNode = Spread<ProblemSelectPayload, SerializedLexicalNode>;
|
|
21
|
+
/**
|
|
22
|
+
* selections는 Selection타입의 배열로서 객관식 정보를 담고 있습니다. (교사용)
|
|
23
|
+
* selected는 학생이 선택한 답의 value를 담고 있습니다.(학생용)
|
|
24
|
+
*/
|
|
25
|
+
export declare class ProblemSelectNode extends DecoratorNode<ReactNode> {
|
|
26
|
+
__selections: Selection[];
|
|
27
|
+
__selected: string[];
|
|
28
|
+
isInline(): boolean;
|
|
29
|
+
static getType(): string;
|
|
30
|
+
getSelections(): Selection[];
|
|
31
|
+
getSelected(): string[];
|
|
32
|
+
setSolutions(selections: Selection[]): void;
|
|
33
|
+
setSelected(selected: string[]): void;
|
|
34
|
+
static clone(node: ProblemSelectNode): ProblemSelectNode;
|
|
35
|
+
constructor(selections: Selection[], selected: string[], key?: NodeKey);
|
|
36
|
+
createDOM(config: EditorConfig): HTMLElement;
|
|
37
|
+
updateDOM(): boolean;
|
|
38
|
+
static importJSON(serializedNode: SerializedProblemSelectNode): ProblemSelectNode;
|
|
39
|
+
exportJSON(): SerializedProblemSelectNode;
|
|
40
|
+
decorate(): ReactNode;
|
|
41
|
+
}
|
|
42
|
+
export declare function $createProblemSelectNode({ selections, selected, key, }: ProblemSelectPayload): ProblemSelectNode;
|
|
43
|
+
export declare function $isProblemSelectNode(node: LexicalNode | null | undefined): node is ProblemSelectNode;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { $applyNodeReplacement, DecoratorNode, } from "lexical";
|
|
3
|
+
import { addClassNamesToElement } from "@lexical/utils";
|
|
4
|
+
import { SelectComponent } from "./SelectComponent";
|
|
5
|
+
/**
|
|
6
|
+
* selections는 Selection타입의 배열로서 객관식 정보를 담고 있습니다. (교사용)
|
|
7
|
+
* selected는 학생이 선택한 답의 value를 담고 있습니다.(학생용)
|
|
8
|
+
*/
|
|
9
|
+
export class ProblemSelectNode extends DecoratorNode {
|
|
10
|
+
isInline() {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
static getType() {
|
|
14
|
+
return "problem-select";
|
|
15
|
+
}
|
|
16
|
+
getSelections() {
|
|
17
|
+
return this.__selections;
|
|
18
|
+
}
|
|
19
|
+
getSelected() {
|
|
20
|
+
return this.__selected;
|
|
21
|
+
}
|
|
22
|
+
setSolutions(selections) {
|
|
23
|
+
const self = this.getWritable();
|
|
24
|
+
self.__selections = selections;
|
|
25
|
+
}
|
|
26
|
+
setSelected(selected) {
|
|
27
|
+
const self = this.getWritable();
|
|
28
|
+
self.__selected = selected;
|
|
29
|
+
}
|
|
30
|
+
static clone(node) {
|
|
31
|
+
return new ProblemSelectNode(node.__selections, node.__selected, node.__key);
|
|
32
|
+
}
|
|
33
|
+
constructor(selections, selected, key) {
|
|
34
|
+
super(key);
|
|
35
|
+
this.__selections = selections;
|
|
36
|
+
this.__selected = selected;
|
|
37
|
+
}
|
|
38
|
+
createDOM(config) {
|
|
39
|
+
// Define the DOM element here
|
|
40
|
+
const root = document.createElement("div");
|
|
41
|
+
addClassNamesToElement(root, config.theme.problemSelect);
|
|
42
|
+
return root;
|
|
43
|
+
}
|
|
44
|
+
updateDOM() {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
static importJSON(serializedNode) {
|
|
48
|
+
const node = $createProblemSelectNode({
|
|
49
|
+
key: serializedNode.key,
|
|
50
|
+
selections: serializedNode.selections,
|
|
51
|
+
selected: serializedNode.selected,
|
|
52
|
+
});
|
|
53
|
+
return node;
|
|
54
|
+
}
|
|
55
|
+
exportJSON() {
|
|
56
|
+
return {
|
|
57
|
+
version: 1,
|
|
58
|
+
type: "problem-select",
|
|
59
|
+
selections: this.__selections,
|
|
60
|
+
selected: this.__selected,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
decorate() {
|
|
64
|
+
return (_jsx(SelectComponent, { selections: this.__selections, selected: this.__selected, nodeKey: this.getKey() }));
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
export function $createProblemSelectNode({ selections, selected, key, }) {
|
|
68
|
+
return $applyNodeReplacement(new ProblemSelectNode(selections, selected, key));
|
|
69
|
+
}
|
|
70
|
+
export function $isProblemSelectNode(node) {
|
|
71
|
+
return node instanceof ProblemSelectNode;
|
|
72
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { ImageProps } from "./ProblemSelectNode";
|
|
2
|
+
export interface SelectBoxProps {
|
|
3
|
+
index: number;
|
|
4
|
+
isSelected?: boolean;
|
|
5
|
+
isAnswer?: boolean;
|
|
6
|
+
image?: ImageProps;
|
|
7
|
+
text: string;
|
|
8
|
+
onClick: () => void;
|
|
9
|
+
fullWidth?: boolean;
|
|
10
|
+
}
|
|
11
|
+
export default function SelectBox(props: SelectBoxProps): import("@emotion/react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "@emotion/react/jsx-runtime";
|
|
2
|
+
/** @jsxImportSource @emotion/react */
|
|
3
|
+
import styled from "@emotion/styled";
|
|
4
|
+
import { css, useTheme } from "@emotion/react";
|
|
5
|
+
import { CheckFillIcon, CheckboxCircleFillIcon } from "../../../../icons";
|
|
6
|
+
export default function SelectBox(props) {
|
|
7
|
+
const { index, isSelected, isAnswer, image, text, onClick, fullWidth } = props;
|
|
8
|
+
const theme = useTheme();
|
|
9
|
+
return (_jsxs(Container, Object.assign({ isSelected: isSelected, fullWidth: fullWidth, onClick: onClick }, { children: [_jsx(Index, Object.assign({ isSelected: isSelected }, { children: isSelected ? (_jsx(CheckFillIcon, { css: css `
|
|
10
|
+
width: 12px;
|
|
11
|
+
height: 12px;
|
|
12
|
+
` })) : (index) })), _jsxs(Content, { children: [image && (_jsx("img", { src: image.src, alt: image.altText, css: css `
|
|
13
|
+
height: auto;
|
|
14
|
+
// 이미지로 인해 좌우로 스크롤이 생기는 것을 방지
|
|
15
|
+
max-width: min(328px, 100%);
|
|
16
|
+
width: fit-content;
|
|
17
|
+
border-radius: 6px;
|
|
18
|
+
` })), text] }), isAnswer && (_jsx(CheckboxCircleFillIcon, { color: theme.color.foreground.success, css: css `
|
|
19
|
+
width: 16px;
|
|
20
|
+
height: 16px;
|
|
21
|
+
` }))] })));
|
|
22
|
+
}
|
|
23
|
+
const Container = styled.div(({ theme, isSelected, fullWidth }) => css `
|
|
24
|
+
cursor: pointer;
|
|
25
|
+
display: flex;
|
|
26
|
+
box-sizing: border-box;
|
|
27
|
+
width: ${fullWidth ? "100%" : "400px"};
|
|
28
|
+
padding: 8px;
|
|
29
|
+
gap: 8px;
|
|
30
|
+
border-radius: 8px;
|
|
31
|
+
background: ${isSelected
|
|
32
|
+
? theme.color.container.primaryContainer
|
|
33
|
+
: theme.color.background.neutralAlt};
|
|
34
|
+
border: ${isSelected
|
|
35
|
+
? `1px solid ${theme.color.foreground.primary}`
|
|
36
|
+
: "1px solid transparent"};
|
|
37
|
+
color: ${isSelected
|
|
38
|
+
? theme.color.container.primaryOnContainer
|
|
39
|
+
: theme.color.foreground.neutralBase};
|
|
40
|
+
`);
|
|
41
|
+
const Index = styled.div(({ theme, isSelected }) => css `
|
|
42
|
+
display: flex;
|
|
43
|
+
box-sizing: border-box;
|
|
44
|
+
width: 20px;
|
|
45
|
+
height: 20px;
|
|
46
|
+
padding: 4px;
|
|
47
|
+
justify-content: center;
|
|
48
|
+
align-items: center;
|
|
49
|
+
border-radius: 4px;
|
|
50
|
+
border: ${isSelected
|
|
51
|
+
? "none"
|
|
52
|
+
: `1px solid ${theme.color.background.neutralAltActive}`};
|
|
53
|
+
background: ${isSelected
|
|
54
|
+
? theme.color.background.primary
|
|
55
|
+
: theme.color.background.neutralBase};
|
|
56
|
+
color: ${isSelected
|
|
57
|
+
? theme.color.foreground.neutralAlt
|
|
58
|
+
: theme.color.foreground.neutralBaseDisabled};
|
|
59
|
+
font-family: ${theme.fontFamily.ui};
|
|
60
|
+
font-size: 14px;
|
|
61
|
+
font-weight: 800;
|
|
62
|
+
line-height: 16px;
|
|
63
|
+
`);
|
|
64
|
+
const Content = styled.div(({ theme }) => css `
|
|
65
|
+
display: flex;
|
|
66
|
+
flex-direction: column;
|
|
67
|
+
gap: 12px;
|
|
68
|
+
flex: 1;
|
|
69
|
+
font-family: ${theme.fontFamily.ui};
|
|
70
|
+
font-size: 14px;
|
|
71
|
+
font-weight: 400;
|
|
72
|
+
line-height: 20px;
|
|
73
|
+
`);
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** @jsxImportSource @emotion/react */
|
|
2
|
+
import { NodeKey } from "lexical";
|
|
3
|
+
import { Selection } from "./ProblemSelectNode";
|
|
4
|
+
export declare function SelectComponent(props: {
|
|
5
|
+
selections: Selection[];
|
|
6
|
+
selected: string[];
|
|
7
|
+
nodeKey: NodeKey;
|
|
8
|
+
}): import("@emotion/react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "@emotion/react/jsx-runtime";
|
|
2
|
+
/** @jsxImportSource @emotion/react */
|
|
3
|
+
import { $getNodeByKey } from "lexical";
|
|
4
|
+
import { $isProblemSelectNode, } from "./ProblemSelectNode";
|
|
5
|
+
import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
|
|
6
|
+
import { useState } from "react";
|
|
7
|
+
import useLexicalEditable from "@lexical/react/useLexicalEditable";
|
|
8
|
+
import { useFieldArray, useForm } from "react-hook-form";
|
|
9
|
+
import SelectBox from "./SelectBox";
|
|
10
|
+
import { css } from "@emotion/react";
|
|
11
|
+
import SquareButton from "../../../../components/SquareButton";
|
|
12
|
+
import { Settings3FillIcon } from "../../../../icons";
|
|
13
|
+
import SettingForm from "./SettingForm";
|
|
14
|
+
export function SelectComponent(props) {
|
|
15
|
+
const { selections, selected, nodeKey } = props;
|
|
16
|
+
const [editor] = useLexicalComposerContext();
|
|
17
|
+
const [settingOpen, setSettingOpen] = useState(false);
|
|
18
|
+
const isEditable = useLexicalEditable();
|
|
19
|
+
const { control, handleSubmit } = useForm({
|
|
20
|
+
mode: "all",
|
|
21
|
+
defaultValues: {
|
|
22
|
+
selections,
|
|
23
|
+
selected,
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
const { fields, append, remove, update } = useFieldArray({
|
|
27
|
+
control,
|
|
28
|
+
name: "selections",
|
|
29
|
+
keyName: "uid",
|
|
30
|
+
});
|
|
31
|
+
// 학생 view
|
|
32
|
+
if (!isEditable) {
|
|
33
|
+
return (_jsx(_Fragment, { children: fields.map((field, index) => (_jsx(SelectBox, { index: index + 1, isSelected: selected.includes(field.value), image: field.show.image, text: field.show.text, onClick: () => {
|
|
34
|
+
const isSelected = selected.includes(field.value);
|
|
35
|
+
if (isSelected) {
|
|
36
|
+
editor.update(() => {
|
|
37
|
+
const node = $getNodeByKey(nodeKey);
|
|
38
|
+
if (!$isProblemSelectNode(node)) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const newSelected = [...selected];
|
|
42
|
+
const index = newSelected.indexOf(field.value);
|
|
43
|
+
newSelected.splice(index, 1);
|
|
44
|
+
node.setSelected(newSelected);
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
editor.update(() => {
|
|
49
|
+
const node = $getNodeByKey(nodeKey);
|
|
50
|
+
if (!$isProblemSelectNode(node)) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const newSelected = [...selected];
|
|
54
|
+
newSelected.push(field.value);
|
|
55
|
+
node.setSelected(newSelected);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}, fullWidth: true }, index))) }));
|
|
59
|
+
}
|
|
60
|
+
// 교사 edit view
|
|
61
|
+
return (_jsxs(_Fragment, { children: [_jsxs("div", Object.assign({ css: css `
|
|
62
|
+
display: flex;
|
|
63
|
+
gap: 4px;
|
|
64
|
+
` }, { children: [_jsx("div", Object.assign({ css: css `
|
|
65
|
+
display: flex;
|
|
66
|
+
flex-direction: column;
|
|
67
|
+
gap: 4px;
|
|
68
|
+
` }, { children: fields.map((field, index) => (_jsx(SelectBox, { index: index + 1, isAnswer: field.isAnswer, image: field.show.image, text: field.show.text || `${index + 1}번 선택지`, onClick: () => setSettingOpen(true) }, index))) })), _jsx(SquareButton, { size: "small", color: "icon", icon: _jsx(Settings3FillIcon, {}), onClick: () => {
|
|
69
|
+
setSettingOpen(true);
|
|
70
|
+
} })] })), settingOpen && (_jsx(SettingForm, { control: control, handleSubmit: handleSubmit, fields: fields, append: append, remove: remove, update: update, nodeKey: nodeKey, onClose: () => setSettingOpen(false) }))] }));
|
|
71
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { ProblemSelectPayload } from "../ProblemSelectNode";
|
|
2
|
+
import { Control, FieldArrayWithId, UseFieldArrayUpdate } from "react-hook-form";
|
|
3
|
+
export interface FormSelectionProps {
|
|
4
|
+
index: number;
|
|
5
|
+
control: Control<ProblemSelectPayload, any>;
|
|
6
|
+
field: FieldArrayWithId<ProblemSelectPayload, "selections", "uid">;
|
|
7
|
+
update: UseFieldArrayUpdate<ProblemSelectPayload, "selections">;
|
|
8
|
+
rules?: any;
|
|
9
|
+
onDelete?: () => void;
|
|
10
|
+
}
|
|
11
|
+
export declare function FormSelection(props: FormSelectionProps): import("@emotion/react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "@emotion/react/jsx-runtime";
|
|
2
|
+
/** @jsxImportSource @emotion/react */
|
|
3
|
+
import styled from "@emotion/styled";
|
|
4
|
+
import { Controller, } from "react-hook-form";
|
|
5
|
+
import { css } from "@emotion/react";
|
|
6
|
+
import Input from "../../../../../components/Input";
|
|
7
|
+
import { DeleteBinLineIcon, ErrorWarningFillIcon, ImageAddFillIcon, ImageEditFillIcon, } from "../../../../../icons";
|
|
8
|
+
import SquareButton from "../../../../../components/SquareButton";
|
|
9
|
+
import Switch from "../../../../../components/Switch";
|
|
10
|
+
import { useState } from "react";
|
|
11
|
+
import { InsertImageDialog } from "./InsertImageDialog";
|
|
12
|
+
export function FormSelection(props) {
|
|
13
|
+
const { index, control, field, update, rules, onDelete } = props;
|
|
14
|
+
const [imageOpen, setImageOpen] = useState(false);
|
|
15
|
+
const [inputFocused, setInputFocused] = useState(false);
|
|
16
|
+
return (_jsxs(_Fragment, { children: [_jsx(InsertImageDialog, { open: imageOpen, onClose: () => setImageOpen(false), updateImg: (props) => {
|
|
17
|
+
update(index, Object.assign(Object.assign({}, field), { show: Object.assign(Object.assign({}, field.show), { image: props }) }));
|
|
18
|
+
} }), _jsxs(Container, { children: [_jsx(Index, { children: index + 1 }), _jsxs("div", Object.assign({ css: css `
|
|
19
|
+
display: flex;
|
|
20
|
+
flex: 1;
|
|
21
|
+
flex-direction: column;
|
|
22
|
+
gap: 4px;
|
|
23
|
+
` }, { children: [field.show.image && field.show.image.src && (_jsx("img", { src: field.show.image.src, alt: field.show.image.altText, css: css `
|
|
24
|
+
height: auto;
|
|
25
|
+
// 이미지로 인해 좌우로 스크롤이 생기는 것을 방지
|
|
26
|
+
max-width: min(400px, 100%);
|
|
27
|
+
width: fit-content;
|
|
28
|
+
border-radius: 6px;
|
|
29
|
+
`, draggable: "false" })), _jsx(Controller, { name: `selections.${index}.show.text`, control: control, rules: rules, render: ({ field: { value, onChange }, fieldState: { invalid, error }, }) => (_jsx(Input, { size: "small", color: invalid
|
|
30
|
+
? "activeDanger"
|
|
31
|
+
: inputFocused
|
|
32
|
+
? "activePrimary"
|
|
33
|
+
: "default", value: value, onChange: onChange, inputProps: {
|
|
34
|
+
onFocus: (_e) => {
|
|
35
|
+
setInputFocused(true);
|
|
36
|
+
},
|
|
37
|
+
onBlur: (_e) => {
|
|
38
|
+
setInputFocused(false);
|
|
39
|
+
// onBlur시에 선택지 미리보기에 반영합니다.
|
|
40
|
+
update(index, Object.assign(Object.assign({}, field), { show: Object.assign(Object.assign({}, field.show), { text: value }) }));
|
|
41
|
+
},
|
|
42
|
+
}, placeholder: `${index + 1}번 선택지`, hintIcon: invalid ? _jsx(ErrorWarningFillIcon, {}) : undefined, hintText: error === null || error === void 0 ? void 0 : error.message, multiline: true, fullWidth: true, css: css `
|
|
43
|
+
flex: 1;
|
|
44
|
+
` })) })] })), _jsxs("div", Object.assign({ css: css `
|
|
45
|
+
display: flex;
|
|
46
|
+
height: 36px;
|
|
47
|
+
gap: 8px;
|
|
48
|
+
align-items: center;
|
|
49
|
+
` }, { children: [_jsx(SquareButton, { color: "icon", size: "xsmall", icon: field.show.image ? _jsx(ImageEditFillIcon, {}) : _jsx(ImageAddFillIcon, {}), onClick: () => {
|
|
50
|
+
setImageOpen(true);
|
|
51
|
+
} }), _jsx(Controller, { name: `selections.${index}.isAnswer`, control: control, render: ({ field: { value, onChange } }) => (_jsxs(Answer, Object.assign({ onClick: () => {
|
|
52
|
+
onChange(!value);
|
|
53
|
+
// 선택지 미리보기에 정답여부를 반영합니다.
|
|
54
|
+
update(index, Object.assign(Object.assign({}, field), { isAnswer: !value }));
|
|
55
|
+
} }, { children: ["\uC815\uB2F5", _jsx(Switch, { checked: value, size: "small" })] }))) }), onDelete && (_jsx(SquareButton, { color: "white", size: "xsmall", icon: _jsx(DeleteBinLineIcon, {}), onClick: onDelete }))] }))] })] }));
|
|
56
|
+
}
|
|
57
|
+
const Container = styled.div(({ theme }) => css `
|
|
58
|
+
display: flex;
|
|
59
|
+
padding: 4px 12px;
|
|
60
|
+
gap: 8px;
|
|
61
|
+
border-radius: 8px;
|
|
62
|
+
background: ${theme.color.background.neutralAlt};
|
|
63
|
+
`);
|
|
64
|
+
const Index = styled.div(({ theme }) => css `
|
|
65
|
+
display: flex;
|
|
66
|
+
box-sizing: border-box;
|
|
67
|
+
width: 20px;
|
|
68
|
+
height: 20px;
|
|
69
|
+
padding: 4px;
|
|
70
|
+
margin-top: 8px;
|
|
71
|
+
justify-content: center;
|
|
72
|
+
align-items: center;
|
|
73
|
+
border-radius: 4px;
|
|
74
|
+
border: 1px solid ${theme.color.background.neutralAltActive};
|
|
75
|
+
background: ${theme.color.background.neutralBase};
|
|
76
|
+
color: ${theme.color.foreground.neutralBaseDisabled};
|
|
77
|
+
font-family: ${theme.fontFamily.ui};
|
|
78
|
+
font-size: 14px;
|
|
79
|
+
font-weight: 800;
|
|
80
|
+
line-height: 16px;
|
|
81
|
+
`);
|
|
82
|
+
const Answer = styled.div(({ theme }) => css `
|
|
83
|
+
display: flex;
|
|
84
|
+
align-items: center;
|
|
85
|
+
padding-right: 4px;
|
|
86
|
+
gap: 8px;
|
|
87
|
+
color: ${theme.color.foreground.neutralBase};
|
|
88
|
+
cursor: pointer;
|
|
89
|
+
/* Default/Label/14px-Md */
|
|
90
|
+
font-family: ${theme.fontFamily.ui};
|
|
91
|
+
font-size: 14px;
|
|
92
|
+
font-style: normal;
|
|
93
|
+
font-weight: 500;
|
|
94
|
+
line-height: 16px; /* 114.286% */
|
|
95
|
+
`);
|
package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/SettingForm/InsertImageDialog.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { ImageProps } from "../ProblemSelectNode";
|
|
2
|
+
export interface InsertImageDialogProps {
|
|
3
|
+
open: boolean;
|
|
4
|
+
onClose: () => void;
|
|
5
|
+
updateImg: (props: ImageProps) => void;
|
|
6
|
+
}
|
|
7
|
+
export declare function InsertImageDialog(props: InsertImageDialogProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,96 @@
|
|
|
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
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
11
|
+
import { useContext, useRef, useState } from "react";
|
|
12
|
+
import styled from "@emotion/styled";
|
|
13
|
+
import { useTheme } from "@emotion/react";
|
|
14
|
+
import { ImageFillIcon, LinkIcon, UploadLineIcon } from "../../../../../icons";
|
|
15
|
+
import { AlertDialog, AlertDialogContent, AlertDialogTitle, } from "../../../../../components/AlertDialog";
|
|
16
|
+
import Button from "../../../../../components/Button";
|
|
17
|
+
import { CodleDesignSystemContext } from "../../../../../CodleDesignSystemProvider";
|
|
18
|
+
import { InsertImageUriDialogBody } from "./InsertImageUriDialogBody";
|
|
19
|
+
import { InsertImageUploadedDialogBody } from "./InsertImageUploadedDialogBody";
|
|
20
|
+
export function InsertImageDialog(props) {
|
|
21
|
+
const { open, onClose, updateImg } = props;
|
|
22
|
+
const theme = useTheme();
|
|
23
|
+
const [mode, setMode] = useState(null);
|
|
24
|
+
const inputRef = useRef(null);
|
|
25
|
+
const cdsContext = useContext(CodleDesignSystemContext);
|
|
26
|
+
const [src, setSrc] = useState("");
|
|
27
|
+
const [altText, setAltText] = useState("");
|
|
28
|
+
const onClick = (props) => {
|
|
29
|
+
updateImg(props);
|
|
30
|
+
handleOnClose();
|
|
31
|
+
};
|
|
32
|
+
const handleOnClose = () => {
|
|
33
|
+
setMode(null);
|
|
34
|
+
setSrc("");
|
|
35
|
+
setAltText("");
|
|
36
|
+
onClose();
|
|
37
|
+
};
|
|
38
|
+
return (_jsxs(StyledAlertDialog, Object.assign({ icon: _jsx(ImageFillIcon, { color: theme.color.background.primary }), open: open, onClose: handleOnClose, disableIconPadding: true }, { children: [_jsx(StyledAlertDialogTitle, Object.assign({ onClose: handleOnClose }, { children: "\uC774\uBBF8\uC9C0 \uC0BD\uC785\uD558\uAE30" })), !mode && (_jsx(AlertDialogContent, { children: _jsxs(Buttons, { children: [_jsxs(ButtonAndDescription, { children: [_jsx(Description, { children: "\uC774\uBBF8\uC9C0 URL\uC744 \uC54C\uACE0 \uC788\uB2E4\uBA74" }), _jsx(Button, { color: "grey", size: "medium", fullWidth: true, label: "URL\uB85C \uC0BD\uC785\uD558\uAE30", startIcon: _jsx(LinkIcon, {}), onClick: () => setMode("url") })] }), _jsxs(ButtonAndDescription, { children: [_jsx(Description, { children: "\uAC16\uACE0 \uC788\uB294 \uC774\uBBF8\uC9C0\uB97C \uC0BD\uC785\uD558\uACE0 \uC2F6\uB2E4\uBA74" }), _jsx(Button, { color: "grey", size: "medium", fullWidth: true, label: "\uD30C\uC77C \uC120\uD0DD\uD558\uAE30", startIcon: _jsx(UploadLineIcon, {}), onClick: () => {
|
|
39
|
+
var _a;
|
|
40
|
+
(_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.click();
|
|
41
|
+
} }), _jsx(HiddenInput, { ref: inputRef, type: "file", accept: "image/*", onChange: (event) => __awaiter(this, void 0, void 0, function* () {
|
|
42
|
+
var _a, _b;
|
|
43
|
+
const file = (_a = event.target.files) === null || _a === void 0 ? void 0 : _a[0];
|
|
44
|
+
if (!file)
|
|
45
|
+
return;
|
|
46
|
+
const uploadByFile = (_b = cdsContext.lexical) === null || _b === void 0 ? void 0 : _b.uploadByFile;
|
|
47
|
+
if (uploadByFile) {
|
|
48
|
+
setSrc(yield uploadByFile(file));
|
|
49
|
+
setMode("file");
|
|
50
|
+
}
|
|
51
|
+
}) })] })] }) })), mode === "url" && (_jsx(InsertImageUriDialogBody, { src: src, setSrc: setSrc, altText: altText, setAltText: setAltText, onClick: onClick })), mode === "file" && (_jsx(InsertImageUploadedDialogBody, { src: src, setSrc: setSrc, altText: altText, setAltText: setAltText, onClick: onClick }))] })));
|
|
52
|
+
}
|
|
53
|
+
const StyledAlertDialog = styled(AlertDialog) `
|
|
54
|
+
gap: 16px;
|
|
55
|
+
`;
|
|
56
|
+
const StyledAlertDialogTitle = styled(AlertDialogTitle) `
|
|
57
|
+
color: ${({ theme }) => theme.color.foreground.neutralBase};
|
|
58
|
+
text-align: center;
|
|
59
|
+
|
|
60
|
+
/* Default/Heading/20px-Bd */
|
|
61
|
+
font-family: ${({ theme }) => theme.fontFamily.ui};
|
|
62
|
+
font-size: 20px;
|
|
63
|
+
font-style: normal;
|
|
64
|
+
font-weight: 700;
|
|
65
|
+
line-height: 28px; /* 140% */
|
|
66
|
+
letter-spacing: 0.25px;
|
|
67
|
+
`;
|
|
68
|
+
const Buttons = styled.div `
|
|
69
|
+
display: flex;
|
|
70
|
+
align-items: flex-start;
|
|
71
|
+
gap: 8px;
|
|
72
|
+
|
|
73
|
+
// Actions가 없는 경우 하단이 디자인과 맞지 않는다
|
|
74
|
+
margin-bottom: -16px;
|
|
75
|
+
`;
|
|
76
|
+
const ButtonAndDescription = styled.div `
|
|
77
|
+
display: flex;
|
|
78
|
+
flex-direction: column;
|
|
79
|
+
align-items: center;
|
|
80
|
+
gap: 8px;
|
|
81
|
+
flex: 1 0 0;
|
|
82
|
+
`;
|
|
83
|
+
const Description = styled.div `
|
|
84
|
+
color: ${({ theme }) => theme.color.foreground.neutralBaseDisabled};
|
|
85
|
+
text-align: center;
|
|
86
|
+
|
|
87
|
+
/* Default/Label/12px-Md */
|
|
88
|
+
font-family: ${({ theme }) => theme.fontFamily.ui};
|
|
89
|
+
font-size: 12px;
|
|
90
|
+
font-style: normal;
|
|
91
|
+
font-weight: 500;
|
|
92
|
+
line-height: 16px; /* 133.333% */
|
|
93
|
+
`;
|
|
94
|
+
const HiddenInput = styled.input `
|
|
95
|
+
display: none;
|
|
96
|
+
`;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { ImageProps } from "../ProblemSelectNode";
|
|
2
|
+
export interface InsertImageUploadedDialogBodyProps {
|
|
3
|
+
src: string;
|
|
4
|
+
setSrc: (src: string) => void;
|
|
5
|
+
altText: string;
|
|
6
|
+
setAltText: (altText: string) => void;
|
|
7
|
+
onClick: (props: ImageProps) => void;
|
|
8
|
+
}
|
|
9
|
+
export declare function InsertImageUploadedDialogBody(props: InsertImageUploadedDialogBodyProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,51 @@
|
|
|
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
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
11
|
+
import { useContext, useRef } from "react";
|
|
12
|
+
import styled from "@emotion/styled";
|
|
13
|
+
import { CodleDesignSystemContext } from "../../../../../CodleDesignSystemProvider";
|
|
14
|
+
import { AlertDialogActions, AlertDialogContent, } from "../../../../../components/AlertDialog";
|
|
15
|
+
import Button from "../../../../../components/Button";
|
|
16
|
+
import { RefreshLineIcon } from "../../../../../icons";
|
|
17
|
+
import Input from "../../../../../components/Input";
|
|
18
|
+
export function InsertImageUploadedDialogBody(props) {
|
|
19
|
+
const { src, setSrc, altText, setAltText, onClick } = props;
|
|
20
|
+
const inputRef = useRef(null);
|
|
21
|
+
const cdsContext = useContext(CodleDesignSystemContext);
|
|
22
|
+
const isDisabled = src === "";
|
|
23
|
+
return (_jsxs(_Fragment, { children: [_jsx(AlertDialogContent, { children: _jsxs(Inputs, { children: [_jsx(Button, { fullWidth: true, color: "grey", size: "medium", label: "\uD30C\uC77C \uBC14\uAFB8\uAE30", onClick: () => {
|
|
24
|
+
var _a;
|
|
25
|
+
(_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.click();
|
|
26
|
+
}, startIcon: _jsx(RefreshLineIcon, {}) }), _jsx(HiddenInput, { ref: inputRef, type: "file", accept: "image/*", onChange: (event) => __awaiter(this, void 0, void 0, function* () {
|
|
27
|
+
var _a, _b;
|
|
28
|
+
const file = (_a = event.target.files) === null || _a === void 0 ? void 0 : _a[0];
|
|
29
|
+
if (!file)
|
|
30
|
+
return;
|
|
31
|
+
const uploadByFile = (_b = cdsContext.lexical) === null || _b === void 0 ? void 0 : _b.uploadByFile;
|
|
32
|
+
if (uploadByFile) {
|
|
33
|
+
setSrc(yield uploadByFile(file));
|
|
34
|
+
}
|
|
35
|
+
}) }), _jsxs(ImagePreview, { children: ["\uC774\uBBF8\uC9C0 \uBBF8\uB9AC\uBCF4\uAE30", _jsx("img", { src: src, alt: altText })] }), _jsx(Input, { fullWidth: true, label: "\uB300\uCCB4 \uD14D\uC2A4\uD2B8", placeholder: "\uC0BD\uC785\uD558\uB294 \uC774\uBBF8\uC9C0\uC5D0 \uAD00\uD55C \uC124\uBA85", color: "default", size: "medium", onChange: (e) => {
|
|
36
|
+
setAltText(e.target.value);
|
|
37
|
+
}, value: altText })] }) }), _jsx(AlertDialogActions, { children: _jsx(Button, { fullWidth: true, label: "\uC0BD\uC785\uD558\uAE30", size: "medium", color: "primary", disabled: isDisabled, onClick: () => onClick({ altText, src }) }) })] }));
|
|
38
|
+
}
|
|
39
|
+
const Inputs = styled.div `
|
|
40
|
+
display: flex;
|
|
41
|
+
flex-direction: column;
|
|
42
|
+
gap: 16px;
|
|
43
|
+
`;
|
|
44
|
+
const HiddenInput = styled.input `
|
|
45
|
+
display: none;
|
|
46
|
+
`;
|
|
47
|
+
const ImagePreview = styled.div `
|
|
48
|
+
display: flex;
|
|
49
|
+
flex-direction: column;
|
|
50
|
+
gap: 8px;
|
|
51
|
+
`;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { ImageProps } from "../ProblemSelectNode";
|
|
2
|
+
export interface InsertImageUriDialogBodyProps {
|
|
3
|
+
src: string;
|
|
4
|
+
setSrc: (src: string) => void;
|
|
5
|
+
altText: string;
|
|
6
|
+
setAltText: (altText: string) => void;
|
|
7
|
+
onClick: (props: ImageProps) => void;
|
|
8
|
+
}
|
|
9
|
+
export declare function InsertImageUriDialogBody(props: InsertImageUriDialogBodyProps): import("react/jsx-runtime").JSX.Element;
|
package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/SettingForm/InsertImageUriDialogBody.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import styled from "@emotion/styled";
|
|
3
|
+
import { AlertDialogActions, AlertDialogContent, } from "../../../../../components/AlertDialog";
|
|
4
|
+
import Input from "../../../../../components/Input";
|
|
5
|
+
import { LinkIcon } from "../../../../../icons";
|
|
6
|
+
import Button from "../../../../../components/Button";
|
|
7
|
+
export function InsertImageUriDialogBody(props) {
|
|
8
|
+
const { src, setSrc, altText, setAltText, onClick } = props;
|
|
9
|
+
const isDisabled = src === "";
|
|
10
|
+
return (_jsxs(_Fragment, { children: [" ", _jsx(AlertDialogContent, { children: _jsxs(Inputs, { children: [_jsx(Input, { fullWidth: true, label: "URL", placeholder: "https://www.pexels.com/photo/n-2848492/", color: "default", size: "medium", onChange: (e) => {
|
|
11
|
+
setSrc(e.target.value);
|
|
12
|
+
}, value: src, startIcon: _jsx(LinkIcon, {}) }), _jsx(Input, { fullWidth: true, label: "\uB300\uCCB4 \uD14D\uC2A4\uD2B8", placeholder: "\uC0BD\uC785\uD558\uB294 \uC774\uBBF8\uC9C0\uC5D0 \uAD00\uD55C \uC124\uBA85", color: "default", size: "medium", onChange: (e) => {
|
|
13
|
+
setAltText(e.target.value);
|
|
14
|
+
}, value: altText })] }) }), _jsx(AlertDialogActions, { children: _jsx(Button, { fullWidth: true, label: "\uC0BD\uC785\uD558\uAE30", size: "medium", color: "primary", disabled: isDisabled, onClick: () => onClick({ altText, src }) }) })] }));
|
|
15
|
+
}
|
|
16
|
+
const Inputs = styled.div `
|
|
17
|
+
display: flex;
|
|
18
|
+
flex-direction: column;
|
|
19
|
+
gap: 16px;
|
|
20
|
+
`;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** @jsxImportSource @emotion/react */
|
|
2
|
+
import { Control, FieldArrayWithId, UseFieldArrayAppend, UseFieldArrayRemove, UseFieldArrayUpdate, UseFormHandleSubmit } from "react-hook-form";
|
|
3
|
+
import { ProblemSelectPayload } from "../ProblemSelectNode";
|
|
4
|
+
import { NodeKey } from "lexical";
|
|
5
|
+
export interface SettingFormProps {
|
|
6
|
+
control: Control<ProblemSelectPayload, any>;
|
|
7
|
+
handleSubmit: UseFormHandleSubmit<ProblemSelectPayload, undefined>;
|
|
8
|
+
fields: FieldArrayWithId<ProblemSelectPayload, "selections", "uid">[];
|
|
9
|
+
append: UseFieldArrayAppend<ProblemSelectPayload, "selections">;
|
|
10
|
+
remove: UseFieldArrayRemove;
|
|
11
|
+
update: UseFieldArrayUpdate<ProblemSelectPayload, "selections">;
|
|
12
|
+
nodeKey: NodeKey;
|
|
13
|
+
onClose: () => void;
|
|
14
|
+
}
|
|
15
|
+
export default function SettingForm(props: SettingFormProps): import("@emotion/react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "@emotion/react/jsx-runtime";
|
|
2
|
+
import { $isProblemSelectNode, } from "../ProblemSelectNode";
|
|
3
|
+
import { $getNodeByKey } from "lexical";
|
|
4
|
+
import { css } from "@emotion/react";
|
|
5
|
+
import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
|
|
6
|
+
import styled from "@emotion/styled";
|
|
7
|
+
import shadows from "../../../../../foundation/shadows";
|
|
8
|
+
import { AddFillIcon, AlarmWarningFillIcon, ListRadioIcon, } from "../../../../../icons";
|
|
9
|
+
import Button from "../../../../../components/Button";
|
|
10
|
+
import { FormSelection } from "./FormSelection";
|
|
11
|
+
export default function SettingForm(props) {
|
|
12
|
+
const { control, handleSubmit, fields, append, remove, update, nodeKey, onClose, } = props;
|
|
13
|
+
const [editor] = useLexicalComposerContext();
|
|
14
|
+
const onSettingSubmit = (data) => {
|
|
15
|
+
editor.update(() => {
|
|
16
|
+
const node = $getNodeByKey(nodeKey);
|
|
17
|
+
if (!$isProblemSelectNode(node)) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
node.setSolutions(data.selections);
|
|
21
|
+
});
|
|
22
|
+
onClose();
|
|
23
|
+
};
|
|
24
|
+
function validateDuplicatedSelection(fields, index) {
|
|
25
|
+
if (index === 0)
|
|
26
|
+
return true;
|
|
27
|
+
const duplicatedIndex = fields
|
|
28
|
+
.slice(0, index)
|
|
29
|
+
.findIndex((prevField) => prevField.show.text === fields[index].show.text);
|
|
30
|
+
if (duplicatedIndex < 0)
|
|
31
|
+
return true;
|
|
32
|
+
return `${duplicatedIndex + 1}번 선택지와 같은 내용입니다.`;
|
|
33
|
+
}
|
|
34
|
+
const hasMultipleAnswers = fields.filter((field) => field.isAnswer).length > 1;
|
|
35
|
+
return (_jsxs(Form, Object.assign({ onSubmit: handleSubmit(onSettingSubmit) }, { children: [_jsxs(Title, { children: [_jsx(ListRadioIcon, { css: css `
|
|
36
|
+
width: 12px;
|
|
37
|
+
height: 12px;
|
|
38
|
+
` }), "\uAC1D\uAD00\uC2DD \uC785\uB825 \uCE78"] }), _jsxs(Content, { children: [_jsxs(FormArea, { children: [_jsx(Label, { children: "\uB2F5\uC548" }), fields.map((field, index) => (_jsx(FormSelection, { index: index, control: control, field: field, update: update, rules: {
|
|
39
|
+
required: "필수 입력 항목입니다.",
|
|
40
|
+
validate: () => validateDuplicatedSelection(fields, index),
|
|
41
|
+
}, onDelete: index !== 0
|
|
42
|
+
? () => {
|
|
43
|
+
remove(index);
|
|
44
|
+
}
|
|
45
|
+
: undefined }, field.uid)))] }), _jsx(Button, { color: "grey", size: "small", startIcon: _jsx(AddFillIcon, {}), label: "\uC120\uD0DD\uC9C0 \uCD94\uAC00", onClick: () => {
|
|
46
|
+
append({
|
|
47
|
+
isAnswer: false,
|
|
48
|
+
show: {
|
|
49
|
+
text: "",
|
|
50
|
+
},
|
|
51
|
+
value: (fields.length + 1).toString(),
|
|
52
|
+
});
|
|
53
|
+
} }), hasMultipleAnswers && (_jsxs(Alert, { children: [_jsx(AlarmWarningFillIcon, { css: css `
|
|
54
|
+
width: 14px;
|
|
55
|
+
height: 14px;
|
|
56
|
+
` }), "\uC815\uB2F5\uC774 \uC5EC\uB7EC \uAC1C\uC778 \uBB38\uC81C\uC5D0\uB294 \uC815\uB2F5\uC744 \uBAA8\uB450 \uC120\uD0DD\uD574\uC57C \uD55C\uB2E4\uB294 \uC548\uB0B4\uAC00 \uC81C\uACF5\uB429\uB2C8\uB2E4."] }))] }), _jsxs(Buttons, { children: [_jsx(Button, { color: "grey", size: "xsmall", label: "\uB2EB\uAE30", onClick: onClose }), _jsx(Button, { color: "primary", size: "xsmall", label: "\uC774\uB300\uB85C \uB123\uAE30", type: "submit" })] })] })));
|
|
57
|
+
}
|
|
58
|
+
const Form = styled.form(({ theme }) => css `
|
|
59
|
+
display: flex;
|
|
60
|
+
width: 620px;
|
|
61
|
+
flex-direction: column;
|
|
62
|
+
border-radius: 6px;
|
|
63
|
+
border: 1px solid ${theme.color.background.neutralAltActive};
|
|
64
|
+
background: ${theme.color.background.neutralBase};
|
|
65
|
+
box-shadow: ${shadows.shadow08};
|
|
66
|
+
`);
|
|
67
|
+
const Title = styled.div(({ theme }) => css `
|
|
68
|
+
display: flex;
|
|
69
|
+
padding: 8px 12px;
|
|
70
|
+
gap: 4px;
|
|
71
|
+
align-items: center;
|
|
72
|
+
color: ${theme.color.foreground.neutralBase};
|
|
73
|
+
/* Default/Label/12px-Md */
|
|
74
|
+
font-family: ${theme.fontFamily.ui};
|
|
75
|
+
font-size: 12px;
|
|
76
|
+
font-style: normal;
|
|
77
|
+
font-weight: 500;
|
|
78
|
+
line-height: 16px; /* 133.333% */
|
|
79
|
+
`);
|
|
80
|
+
const Content = styled.div(({ theme }) => css `
|
|
81
|
+
display: flex;
|
|
82
|
+
flex-direction: column;
|
|
83
|
+
gap: 12px;
|
|
84
|
+
padding: 12px;
|
|
85
|
+
border-top: 1px solid ${theme.color.background.neutralAltActive};
|
|
86
|
+
border-bottom: 1px solid ${theme.color.background.neutralAltActive};
|
|
87
|
+
`);
|
|
88
|
+
const FormArea = styled.div `
|
|
89
|
+
display: flex;
|
|
90
|
+
flex-direction: column;
|
|
91
|
+
gap: 8px;
|
|
92
|
+
`;
|
|
93
|
+
const Label = styled.div(({ theme }) => css `
|
|
94
|
+
color: ${theme.color.foreground.neutralBaseDisabled};
|
|
95
|
+
/* Default/Label/12px-Md */
|
|
96
|
+
font-family: ${theme.fontFamily.ui};
|
|
97
|
+
font-size: 12px;
|
|
98
|
+
font-style: normal;
|
|
99
|
+
font-weight: 500;
|
|
100
|
+
line-height: 16px; /* 133.333% */
|
|
101
|
+
`);
|
|
102
|
+
const Buttons = styled.div `
|
|
103
|
+
display: flex;
|
|
104
|
+
padding: 12px;
|
|
105
|
+
justify-content: flex-end;
|
|
106
|
+
align-items: center;
|
|
107
|
+
gap: 8px;
|
|
108
|
+
`;
|
|
109
|
+
const Alert = styled.div(({ theme }) => css `
|
|
110
|
+
display: flex;
|
|
111
|
+
gap: 4px;
|
|
112
|
+
color: ${theme.color.foreground.neutralBaseDisabled};
|
|
113
|
+
/* Default/Label/12px-Md */
|
|
114
|
+
font-family: ${theme.fontFamily.ui};
|
|
115
|
+
font-size: 12px;
|
|
116
|
+
font-style: normal;
|
|
117
|
+
font-weight: 500;
|
|
118
|
+
line-height: 16px; /* 133.333% */
|
|
119
|
+
`);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from "./SettingForm";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from "./SettingForm";
|
package/dist/patterns/LexicalEditor/plugins/ComponentPickerMenuPlugin/ComponentPickerMenuPlugin.js
CHANGED
|
@@ -25,6 +25,7 @@ import { TextIcon, H1Icon, H2Icon, H3Icon, ListUnorderedIcon, ListOrderedIcon, D
|
|
|
25
25
|
import { ZINDEX } from "../../../../utils/zIndex";
|
|
26
26
|
import { css, useTheme } from "@emotion/react";
|
|
27
27
|
import { INSERT_PROBLEM_INPUT_COMMAND } from "../ProblemInputPlugin";
|
|
28
|
+
import { INSERT_PROBLEM_SELECT_COMMAND } from "../ProblemSelectPlugin";
|
|
28
29
|
// import useModal from "../../hooks/useModal";
|
|
29
30
|
// import catTypingGif from "../../images/cat-typing.gif";
|
|
30
31
|
// import { INSERT_IMAGE_COMMAND, InsertImageDialog } from "../ImagesPlugin";
|
|
@@ -86,7 +87,18 @@ function getQuizContextOptions(editor, theme) {
|
|
|
86
87
|
icon: _jsx(ListRadioIcon, { color: theme.color.foreground.primary }),
|
|
87
88
|
keywords: ["problem select", "객관식 입력"],
|
|
88
89
|
onSelect: () => {
|
|
89
|
-
|
|
90
|
+
editor.dispatchCommand(INSERT_PROBLEM_SELECT_COMMAND, {
|
|
91
|
+
selections: [
|
|
92
|
+
{
|
|
93
|
+
isAnswer: false,
|
|
94
|
+
show: {
|
|
95
|
+
text: "",
|
|
96
|
+
},
|
|
97
|
+
value: "1",
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
selected: [],
|
|
101
|
+
});
|
|
90
102
|
},
|
|
91
103
|
}),
|
|
92
104
|
new ComponentDrawerOption("메뉴구분선", (_jsx("div", { css: css `
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/// <reference types="react" />
|
|
2
|
+
import { LexicalCommand } from "lexical";
|
|
3
|
+
import { ProblemSelectPayload } from "../../nodes/ProblemSelectNode";
|
|
4
|
+
export declare const INSERT_PROBLEM_SELECT_COMMAND: LexicalCommand<ProblemSelectPayload>;
|
|
5
|
+
export default function ProblemInputPlugin(): JSX.Element | null;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
|
|
2
|
+
import { $insertNodeToNearestRoot } from "@lexical/utils";
|
|
3
|
+
import { COMMAND_PRIORITY_EDITOR, createCommand, } from "lexical";
|
|
4
|
+
import { useEffect } from "react";
|
|
5
|
+
import { $createProblemSelectNode, ProblemSelectNode, } from "../../nodes/ProblemSelectNode";
|
|
6
|
+
export const INSERT_PROBLEM_SELECT_COMMAND = createCommand("INSERT_PROBLEM_SELECT_COMMAND");
|
|
7
|
+
export default function ProblemInputPlugin() {
|
|
8
|
+
const [editor] = useLexicalComposerContext();
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
if (!editor.hasNodes([ProblemSelectNode])) {
|
|
11
|
+
throw new Error("ProblemSelectNode: ProblemSelectNode not registered on editor");
|
|
12
|
+
}
|
|
13
|
+
editor.registerCommand(INSERT_PROBLEM_SELECT_COMMAND, (payload) => {
|
|
14
|
+
const problemInputNode = $createProblemSelectNode(payload);
|
|
15
|
+
$insertNodeToNearestRoot(problemInputNode);
|
|
16
|
+
return true;
|
|
17
|
+
}, COMMAND_PRIORITY_EDITOR);
|
|
18
|
+
}, [editor]);
|
|
19
|
+
return null;
|
|
20
|
+
}
|