Skip to content

Commit

Permalink
Merge pull request premieroctet#46 from premieroctet/feature/duplicate
Browse files Browse the repository at this point in the history
Add component duplication
  • Loading branch information
tlenclos authored Feb 12, 2020
2 parents c27340a + edaee35 commit 372fa46
Show file tree
Hide file tree
Showing 13 changed files with 323 additions and 78 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ By clicking on a component containing children, you will see a Children panel ap
| ---------------- | ------------------------- |
| `cmd+Z` `ctrl+Z` | Undo last action |
| `cmd+Y` `ctrl+y` | Redo action |
| `cmd+D` `ctrl+d` | Duplicate component |
| `del` | Delete selected component |
| `c` | Toggle Code panel |
| `b` | Toggle Builder mode |
Expand Down
1 change: 1 addition & 0 deletions src/components/editor/ComponentPreview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ const ComponentPreview: React.FC<{
case 'ListItem':
case 'NumberInput':
case 'BreadcrumbLink':
case 'Select':
return (
<PreviewContainer
component={component}
Expand Down
38 changes: 38 additions & 0 deletions src/components/inspector/ActionButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import React from 'react'
import {
TooltipProps,
IconButtonProps,
Tooltip,
IconButton,
} from '@chakra-ui/core'

interface Props
extends Omit<TooltipProps, 'label' | 'aria-label' | 'children'> {
icon: IconButtonProps['icon']
as?: IconButtonProps['as']
label: string
onClick?: () => void
}

const ActionButton: React.FC<Props> = ({
icon,
as,
label,
onClick,
...props
}) => {
return (
<Tooltip hasArrow aria-label={label} label={label} zIndex={11} {...props}>
<IconButton
size="xs"
variant="ghost"
as={as}
onClick={onClick}
icon={icon}
aria-label={label}
/>
</Tooltip>
)
}

export default ActionButton
94 changes: 43 additions & 51 deletions src/components/inspector/Inspector.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react'
import { Link, Box, IconButton } from '@chakra-ui/core'
import { Link, Box, Stack } from '@chakra-ui/core'

import Panels from './panels/Panels'
import { GoRepo } from 'react-icons/go'
Expand All @@ -9,8 +9,8 @@ import { useSelector } from 'react-redux'
import useDispatch from '../../hooks/useDispatch'
import QuickPropsPanel from './QuickPropsPanel'
import StylesPanel from './panels/StylesPanel'
import { Tooltip } from '@chakra-ui/core'
import { getSelectedComponent } from '../../core/selectors/components'
import ActionButton from './ActionButton'

const Inspector = () => {
const dispatch = useDispatch()
Expand All @@ -35,65 +35,57 @@ const Inspector = () => {
px={2}
shadow="sm"
bg="yellow.100"
mb={3}
display="flex"
alignItems="center"
justifyContent="space-between"
>
{isRoot ? 'Document' : type}
<Box>
{!isRoot && (
<>
<Tooltip hasArrow aria-label="Reset" label="Reset">
<IconButton
size="xs"
variant="ghost"
aria-label="Reset"
onClick={() => dispatch.components.resetProps(component.id)}
icon={IoMdRefresh}
/>
</Tooltip>
<Tooltip hasArrow aria-label="Doc" label="Doc">
<IconButton
size="xs"
variant="ghost"
as={Link}
onClick={() => {
window.open(
`https://chakra-ui.com/${docType.toLowerCase()}`,
'_blank',
)
}}
aria-label="Doc"
icon={GoRepo}
/>
</Tooltip>

<Tooltip
bg="red.500"
hasArrow
aria-label="Remove"
label="Remove"
>
<IconButton
size="xs"
variant="ghost"
onClick={() =>
dispatch.components.deleteComponent(component.id)
}
aria-label="Remove"
icon={FiTrash2}
/>
</Tooltip>
</>
)}
</Box>
</Box>
{!isRoot && (
<Stack
isInline
py={2}
spacing={4}
align="center"
zIndex={99}
px={2}
flexWrap="wrap"
justify="flex-end"
>
<ActionButton
label="Duplicate"
onClick={() => dispatch.components.duplicate()}
icon="copy"
/>
<ActionButton
label="Reset"
icon={IoMdRefresh}
onClick={() => dispatch.components.resetProps(component.id)}
/>
<ActionButton
label="Doc"
as={Link}
onClick={() => {
window.open(
`https://chakra-ui.com/${docType.toLowerCase()}`,
'_blank',
)
}}
icon={GoRepo}
/>
<ActionButton
bg="red.500"
label="Remove"
onClick={() => dispatch.components.deleteComponent(component.id)}
icon={FiTrash2}
/>
</Stack>
)}
</Box>

<Box bg="white" px={3}>
{!isRoot && <QuickPropsPanel />}
<Panels component={component} />
<Panels component={component} isRoot={isRoot} />
</Box>

<StylesPanel isRoot={isRoot} showChildren={componentHasChildren} />
Expand Down
9 changes: 8 additions & 1 deletion src/components/inspector/panels/Panels.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,16 @@ import AspectRatioPanel from './components/AspectRatioPanel'
import BreadcrumbPanel from './components/BreadcrumbPanel'
import BreadcrumbItemPanel from './components/BreadcrumbItemPanel'

const Panels: React.FC<{ component: IComponent }> = ({ component }) => {
const Panels: React.FC<{ component: IComponent; isRoot: boolean }> = ({
component,
isRoot,
}) => {
const { type } = component

if (isRoot) {
return null
}

return (
<>
{type === 'Button' && <ButtonPanel />}
Expand Down
9 changes: 0 additions & 9 deletions src/core/models/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,6 @@ export type AppState = {
overlay: undefined | Overlay
}

export const generateId = () => {
return `comp-${(
Date.now().toString(36) +
Math.random()
.toString(36)
.substr(2, 5)
).toUpperCase()}`
}

const app = createModel({
state: {
showLayout: true,
Expand Down
43 changes: 27 additions & 16 deletions src/core/models/components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import { createModel } from '@rematch/core'
import { DEFAULT_PROPS } from '../../utils/defaultProps'
import omit from 'lodash/omit'
import templates, { TemplateType } from '../../templates'
import { generateId } from './app'
import { generateId } from '../../utils/generateId'
import { duplicateComponent, deleteComponent } from '../../utils/recursive'

export type ComponentsState = {
components: IComponents
Expand Down Expand Up @@ -98,21 +99,7 @@ const components = createModel({
}
}

const deleteRecursive = (
children: IComponent['children'],
id: IComponent['id'],
) => {
children.forEach(child => {
updatedComponents[child] &&
deleteRecursive(updatedComponents[child].children, componentId)
})

updatedComponents = omit(updatedComponents, id)
}

deleteRecursive(component.children, componentId)

updatedComponents = omit(updatedComponents, componentId)
updatedComponents = deleteComponent(component, updatedComponents)

return {
...state,
Expand Down Expand Up @@ -259,6 +246,30 @@ const components = createModel({
selectedId: state.components[selectedComponent.parent].id,
}
},
duplicate(state: ComponentsState): ComponentsState {
const selectedComponent = state.components[state.selectedId]
if (selectedComponent.id !== DEFAULT_ID) {
const parentElement = state.components[selectedComponent.parent]

const { newId, clonedComponents } = duplicateComponent(
selectedComponent,
state.components,
)

return {
...state,
components: {
...state.components,
...clonedComponents,
[parentElement.id]: {
...parentElement,
children: [...parentElement.children, newId],
},
},
}
}
return state
},
},
})

Expand Down
2 changes: 1 addition & 1 deletion src/core/models/composer/composer.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { DEFAULT_PROPS } from '../../../utils/defaultProps'
import { generateId } from '../app'
import { generateId } from '../../../utils/generateId'

type AddNode = {
type: ComponentType
Expand Down
10 changes: 10 additions & 0 deletions src/hooks/useShortcuts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export const keyMap = {
REDO: ['ctrl+y', 'cmd+y'],
UNSELECT: ['Escape'],
PARENT: 'p',
DUPLICATE: ['ctrl+d', 'cmd+d'],
}

const hasNoSpecialKeyPressed = (event: KeyboardEvent | undefined) =>
Expand Down Expand Up @@ -75,6 +76,14 @@ const useShortcuts = () => {
}
}

const onDuplicate = (event: KeyboardEvent | undefined) => {
if (event) {
event.preventDefault()
}

dispatch.components.duplicate()
}

const handlers = {
DELETE_NODE: deleteNode,
TOGGLE_BUILDER_MODE: toggleBuilderMode,
Expand All @@ -83,6 +92,7 @@ const useShortcuts = () => {
REDO: redo,
UNSELECT: onUnselect,
PARENT: onSelectParent,
DUPLICATE: onDuplicate,
}

return { handlers }
Expand Down
8 changes: 8 additions & 0 deletions src/utils/generateId.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export const generateId = () => {
return `comp-${(
Date.now().toString(36) +
Math.random()
.toString(36)
.substr(2, 5)
).toUpperCase()}`
}
Loading

0 comments on commit 372fa46

Please sign in to comment.