Skip to content

Commit 668c989

Browse files
committed
[Feat]: #1755 add ability to copy the Hook components
1 parent baf0221 commit 668c989

2 files changed

Lines changed: 135 additions & 0 deletions

File tree

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import { HookComp } from "comps/hooks/hookComp";
2+
import { EditorState } from "comps/editorState";
3+
import { wrapActionExtraInfo, type Comp } from "lowcoder-core";
4+
import { messageInstance } from "lowcoder-design";
5+
import { trans } from "i18n";
6+
7+
type CopyableHookType = "modal" | "drawer";
8+
9+
type CopyableHookComp = HookComp & {
10+
children: HookComp["children"] & {
11+
compType: { getView: () => CopyableHookType };
12+
};
13+
};
14+
15+
const copyableHookTypes = new Set<CopyableHookType>(["modal", "drawer"]);
16+
17+
export class HookCompOperator {
18+
private static copyHooks: CopyableHookComp[] = [];
19+
20+
/**
21+
* Copy modals/drawers by name from selectedCompNames.
22+
*/
23+
static copyComp(editorState: EditorState, compRecords: Record<string, Comp>) {
24+
const selectedNames = Array.from(editorState.selectedCompNames);
25+
if (!selectedNames.length) {
26+
return false;
27+
}
28+
29+
const hookMap = editorState.getHooksComp().getAllCompItems();
30+
const selectedHookComps = Object.values(hookMap)
31+
.filter((comp: any) => {
32+
const name = comp.children.name.getView();
33+
const compType = comp.children.compType.getView();
34+
return selectedNames.includes(name) && copyableHookTypes.has(compType);
35+
}) as CopyableHookComp[];
36+
37+
if (!selectedHookComps.length) {
38+
return false;
39+
}
40+
41+
this.copyHooks = selectedHookComps;
42+
messageInstance.success(trans("notification.copySuccess"));
43+
return true;
44+
}
45+
46+
static clearCopy() {
47+
this.copyHooks = [];
48+
}
49+
50+
/**
51+
* Paste previously copied modals/drawers and re-generate nested component names.
52+
*/
53+
static pasteComp(editorState: EditorState) {
54+
if (!this.copyHooks.length) {
55+
messageInstance.info(trans("gridCompOperator.selectCompFirst"));
56+
return false;
57+
}
58+
59+
const hooksComp = editorState.getHooksComp();
60+
const nameGenerator = editorState.getNameGenerator();
61+
const newNames = new Set<string>();
62+
63+
this.copyHooks.forEach((hookComp) => {
64+
const compType = hookComp.children.compType.getView();
65+
const newName = nameGenerator.genItemName(compType);
66+
const childComp: any = hookComp.children.comp;
67+
const baseValue = childComp?.toJsonValue ? childComp.toJsonValue() : {};
68+
const pasteValue =
69+
childComp?.getPasteValue?.(nameGenerator) ?? {};
70+
71+
const payload = {
72+
...(hookComp.toJsonValue() as any),
73+
name: newName,
74+
comp: {
75+
...baseValue,
76+
...pasteValue,
77+
},
78+
};
79+
80+
hooksComp.dispatch(
81+
wrapActionExtraInfo(
82+
hooksComp.pushAction(payload),
83+
{
84+
compInfos: [
85+
{
86+
type: "add",
87+
compName: newName,
88+
compType,
89+
},
90+
],
91+
}
92+
)
93+
);
94+
newNames.add(newName);
95+
});
96+
97+
editorState.setSelectedCompNames(newNames, "leftPanel");
98+
messageInstance.success(trans("notification.copySuccess"));
99+
return true;
100+
}
101+
}
102+

client/packages/lowcoder/src/pages/editor/editorHotKeys.tsx

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React, { useCallback, useContext, useRef, useEffect } from "react";
22
import { EditorContext, EditorState } from "comps/editorState";
33
import { GridCompOperator } from "comps/utils/gridCompOperator";
4+
import { HookCompOperator } from "comps/utils/hookCompOperator";
45
import { ExternalEditorContext } from "util/context/ExternalEditorContext";
56
import { EditorHistory } from "util/editoryHistory";
67
import { executeQueryAction } from "lowcoder-core";
@@ -75,6 +76,31 @@ function handleGlobalKeyDown(
7576
break;
7677
}
7778
default:
79+
// Capture component copy/paste/cut/deselect globally
80+
if (modKeyPressed(e)) {
81+
const key = e.key?.toLowerCase();
82+
if (key === "c") {
83+
if (!HookCompOperator.copyComp(editorState, editorState.selectedComps())) {
84+
HookCompOperator.clearCopy();
85+
GridCompOperator.copyComp(editorState, editorState.selectedComps());
86+
}
87+
break;
88+
}
89+
if (key === "v") {
90+
if (!HookCompOperator.pasteComp(editorState)) {
91+
GridCompOperator.pasteComp(editorState);
92+
}
93+
break;
94+
}
95+
if (key === "x") {
96+
GridCompOperator.cutComp(editorState, editorState.selectedComps());
97+
break;
98+
}
99+
}
100+
if (e.key === "Escape") {
101+
editorState.setSelectedCompNames(new Set());
102+
break;
103+
}
78104
return;
79105
}
80106
// avoid conflicts with the browser
@@ -194,9 +220,16 @@ function handleEditorKeyDown(e: React.KeyboardEvent, editorState: EditorState) {
194220
e.stopPropagation();
195221
return;
196222
case "copyComps":
223+
if (HookCompOperator.copyComp(editorState, editorState.selectedComps())) {
224+
return;
225+
}
226+
HookCompOperator.clearCopy();
197227
GridCompOperator.copyComp(editorState, editorState.selectedComps());
198228
return;
199229
case "pasteComps":
230+
if (HookCompOperator.pasteComp(editorState)) {
231+
return;
232+
}
200233
GridCompOperator.pasteComp(editorState);
201234
return;
202235
case "cutComps":

0 commit comments

Comments
 (0)