diff --git a/apps/wavemail/src/app/mail-builder/mail-builder/components/hotkeys.tsx b/apps/wavemail/src/app/mail-builder/mail-builder/components/hotkeys.tsx index 58c5952..a84a23b 100644 --- a/apps/wavemail/src/app/mail-builder/mail-builder/components/hotkeys.tsx +++ b/apps/wavemail/src/app/mail-builder/mail-builder/components/hotkeys.tsx @@ -2,6 +2,7 @@ import { filter, fromEvent, merge, map } from 'rxjs'; import { useSubscription } from '@waveditors/rxjs-react'; import { CanvasKeyDownEvent, + duplicateComponent, removeSelectedElement, useAction, useBuilderContext, @@ -13,6 +14,7 @@ const HotkeyActions = { redo: 'KeyY', save: 'KeyS', load: 'KeyL', + duplicate: 'KeyD', remove: 'Backspace', }; @@ -23,6 +25,7 @@ export const Hotkeys = () => { editor: { events }, } = useBuilderContext(); const removeSelected = useAction(removeSelectedElement); + const duplicate = useAction(duplicateComponent); useSubscription(() => { const keyboardEvents = fromEvent(document, 'keydown'); @@ -54,6 +57,8 @@ export const Hotkeys = () => { } case HotkeyActions.remove: return removeSelected(); + case HotkeyActions.duplicate: + return duplicate(); } }); }, [undoRedo, events]); diff --git a/libs/editor-model/src/builder/actions/duplicate-component.ts b/libs/editor-model/src/builder/actions/duplicate-component.ts new file mode 100644 index 0000000..193f8cf --- /dev/null +++ b/libs/editor-model/src/builder/actions/duplicate-component.ts @@ -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; +}; diff --git a/libs/editor-model/src/builder/actions/index.ts b/libs/editor-model/src/builder/actions/index.ts index 27e6483..b47fbe5 100644 --- a/libs/editor-model/src/builder/actions/index.ts +++ b/libs/editor-model/src/builder/actions/index.ts @@ -1 +1,2 @@ export { mergeComponent } from './merge-component'; +export { duplicateComponent } from './duplicate-component'; diff --git a/libs/editor-model/src/builder/actions/merge-component.ts b/libs/editor-model/src/builder/actions/merge-component.ts index 1f3fe35..e884e9b 100644 --- a/libs/editor-model/src/builder/actions/merge-component.ts +++ b/libs/editor-model/src/builder/actions/merge-component.ts @@ -2,7 +2,6 @@ import { ConfigFont } from '../../config'; import { applyFontsIdTableToRelations, applyVariablesTableToElements, - cloneComponent, } from '../../component'; import { Position, getLayoutElement } from '../../elements'; import { BuilderContext, EditorSnapshot } from '../../types'; @@ -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(), diff --git a/libs/editor-model/src/component/actions/index.ts b/libs/editor-model/src/component/actions/index.ts index 4936ce6..82fddc0 100644 --- a/libs/editor-model/src/component/actions/index.ts +++ b/libs/editor-model/src/component/actions/index.ts @@ -1,2 +1 @@ export * from './extract-component'; -export * from './clone-component'; diff --git a/libs/editor-model/src/component/index.ts b/libs/editor-model/src/component/index.ts index 485f1b1..a9ddeca 100644 --- a/libs/editor-model/src/component/index.ts +++ b/libs/editor-model/src/component/index.ts @@ -1 +1,2 @@ export * from './actions'; +export * from './services'; diff --git a/libs/editor-model/src/component/actions/__snapshots__/clone-component.spec.ts.snap b/libs/editor-model/src/component/services/__snapshots__/clone-component.spec.ts.snap similarity index 100% rename from libs/editor-model/src/component/actions/__snapshots__/clone-component.spec.ts.snap rename to libs/editor-model/src/component/services/__snapshots__/clone-component.spec.ts.snap diff --git a/libs/editor-model/src/component/actions/clone-component.spec.ts b/libs/editor-model/src/component/services/clone-component.spec.ts similarity index 100% rename from libs/editor-model/src/component/actions/clone-component.spec.ts rename to libs/editor-model/src/component/services/clone-component.spec.ts diff --git a/libs/editor-model/src/component/actions/clone-component.ts b/libs/editor-model/src/component/services/clone-component.ts similarity index 85% rename from libs/editor-model/src/component/actions/clone-component.ts rename to libs/editor-model/src/component/services/clone-component.ts index 25760e9..0a66251 100644 --- a/libs/editor-model/src/component/actions/clone-component.ts +++ b/libs/editor-model/src/component/services/clone-component.ts @@ -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], }); @@ -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), + }; +}; diff --git a/libs/editor-model/src/component/services/index.ts b/libs/editor-model/src/component/services/index.ts new file mode 100644 index 0000000..4775b85 --- /dev/null +++ b/libs/editor-model/src/component/services/index.ts @@ -0,0 +1 @@ +export * from './clone-component'; diff --git a/libs/editor-model/src/services/effects.ts b/libs/editor-model/src/services/effects.ts index b429bd6..ce64e2a 100644 --- a/libs/editor-model/src/services/effects.ts +++ b/libs/editor-model/src/services/effects.ts @@ -6,5 +6,6 @@ export const commonUndoRedoEffect = ({ createUndoRedoEffect, }: UndoRedoModule) => createUndoRedoEffect('element', { + filterActions: ['setMeta'] as Array, filter: ({ payload }, value) => payload.next.id === value.id, }); diff --git a/libs/layout-editor/src/components/index.tsx b/libs/layout-editor/src/components/index.tsx index 4cb250b..0336409 100644 --- a/libs/layout-editor/src/components/index.tsx +++ b/libs/layout-editor/src/components/index.tsx @@ -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; } diff --git a/libs/layout-editor/src/hooks/use-dnd.ts b/libs/layout-editor/src/hooks/use-dnd.ts index c40db7e..bcbbefc 100644 --- a/libs/layout-editor/src/hooks/use-dnd.ts +++ b/libs/layout-editor/src/hooks/use-dnd.ts @@ -17,6 +17,7 @@ import { selectByType, } from '@waveditors/utils'; import { + cloneComponent, ElementsStore, getElementPosition, getParentElement, @@ -185,7 +186,10 @@ export const useDnd = ({ } : { type: 'AddComponent', - payload: { element: payload.element, position }, + payload: { + element: cloneComponent(payload.element), + position, + }, } ) ); diff --git a/libs/rxjs-react/src/modules/undo-redo.ts b/libs/rxjs-react/src/modules/undo-redo.ts index af3db94..b3d916d 100644 --- a/libs/rxjs-react/src/modules/undo-redo.ts +++ b/libs/rxjs-react/src/modules/undo-redo.ts @@ -41,6 +41,7 @@ type UndoRedoEvents = UndoRedoEvent[]; 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 = >( size = 10 ): UndoRedoModule => { diff --git a/libs/text-editor/src/components/bubble-menu/link.tsx b/libs/text-editor/src/components/bubble-menu/link.tsx index 1ee2ee8..14bd957 100644 --- a/libs/text-editor/src/components/bubble-menu/link.tsx +++ b/libs/text-editor/src/components/bubble-menu/link.tsx @@ -69,7 +69,12 @@ export const Link = () => { }, [events]); return ( - e.stopPropagation()}> + { + e.stopPropagation(); + e.preventDefault(); + }} + > {state.open && (