Add initial work on grid layout
This commit is contained in:
parent
3f1e782b05
commit
b839325a86
|
@ -439,7 +439,16 @@ export const getFrontendStore = () => {
|
|||
return {
|
||||
_id: Helpers.uuid(),
|
||||
_component: definition.component,
|
||||
_styles: { normal: {}, hover: {}, active: {} },
|
||||
_styles: {
|
||||
normal: {
|
||||
"grid-column-start": 1,
|
||||
"grid-column-end": 2,
|
||||
"grid-row-start": 1,
|
||||
"grid-row-end": 2,
|
||||
},
|
||||
hover: {},
|
||||
active: {},
|
||||
},
|
||||
_instanceName: `New ${definition.name}`,
|
||||
...cloneDeep(props),
|
||||
...extras,
|
||||
|
@ -873,6 +882,14 @@ export const getFrontendStore = () => {
|
|||
}
|
||||
})
|
||||
},
|
||||
updateStyles: async styles => {
|
||||
await store.actions.components.patch(component => {
|
||||
component._styles.normal = {
|
||||
...component._styles.normal,
|
||||
...styles,
|
||||
}
|
||||
})
|
||||
},
|
||||
updateCustomStyle: async style => {
|
||||
await store.actions.components.patch(component => {
|
||||
component._styles.custom = style
|
||||
|
|
|
@ -143,6 +143,8 @@
|
|||
}
|
||||
} else if (type === "update-prop") {
|
||||
await store.actions.components.updateSetting(data.prop, data.value)
|
||||
} else if (type === "update-styles") {
|
||||
await store.actions.components.updateStyles(data.styles)
|
||||
} else if (type === "delete-component" && data.id) {
|
||||
// Legacy type, can be deleted in future
|
||||
confirmDeleteComponent(data.id)
|
||||
|
|
|
@ -1,6 +1,67 @@
|
|||
import { Input, Select } from "@budibase/bbui"
|
||||
import ColorPicker from "components/design/settings/controls/ColorPicker.svelte"
|
||||
|
||||
export const grid = {
|
||||
label: "Grid",
|
||||
columns: "1fr 1fr",
|
||||
settings: [
|
||||
{
|
||||
label: "Col. start",
|
||||
key: "grid-column-start",
|
||||
control: Select,
|
||||
placeholder: "Auto",
|
||||
options: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12],
|
||||
},
|
||||
{
|
||||
label: "Col. end",
|
||||
key: "grid-column-end",
|
||||
control: Select,
|
||||
placeholder: "Auto",
|
||||
options: [
|
||||
{ label: "1", value: 2 },
|
||||
{ label: "2", value: 3 },
|
||||
{ label: "3", value: 4 },
|
||||
{ label: "4", value: 5 },
|
||||
{ label: "5", value: 6 },
|
||||
{ label: "6", value: 7 },
|
||||
{ label: "7", value: 8 },
|
||||
{ label: "8", value: 9 },
|
||||
{ label: "9", value: 10 },
|
||||
{ label: "10", value: 11 },
|
||||
{ label: "11", value: 12 },
|
||||
{ label: "12", value: 13 },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Row start",
|
||||
key: "grid-row-start",
|
||||
control: Select,
|
||||
placeholder: "Auto",
|
||||
options: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12],
|
||||
},
|
||||
{
|
||||
label: "Row end",
|
||||
key: "grid-row-end",
|
||||
control: Select,
|
||||
placeholder: "Auto",
|
||||
options: [
|
||||
{ label: "1", value: 2 },
|
||||
{ label: "2", value: 3 },
|
||||
{ label: "3", value: 4 },
|
||||
{ label: "4", value: 5 },
|
||||
{ label: "5", value: 6 },
|
||||
{ label: "6", value: 7 },
|
||||
{ label: "7", value: 8 },
|
||||
{ label: "8", value: 9 },
|
||||
{ label: "9", value: 10 },
|
||||
{ label: "10", value: 11 },
|
||||
{ label: "11", value: 12 },
|
||||
{ label: "12", value: 13 },
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
export const margin = {
|
||||
label: "Margin",
|
||||
columns: "1fr 1fr",
|
||||
|
|
|
@ -13,7 +13,8 @@
|
|||
"icon": "ClassicGridView",
|
||||
"children": [
|
||||
"container",
|
||||
"section"
|
||||
"section",
|
||||
"grid"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
|
@ -86,6 +86,7 @@
|
|||
"hasChildren": true,
|
||||
"showSettingsBar": true,
|
||||
"styles": [
|
||||
"grid",
|
||||
"padding",
|
||||
"size",
|
||||
"background",
|
||||
|
@ -4378,5 +4379,13 @@
|
|||
"required": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"grid": {
|
||||
"name": "Grid",
|
||||
"icon": "ViewGrid",
|
||||
"hasChildren": true,
|
||||
"styles": [
|
||||
"size"
|
||||
]
|
||||
}
|
||||
}
|
|
@ -151,6 +151,10 @@
|
|||
children: children.length,
|
||||
styles: {
|
||||
...instance._styles,
|
||||
normal: {
|
||||
...instance._styles?.normal,
|
||||
...(selected ? $builderStore.gridStyles : null),
|
||||
},
|
||||
id,
|
||||
empty: emptyState,
|
||||
interactive,
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
<script>
|
||||
import { getContext } from "svelte"
|
||||
import { fade } from "svelte/transition"
|
||||
|
||||
const component = getContext("component")
|
||||
const { styleable, builderStore } = getContext("sdk")
|
||||
|
||||
$: coords = generateCoords(12)
|
||||
|
||||
const generateCoords = num => {
|
||||
let grid = []
|
||||
for (let row = 0; row < num; row++) {
|
||||
for (let col = 0; col < num; col++) {
|
||||
grid.push({ row, col })
|
||||
}
|
||||
}
|
||||
return grid
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="grid" use:styleable={$component.styles}>
|
||||
<div class="underlay">
|
||||
{#each coords as coord}
|
||||
<div class="placeholder" />
|
||||
{/each}
|
||||
</div>
|
||||
<slot />
|
||||
{#if $builderStore.isDragging}
|
||||
<div
|
||||
class="overlay"
|
||||
in:fade={{ duration: 130 }}
|
||||
out:fade|self={{ duration: 130 }}
|
||||
>
|
||||
{#each coords as coord}
|
||||
<div
|
||||
class="placeholder grid-coord"
|
||||
data-row={coord.row}
|
||||
data-col={coord.col}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.grid {
|
||||
position: relative;
|
||||
min-height: 400px;
|
||||
}
|
||||
.grid,
|
||||
.underlay,
|
||||
.overlay {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(12, 1fr);
|
||||
grid-template-rows: repeat(12, 1fr);
|
||||
}
|
||||
.underlay,
|
||||
.overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
grid-gap: 2px;
|
||||
background-color: var(--spectrum-global-color-gray-200);
|
||||
border: 2px solid var(--spectrum-global-color-gray-200);
|
||||
}
|
||||
.underlay {
|
||||
z-index: -1;
|
||||
}
|
||||
.overlay {
|
||||
z-index: 999;
|
||||
background-color: var(--spectrum-global-color-gray-500);
|
||||
border-color: var(--spectrum-global-color-gray-500);
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
background-color: var(--spectrum-global-color-gray-100);
|
||||
}
|
||||
</style>
|
|
@ -34,6 +34,7 @@ export { default as spectrumcard } from "./SpectrumCard.svelte"
|
|||
export { default as tag } from "./Tag.svelte"
|
||||
export { default as markdownviewer } from "./MarkdownViewer.svelte"
|
||||
export { default as embeddedmap } from "./embedded-map/EmbeddedMap.svelte"
|
||||
export { default as grid } from "./Grid.svelte"
|
||||
export * from "./charts"
|
||||
export * from "./forms"
|
||||
export * from "./table"
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
import { get } from "svelte/store"
|
||||
import IndicatorSet from "./IndicatorSet.svelte"
|
||||
import DNDPositionIndicator from "./DNDPositionIndicator.svelte"
|
||||
import { builderStore } from "stores"
|
||||
import { builderStore, componentStore } from "stores"
|
||||
|
||||
let dragInfo
|
||||
let dropInfo
|
||||
|
@ -35,22 +35,41 @@
|
|||
|
||||
const getDOMNodeForComponent = component => {
|
||||
const parent = component.closest(".component")
|
||||
const children = Array.from(parent.children)
|
||||
return children[0]
|
||||
const children = Array.from(parent?.children || [])
|
||||
return children?.[0]
|
||||
}
|
||||
|
||||
// Callback when initially starting a drag on a draggable component
|
||||
const onDragStart = e => {
|
||||
const parent = e.target.closest(".component")
|
||||
if (!parent?.classList.contains("draggable")) {
|
||||
var img = new Image()
|
||||
img.src =
|
||||
"data:image/gif;base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs="
|
||||
e.dataTransfer.setDragImage(img, 0, 0)
|
||||
|
||||
// Resize component
|
||||
if (e.target.classList.contains("anchor")) {
|
||||
dragInfo = {
|
||||
target: e.target.dataset.id,
|
||||
side: e.target.dataset.side,
|
||||
mode: "resize",
|
||||
}
|
||||
} else {
|
||||
// Drag component
|
||||
const parent = e.target.closest(".component")
|
||||
if (!parent?.classList.contains("draggable")) {
|
||||
return
|
||||
}
|
||||
dragInfo = {
|
||||
target: parent.dataset.id,
|
||||
parent: parent.dataset.parent,
|
||||
mode: "move",
|
||||
}
|
||||
}
|
||||
|
||||
if (!dragInfo) {
|
||||
return
|
||||
}
|
||||
|
||||
// Update state
|
||||
dragInfo = {
|
||||
target: parent.dataset.id,
|
||||
parent: parent.dataset.parent,
|
||||
}
|
||||
builderStore.actions.selectComponent(dragInfo.target)
|
||||
builderStore.actions.setDragging(true)
|
||||
|
||||
|
@ -71,20 +90,48 @@
|
|||
}
|
||||
}
|
||||
|
||||
// Update grid styles
|
||||
if ($builderStore.gridStyles) {
|
||||
builderStore.actions.updateStyles($builderStore.gridStyles)
|
||||
}
|
||||
|
||||
// Reset state and styles
|
||||
dragInfo = null
|
||||
dropInfo = null
|
||||
builderStore.actions.setDragging(false)
|
||||
}
|
||||
|
||||
// Callback when on top of a component
|
||||
const onDragOver = e => {
|
||||
// Skip if we aren't validly dragging currently
|
||||
if (!dragInfo || !dropInfo) {
|
||||
if (!dragInfo) {
|
||||
return
|
||||
}
|
||||
|
||||
e.preventDefault()
|
||||
|
||||
// Set drag info for grids if not set
|
||||
if (!dragInfo.grid) {
|
||||
const coord = e.target.closest(".grid-coord")
|
||||
if (coord) {
|
||||
const row = parseInt(coord.dataset.row)
|
||||
const col = parseInt(coord.dataset.col)
|
||||
const component = $componentStore.selectedComponent
|
||||
const getStyle = x => parseInt(component._styles.normal?.[x] || "0")
|
||||
dragInfo.grid = {
|
||||
startRow: row,
|
||||
startCol: col,
|
||||
rowDeltaMin: 1 - getStyle("grid-row-start"),
|
||||
rowDeltaMax: 13 - getStyle("grid-row-end"),
|
||||
colDeltaMin: 1 - getStyle("grid-column-start"),
|
||||
colDeltaMax: 13 - getStyle("grid-column-end"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!dropInfo) {
|
||||
return
|
||||
}
|
||||
|
||||
const { droppableInside, bounds } = dropInfo
|
||||
const { top, left, height, width } = bounds
|
||||
const mouseY = e.clientY
|
||||
|
@ -147,6 +194,72 @@
|
|||
return
|
||||
}
|
||||
|
||||
const coord = e.target.closest(".grid-coord")
|
||||
if (coord && dragInfo.grid) {
|
||||
const row = parseInt(coord.dataset.row)
|
||||
const col = parseInt(coord.dataset.col)
|
||||
const { mode, side, grid } = dragInfo
|
||||
const {
|
||||
startRow,
|
||||
startCol,
|
||||
rowDeltaMin,
|
||||
rowDeltaMax,
|
||||
colDeltaMin,
|
||||
colDeltaMax,
|
||||
} = grid
|
||||
|
||||
const component = $componentStore.selectedComponent
|
||||
const rowStart = parseInt(
|
||||
component._styles.normal?.["grid-row-start"] || 0
|
||||
)
|
||||
const rowEnd = parseInt(component._styles.normal?.["grid-row-end"] || 0)
|
||||
const colStart = parseInt(
|
||||
component._styles.normal?.["grid-column-start"] || 0
|
||||
)
|
||||
const colEnd = parseInt(
|
||||
component._styles.normal?.["grid-column-end"] || 0
|
||||
)
|
||||
|
||||
let rowDelta = row - startRow
|
||||
let colDelta = col - startCol
|
||||
|
||||
if (mode === "move") {
|
||||
rowDelta = Math.min(Math.max(rowDelta, rowDeltaMin), rowDeltaMax)
|
||||
colDelta = Math.min(Math.max(colDelta, colDeltaMin), colDeltaMax)
|
||||
builderStore.actions.setGridStyles({
|
||||
"grid-row-start": rowStart + rowDelta,
|
||||
"grid-row-end": rowEnd + rowDelta,
|
||||
"grid-column-start": colStart + colDelta,
|
||||
"grid-column-end": colEnd + colDelta,
|
||||
})
|
||||
} else if (mode === "resize") {
|
||||
let newStyles = {}
|
||||
if (side === "right") {
|
||||
newStyles["grid-column-end"] = colEnd + colDelta
|
||||
} else if (side === "left") {
|
||||
newStyles["grid-column-start"] = colStart + colDelta
|
||||
} else if (side === "top") {
|
||||
newStyles["grid-row-start"] = rowStart + rowDelta
|
||||
} else if (side === "bottom") {
|
||||
newStyles["grid-row-end"] = rowEnd + rowDelta
|
||||
} else if (side === "bottom-right") {
|
||||
newStyles["grid-column-end"] = colEnd + colDelta
|
||||
newStyles["grid-row-end"] = rowEnd + rowDelta
|
||||
} else if (side === "bottom-left") {
|
||||
newStyles["grid-column-start"] = colStart + colDelta
|
||||
newStyles["grid-row-end"] = rowEnd + rowDelta
|
||||
} else if (side === "top-right") {
|
||||
newStyles["grid-column-end"] = colEnd + colDelta
|
||||
newStyles["grid-row-start"] = rowStart + rowDelta
|
||||
} else if (side === "top-left") {
|
||||
newStyles["grid-column-start"] = colStart + colDelta
|
||||
newStyles["grid-row-start"] = rowStart + rowDelta
|
||||
}
|
||||
builderStore.actions.setGridStyles(newStyles)
|
||||
}
|
||||
}
|
||||
return
|
||||
|
||||
const element = e.target.closest(".component:not(.block)")
|
||||
if (
|
||||
element &&
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
export let transition = false
|
||||
export let line = false
|
||||
export let alignRight = false
|
||||
export let componentId
|
||||
|
||||
$: flipped = top < 24
|
||||
</script>
|
||||
|
@ -40,6 +41,54 @@
|
|||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
<div
|
||||
draggable={true}
|
||||
class="anchor right"
|
||||
data-side="right"
|
||||
data-id={componentId}
|
||||
/>
|
||||
<div
|
||||
draggable={true}
|
||||
class="anchor top"
|
||||
data-side="top"
|
||||
data-id={componentId}
|
||||
/>
|
||||
<div
|
||||
draggable={true}
|
||||
class="anchor left"
|
||||
data-side="left"
|
||||
data-id={componentId}
|
||||
/>
|
||||
<div
|
||||
draggable={true}
|
||||
class="anchor bottom"
|
||||
data-side="bottom"
|
||||
data-id={componentId}
|
||||
/>
|
||||
<div
|
||||
draggable={true}
|
||||
class="anchor bottom-right"
|
||||
data-side="bottom-right"
|
||||
data-id={componentId}
|
||||
/>
|
||||
<div
|
||||
draggable={true}
|
||||
class="anchor bottom-left"
|
||||
data-side="bottom-left"
|
||||
data-id={componentId}
|
||||
/>
|
||||
<div
|
||||
draggable={true}
|
||||
class="anchor top-right"
|
||||
data-side="top-right"
|
||||
data-id={componentId}
|
||||
/>
|
||||
<div
|
||||
draggable={true}
|
||||
class="anchor top-left"
|
||||
data-side="top-left"
|
||||
data-id={componentId}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
|
@ -105,4 +154,55 @@
|
|||
/* Icon styles */
|
||||
.label :global(.spectrum-Icon + .text) {
|
||||
}
|
||||
|
||||
/* Anchor */
|
||||
.anchor {
|
||||
position: absolute;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
background-color: var(--color);
|
||||
pointer-events: all;
|
||||
}
|
||||
.anchor.right {
|
||||
right: -6px;
|
||||
top: calc(50% - 5px);
|
||||
cursor: e-resize;
|
||||
}
|
||||
.anchor.left {
|
||||
left: -6px;
|
||||
top: calc(50% - 5px);
|
||||
cursor: w-resize;
|
||||
}
|
||||
.anchor.bottom {
|
||||
left: calc(50% - 5px);
|
||||
bottom: -6px;
|
||||
cursor: s-resize;
|
||||
}
|
||||
.anchor.top {
|
||||
left: calc(50% - 5px);
|
||||
top: -6px;
|
||||
cursor: n-resize;
|
||||
}
|
||||
|
||||
.anchor.bottom-right {
|
||||
right: -6px;
|
||||
bottom: -6px;
|
||||
cursor: se-resize;
|
||||
}
|
||||
.anchor.bottom-left {
|
||||
left: -6px;
|
||||
bottom: -6px;
|
||||
cursor: sw-resize;
|
||||
}
|
||||
.anchor.top-right {
|
||||
right: -6px;
|
||||
top: -6px;
|
||||
cursor: ne-resize;
|
||||
}
|
||||
.anchor.top-left {
|
||||
left: -6px;
|
||||
top: -6px;
|
||||
cursor: nw-resize;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -127,6 +127,7 @@
|
|||
height={indicator.height}
|
||||
text={idx === 0 ? text : null}
|
||||
icon={idx === 0 ? icon : null}
|
||||
{componentId}
|
||||
{transition}
|
||||
{zIndex}
|
||||
{color}
|
||||
|
|
|
@ -32,6 +32,10 @@ const loadBudibase = () => {
|
|||
const enableDevTools = !get(builderStore).inBuilder && get(appStore).isDevApp
|
||||
devToolsStore.actions.setEnabled(enableDevTools)
|
||||
|
||||
if (get(builderStore).gridOffset) {
|
||||
builderStore.actions.setDragging(false)
|
||||
}
|
||||
|
||||
// Create app if one hasn't been created yet
|
||||
if (!app) {
|
||||
app = new ClientApp({
|
||||
|
|
|
@ -40,6 +40,9 @@ const createBuilderStore = () => {
|
|||
updateProp: (prop, value) => {
|
||||
dispatchEvent("update-prop", { prop, value })
|
||||
},
|
||||
updateStyles: styles => {
|
||||
dispatchEvent("update-styles", { styles })
|
||||
},
|
||||
keyDown: (key, ctrlKey) => {
|
||||
dispatchEvent("key-down", { key, ctrlKey })
|
||||
},
|
||||
|
@ -67,7 +70,11 @@ const createBuilderStore = () => {
|
|||
if (dragging === get(store).isDragging) {
|
||||
return
|
||||
}
|
||||
store.update(state => ({ ...state, isDragging: dragging }))
|
||||
store.update(state => ({
|
||||
...state,
|
||||
isDragging: dragging,
|
||||
gridStyles: null,
|
||||
}))
|
||||
},
|
||||
setEditMode: enabled => {
|
||||
if (enabled === get(store).editMode) {
|
||||
|
@ -84,6 +91,12 @@ const createBuilderStore = () => {
|
|||
highlightSetting: setting => {
|
||||
dispatchEvent("highlight-setting", { setting })
|
||||
},
|
||||
setGridStyles: styles => {
|
||||
store.update(state => {
|
||||
state.gridStyles = styles
|
||||
return state
|
||||
})
|
||||
},
|
||||
}
|
||||
return {
|
||||
...store,
|
||||
|
|
|
@ -27,7 +27,7 @@ export const styleable = (node, styles = {}) => {
|
|||
const setupStyles = (newStyles = {}) => {
|
||||
let baseStyles = {}
|
||||
if (newStyles.empty) {
|
||||
baseStyles.border = "2px dashed var(--spectrum-global-color-gray-600)"
|
||||
// baseStyles.border = "2px dashed var(--spectrum-global-color-gray-600)"
|
||||
baseStyles.padding = "var(--spacing-l)"
|
||||
baseStyles.overflow = "hidden"
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue