forked from tsai/budibase
committed by
GitHub
14 changed files with 388 additions and 304 deletions
@ -0,0 +1,59 @@ |
|||||
|
import { makePropsSafe } from "components/userInterface/pagesParsing/createProps" |
||||
|
import api from "./api" |
||||
|
|
||||
|
export const selectComponent = (state, component) => { |
||||
|
const componentDef = component._component.startsWith("##") |
||||
|
? component |
||||
|
: state.components[component._component] |
||||
|
state.currentComponentInfo = makePropsSafe(componentDef, component) |
||||
|
state.currentView = "component" |
||||
|
return state |
||||
|
} |
||||
|
|
||||
|
export const getParent = (rootProps, child) => { |
||||
|
let parent |
||||
|
walkProps(rootProps, (p, breakWalk) => { |
||||
|
if ( |
||||
|
p._children && |
||||
|
(p._children.includes(child) || p._children.some(c => c._id === child)) |
||||
|
) { |
||||
|
parent = p |
||||
|
breakWalk() |
||||
|
} |
||||
|
}) |
||||
|
return parent |
||||
|
} |
||||
|
|
||||
|
export const saveCurrentPreviewItem = s => |
||||
|
s.currentFrontEndType === "page" |
||||
|
? savePage(s) |
||||
|
: saveScreenApi(s.currentPreviewItem, s) |
||||
|
|
||||
|
export const savePage = async s => { |
||||
|
const page = s.pages[s.currentPageName] |
||||
|
await api.post(`/_builder/api/${s.appId}/pages/${s.currentPageName}`, { |
||||
|
page: { componentLibraries: s.pages.componentLibraries, ...page }, |
||||
|
uiFunctions: s.currentPageFunctions, |
||||
|
screens: page._screens, |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
export const saveScreenApi = (screen, s) => { |
||||
|
api |
||||
|
.post(`/_builder/api/${s.appId}/pages/${s.currentPageName}/screen`, screen) |
||||
|
.then(() => savePage(s)) |
||||
|
} |
||||
|
|
||||
|
export const walkProps = (props, action, cancelToken = null) => { |
||||
|
cancelToken = cancelToken || { cancelled: false } |
||||
|
action(props, () => { |
||||
|
cancelToken.cancelled = true |
||||
|
}) |
||||
|
|
||||
|
if (props._children) { |
||||
|
for (let child of props._children) { |
||||
|
if (cancelToken.cancelled) return |
||||
|
walkProps(child, action, cancelToken) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
After Width: | Height: | Size: 419 B |
@ -0,0 +1,232 @@ |
|||||
|
<script> |
||||
|
import { MoreIcon } from "components/common/Icons" |
||||
|
import { store } from "builderStore" |
||||
|
import { getComponentDefinition } from "builderStore/store" |
||||
|
import ConfirmDialog from "components/common/ConfirmDialog.svelte" |
||||
|
import { last, cloneDeep } from "lodash/fp" |
||||
|
import UIkit from "uikit" |
||||
|
import { |
||||
|
selectComponent, |
||||
|
getParent, |
||||
|
walkProps, |
||||
|
saveCurrentPreviewItem, |
||||
|
} from "builderStore/storeUtils" |
||||
|
import { uuid } from "builderStore/uuid" |
||||
|
|
||||
|
export let component |
||||
|
|
||||
|
let confirmDeleteDialog |
||||
|
let dropdownEl |
||||
|
|
||||
|
$: dropdown = UIkit.dropdown(dropdownEl, { |
||||
|
mode: "click", |
||||
|
offset: 0, |
||||
|
pos: "bottom-right", |
||||
|
"delay-hide": 0, |
||||
|
animation: false, |
||||
|
}) |
||||
|
$: dropdown && UIkit.util.on(dropdown, "shown", () => (hidden = false)) |
||||
|
$: noChildrenAllowed = |
||||
|
!component || |
||||
|
getComponentDefinition($store, component._component).children === false |
||||
|
$: noPaste = |
||||
|
!$store.componentToPaste || $store.componentToPaste._id === component._id |
||||
|
|
||||
|
const lastPartOfName = c => (c ? last(c._component.split("/")) : "") |
||||
|
|
||||
|
const hideDropdown = () => { |
||||
|
dropdown.hide() |
||||
|
} |
||||
|
|
||||
|
const moveUpComponent = () => { |
||||
|
store.update(s => { |
||||
|
const parent = getParent(s.currentPreviewItem.props, component) |
||||
|
|
||||
|
if (parent) { |
||||
|
const currentIndex = parent._children.indexOf(component) |
||||
|
if (currentIndex === 0) return s |
||||
|
|
||||
|
const newChildren = parent._children.filter(c => c !== component) |
||||
|
newChildren.splice(currentIndex - 1, 0, component) |
||||
|
parent._children = newChildren |
||||
|
} |
||||
|
s.currentComponentInfo = component |
||||
|
saveCurrentPreviewItem(s) |
||||
|
|
||||
|
return s |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
const moveDownComponent = () => { |
||||
|
store.update(s => { |
||||
|
const parent = getParent(s.currentPreviewItem.props, component) |
||||
|
|
||||
|
if (parent) { |
||||
|
const currentIndex = parent._children.indexOf(component) |
||||
|
if (currentIndex === parent._children.length - 1) return s |
||||
|
|
||||
|
const newChildren = parent._children.filter(c => c !== component) |
||||
|
newChildren.splice(currentIndex + 1, 0, component) |
||||
|
parent._children = newChildren |
||||
|
} |
||||
|
s.currentComponentInfo = component |
||||
|
saveCurrentPreviewItem(s) |
||||
|
|
||||
|
return s |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
const copyComponent = () => { |
||||
|
store.update(s => { |
||||
|
const parent = getParent(s.currentPreviewItem.props, component) |
||||
|
const copiedComponent = cloneDeep(component) |
||||
|
walkProps(copiedComponent, p => { |
||||
|
p._id = uuid() |
||||
|
}) |
||||
|
parent._children = [...parent._children, copiedComponent] |
||||
|
saveCurrentPreviewItem(s) |
||||
|
s.currentComponentInfo = copiedComponent |
||||
|
return s |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
const deleteComponent = () => { |
||||
|
store.update(state => { |
||||
|
const parent = getParent(state.currentPreviewItem.props, component) |
||||
|
|
||||
|
if (parent) { |
||||
|
parent._children = parent._children.filter(c => c !== component) |
||||
|
} |
||||
|
|
||||
|
saveCurrentPreviewItem(state) |
||||
|
|
||||
|
return state |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
const generateNewIdsForComponent = c => |
||||
|
walkProps(c, p => { |
||||
|
p._id = uuid() |
||||
|
}) |
||||
|
|
||||
|
const storeComponentForCopy = (cut = false) => { |
||||
|
store.update(s => { |
||||
|
const copiedComponent = cloneDeep(component) |
||||
|
s.componentToPaste = copiedComponent |
||||
|
if (cut) { |
||||
|
const parent = getParent(s.currentPreviewItem.props, component._id) |
||||
|
parent._children = parent._children.filter(c => c._id !== component._id) |
||||
|
selectComponent(s, parent) |
||||
|
} |
||||
|
|
||||
|
return s |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
const pasteComponent = mode => { |
||||
|
store.update(s => { |
||||
|
if (!s.componentToPaste) return s |
||||
|
|
||||
|
const componentToPaste = cloneDeep(s.componentToPaste) |
||||
|
generateNewIdsForComponent(componentToPaste) |
||||
|
delete componentToPaste._cutId |
||||
|
|
||||
|
if (mode === "inside") { |
||||
|
component._children.push(componentToPaste) |
||||
|
return s |
||||
|
} |
||||
|
|
||||
|
const parent = getParent(s.currentPreviewItem.props, component) |
||||
|
|
||||
|
const targetIndex = parent._children.indexOf(component) |
||||
|
const index = mode === "above" ? targetIndex : targetIndex + 1 |
||||
|
parent._children.splice(index, 0, cloneDeep(componentToPaste)) |
||||
|
|
||||
|
saveCurrentPreviewItem(s) |
||||
|
selectComponent(s, componentToPaste) |
||||
|
|
||||
|
return s |
||||
|
}) |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<div class="root" on:click|stopPropagation={() => {}}> |
||||
|
<button> |
||||
|
<MoreIcon /> |
||||
|
</button> |
||||
|
<ul class="menu" bind:this={dropdownEl} on:click={hideDropdown}> |
||||
|
<li on:click={() => confirmDeleteDialog.show()}>Delete</li> |
||||
|
<li on:click={moveUpComponent}>Move up</li> |
||||
|
<li on:click={moveDownComponent}>Move down</li> |
||||
|
<li on:click={copyComponent}>Duplicate</li> |
||||
|
<li on:click={() => storeComponentForCopy(true)}>Cut</li> |
||||
|
<li on:click={() => storeComponentForCopy(false)}>Copy</li> |
||||
|
<hr /> |
||||
|
<li class:disabled={noPaste} on:click={() => pasteComponent('above')}> |
||||
|
Paste above |
||||
|
</li> |
||||
|
<li class:disabled={noPaste} on:click={() => pasteComponent('below')}> |
||||
|
Paste below |
||||
|
</li> |
||||
|
<li |
||||
|
class:disabled={noPaste || noChildrenAllowed} |
||||
|
on:click={() => pasteComponent('inside')}> |
||||
|
Paste inside |
||||
|
</li> |
||||
|
</ul> |
||||
|
</div> |
||||
|
|
||||
|
<ConfirmDialog |
||||
|
bind:this={confirmDeleteDialog} |
||||
|
title="Confirm Delete" |
||||
|
body={`Are you sure you wish to delete this '${lastPartOfName(component)}' component?`} |
||||
|
okText="Delete Component" |
||||
|
onOk={deleteComponent} /> |
||||
|
|
||||
|
<style> |
||||
|
.root { |
||||
|
overflow: hidden; |
||||
|
z-index: 9; |
||||
|
} |
||||
|
|
||||
|
.root button { |
||||
|
border-style: none; |
||||
|
border-radius: 2px; |
||||
|
padding: 5px; |
||||
|
background: transparent; |
||||
|
cursor: pointer; |
||||
|
color: var(--button-text); |
||||
|
outline: none; |
||||
|
} |
||||
|
|
||||
|
.menu { |
||||
|
z-index: 100000; |
||||
|
overflow: visible; |
||||
|
padding: 10px 0; |
||||
|
} |
||||
|
|
||||
|
.menu li { |
||||
|
border-style: none; |
||||
|
background-color: transparent; |
||||
|
list-style-type: none; |
||||
|
padding: 4px 5px 4px 15px; |
||||
|
margin: 0; |
||||
|
width: 100%; |
||||
|
box-sizing: border-box; |
||||
|
} |
||||
|
|
||||
|
.menu li:not(.disabled) { |
||||
|
cursor: pointer; |
||||
|
color: var(--ink); |
||||
|
} |
||||
|
|
||||
|
.menu li:not(.disabled):hover { |
||||
|
color: var(--button-text); |
||||
|
background-color: var(--grey-light); |
||||
|
} |
||||
|
|
||||
|
.disabled { |
||||
|
color: var(--grey-dark); |
||||
|
cursor: default; |
||||
|
} |
||||
|
</style> |
||||
Loading…
Reference in new issue