Skip to content

Commit

Permalink
Merge pull request #32 from qvantor/feature/duplicate
Browse files Browse the repository at this point in the history
Feature/duplicate
  • Loading branch information
qvantor authored Nov 3, 2023
2 parents 37a6f80 + d1aeaf3 commit 0e4d608
Show file tree
Hide file tree
Showing 15 changed files with 82 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { filter, fromEvent, merge, map } from 'rxjs';
import { useSubscription } from '@waveditors/rxjs-react';
import {
CanvasKeyDownEvent,
duplicateComponent,
removeSelectedElement,
useAction,
useBuilderContext,
Expand All @@ -13,6 +14,7 @@ const HotkeyActions = {
redo: 'KeyY',
save: 'KeyS',
load: 'KeyL',
duplicate: 'KeyD',
remove: 'Backspace',
};

Expand All @@ -23,6 +25,7 @@ export const Hotkeys = () => {
editor: { events },
} = useBuilderContext();
const removeSelected = useAction(removeSelectedElement);
const duplicate = useAction(duplicateComponent);

useSubscription(() => {
const keyboardEvents = fromEvent<KeyboardEvent>(document, 'keydown');
Expand Down Expand Up @@ -54,6 +57,8 @@ export const Hotkeys = () => {
}
case HotkeyActions.remove:
return removeSelected();
case HotkeyActions.duplicate:
return duplicate();
}
});
}, [undoRedo, events]);
Expand Down
21 changes: 21 additions & 0 deletions libs/editor-model/src/builder/actions/duplicate-component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { BuilderContext } from '../../types';
import { getElementPosition, getParentElement } from '../../elements';
import { duplicateCloneComponent, extractComponent } from '../../component';
import { mergeComponent } from './merge-component';

export const duplicateComponent = (context: BuilderContext) => () => {
const {
model: { elements },
interaction: { selected },
} = context;
const elementId = selected.getValue();
if (!elementId) return false;
const parent = getParentElement(elements.getValue(), elementId);
if (!parent) return false;
const mergeComp = mergeComponent(context);
const extractComp = extractComponent(context);
const element = duplicateCloneComponent(extractComp(elementId));
const position = getElementPosition(parent.getValue(), elementId);
mergeComp({ position, element });
return true;
};
1 change: 1 addition & 0 deletions libs/editor-model/src/builder/actions/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { mergeComponent } from './merge-component';
export { duplicateComponent } from './duplicate-component';
28 changes: 10 additions & 18 deletions libs/editor-model/src/builder/actions/merge-component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { ConfigFont } from '../../config';
import {
applyFontsIdTableToRelations,
applyVariablesTableToElements,
cloneComponent,
} from '../../component';
import { Position, getLayoutElement } from '../../elements';
import { BuilderContext, EditorSnapshot } from '../../types';
Expand Down Expand Up @@ -53,46 +52,39 @@ const detectVariablesToAdd = (
},
[[], {}]
);

type Params = {
position: Position | null;
element: EditorSnapshot;
};
export const mergeComponent =
({
model: { elements, config, variables, relations },
module: { undoRedo },
}: BuilderContext) =>
({
position,
element,
}: {
position: Position | null;
element: EditorSnapshot;
}) => {
({ position, element }: Params) => {
const internalPosition = position ?? {
layout: config.getValue().rootElementId,
column: 0,
index: 0,
};

const componentClone = cloneComponent(element);
// detecting intersections between variables in template and component
const [varsToAdd, variablesIdTable] = detectVariablesToAdd(
componentClone.variables,
element.variables,
variables.getValue()
);
// detecting intersections between fonts from component and current template
const [fontsToAdd, fontsIdTable] = detectFontToAdd(
componentClone.config.fonts,
element.config.fonts,
config.getValue().fonts
);
const component = {
...componentClone,
...element,
elements: applyVariablesTableToElements(
componentClone.elements,
element.elements,
variablesIdTable
),
relations: applyFontsIdTableToRelations(
componentClone.relations,
fontsIdTable
),
relations: applyFontsIdTableToRelations(element.relations, fontsIdTable),
};
const parent = getLayoutElement(
elements.getValue(),
Expand Down
1 change: 0 additions & 1 deletion libs/editor-model/src/component/actions/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
export * from './extract-component';
export * from './clone-component';
1 change: 1 addition & 0 deletions libs/editor-model/src/component/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './actions';
export * from './services';
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,10 @@ const applyIdTableToConfig = (
fontsIdTable: IdRemapTable
) => ({
...config,
fonts: config.fonts.map((font) => ({ ...font, id: fontsIdTable[font.id] })),
fonts: config.fonts.map((font) => ({
...font,
id: fontsIdTable[font.id],
})),
rootElementId: idTable[config.rootElementId],
});

Expand Down Expand Up @@ -127,3 +130,24 @@ export const cloneComponent = ({
),
};
};

// duplicate updates elements id's, but keep font and variables id's the same
export const duplicateCloneComponent = ({
elements,
config,
relations,
}: EditorSnapshot): EditorSnapshot => {
const idTable = genArrayWithIdTable(Object.values(elements));

return {
elements: applyIdTableToElements(elements, idTable),
config: {
rootElementId: idTable[config.rootElementId],
style: {},
fonts: [],
viewportWidth: 0,
},
variables: [],
relations: applyIdTableToRelations(relations, idTable),
};
};
1 change: 1 addition & 0 deletions libs/editor-model/src/component/services/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './clone-component';
1 change: 1 addition & 0 deletions libs/editor-model/src/services/effects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ export const commonUndoRedoEffect = <V extends Element, A>({
createUndoRedoEffect,
}: UndoRedoModule<UndoRedoEvents>) =>
createUndoRedoEffect<V, A, 'element'>('element', {
filterActions: ['setMeta'] as Array<keyof A>,
filter: ({ payload }, value) => payload.next.id === value.id,
});
5 changes: 5 additions & 0 deletions libs/layout-editor/src/components/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,14 @@ import { LayoutEditor as LayoutEditorInternal } from './layout-editor';
const GlobalStyle = createGlobalStyle`
* {
user-select: none;
-webkit-user-drag: none;
outline: none;
}
a {
cursor: default;
}
body {
margin: 0;
}
Expand Down
6 changes: 5 additions & 1 deletion libs/layout-editor/src/hooks/use-dnd.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
selectByType,
} from '@waveditors/utils';
import {
cloneComponent,
ElementsStore,
getElementPosition,
getParentElement,
Expand Down Expand Up @@ -185,7 +186,10 @@ export const useDnd = ({
}
: {
type: 'AddComponent',
payload: { element: payload.element, position },
payload: {
element: cloneComponent(payload.element),
position,
},
}
)
);
Expand Down
1 change: 1 addition & 0 deletions libs/rxjs-react/src/modules/undo-redo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ type UndoRedoEvents<E> = UndoRedoEvent<E>[];

export const generateId = () => Math.random().toString();

// double undo added when duplicate text with variable (because text's not equal - key ordering changed)
export const undoRedoModule = <E extends CommonUndoEvent<string, unknown>>(
size = 10
): UndoRedoModule<E> => {
Expand Down
7 changes: 6 additions & 1 deletion libs/text-editor/src/components/bubble-menu/link.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,12 @@ export const Link = () => {
}, [events]);

return (
<Group onClick={(e) => e.stopPropagation()}>
<Group
onClick={(e) => {
e.stopPropagation();
e.preventDefault();
}}
>
{state.open && (
<PopupRoot>
<Input
Expand Down

0 comments on commit 0e4d608

Please sign in to comment.