Add initial work on grid layout

This commit is contained in:
Andrew Kingston 2022-08-26 08:47:50 +01:00
parent 211277b594
commit 16c9c6c0dc
14 changed files with 423 additions and 16 deletions

View File

@ -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

View File

@ -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)

View File

@ -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",

View File

@ -13,7 +13,8 @@
"icon": "ClassicGridView",
"children": [
"container",
"section"
"section",
"grid"
]
},
{

View File

@ -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"
]
}
}

View File

@ -151,6 +151,10 @@
children: children.length,
styles: {
...instance._styles,
normal: {
...instance._styles?.normal,
...(selected ? $builderStore.gridStyles : null),
},
id,
empty: emptyState,
interactive,

View File

@ -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>

View File

@ -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"

View File

@ -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 &&

View File

@ -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>

View File

@ -127,6 +127,7 @@
height={indicator.height}
text={idx === 0 ? text : null}
icon={idx === 0 ? icon : null}
{componentId}
{transition}
{zIndex}
{color}

View File

@ -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({

View File

@ -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,

View File

@ -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"
}