diff --git a/packages/builder/src/builderStore/store/frontend.js b/packages/builder/src/builderStore/store/frontend.js
index 4d0653208c..9deeef15f1 100644
--- a/packages/builder/src/builderStore/store/frontend.js
+++ b/packages/builder/src/builderStore/store/frontend.js
@@ -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
diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/AppPreview.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/AppPreview.svelte
index 9f81effd1d..ba132053b7 100644
--- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/AppPreview.svelte
+++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/AppPreview.svelte
@@ -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)
diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/_components/settings/componentStyles.js b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/_components/settings/componentStyles.js
index d4912241b3..710cbed9b1 100644
--- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/_components/settings/componentStyles.js
+++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/_components/settings/componentStyles.js
@@ -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",
diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/new/_components/componentStructure.json b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/new/_components/componentStructure.json
index acd28c6a41..fddff9d0d9 100644
--- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/new/_components/componentStructure.json
+++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/new/_components/componentStructure.json
@@ -13,7 +13,8 @@
"icon": "ClassicGridView",
"children": [
"container",
- "section"
+ "section",
+ "grid"
]
},
{
diff --git a/packages/client/manifest.json b/packages/client/manifest.json
index 87cc8b2567..642b4e0f99 100644
--- a/packages/client/manifest.json
+++ b/packages/client/manifest.json
@@ -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"
+ ]
}
}
\ No newline at end of file
diff --git a/packages/client/src/components/Component.svelte b/packages/client/src/components/Component.svelte
index a7f506a387..e55b5288e5 100644
--- a/packages/client/src/components/Component.svelte
+++ b/packages/client/src/components/Component.svelte
@@ -151,6 +151,10 @@
children: children.length,
styles: {
...instance._styles,
+ normal: {
+ ...instance._styles?.normal,
+ ...(selected ? $builderStore.gridStyles : null),
+ },
id,
empty: emptyState,
interactive,
diff --git a/packages/client/src/components/app/Grid.svelte b/packages/client/src/components/app/Grid.svelte
new file mode 100644
index 0000000000..51c5162386
--- /dev/null
+++ b/packages/client/src/components/app/Grid.svelte
@@ -0,0 +1,81 @@
+
+
+
+
+ {#each coords as coord}
+
+ {/each}
+
+
+ {#if $builderStore.isDragging}
+
+ {#each coords as coord}
+
+ {/each}
+
+ {/if}
+
+
+
diff --git a/packages/client/src/components/app/index.js b/packages/client/src/components/app/index.js
index 1c0d868433..b64e074115 100644
--- a/packages/client/src/components/app/index.js
+++ b/packages/client/src/components/app/index.js
@@ -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"
diff --git a/packages/client/src/components/preview/DNDHandler.svelte b/packages/client/src/components/preview/DNDHandler.svelte
index c37eb93afa..719f17efe3 100644
--- a/packages/client/src/components/preview/DNDHandler.svelte
+++ b/packages/client/src/components/preview/DNDHandler.svelte
@@ -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 &&
diff --git a/packages/client/src/components/preview/Indicator.svelte b/packages/client/src/components/preview/Indicator.svelte
index 1cb669bdc4..d450dd666a 100644
--- a/packages/client/src/components/preview/Indicator.svelte
+++ b/packages/client/src/components/preview/Indicator.svelte
@@ -13,6 +13,7 @@
export let transition = false
export let line = false
export let alignRight = false
+ export let componentId
$: flipped = top < 24
@@ -40,6 +41,54 @@
{/if}
{/if}
+
+
+
+
+
+
+
+
diff --git a/packages/client/src/components/preview/IndicatorSet.svelte b/packages/client/src/components/preview/IndicatorSet.svelte
index 662741d100..99db893faa 100644
--- a/packages/client/src/components/preview/IndicatorSet.svelte
+++ b/packages/client/src/components/preview/IndicatorSet.svelte
@@ -127,6 +127,7 @@
height={indicator.height}
text={idx === 0 ? text : null}
icon={idx === 0 ? icon : null}
+ {componentId}
{transition}
{zIndex}
{color}
diff --git a/packages/client/src/index.js b/packages/client/src/index.js
index b582dab4d3..b590ee9b00 100644
--- a/packages/client/src/index.js
+++ b/packages/client/src/index.js
@@ -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({
diff --git a/packages/client/src/stores/builder.js b/packages/client/src/stores/builder.js
index 32eb956d52..77540b3205 100644
--- a/packages/client/src/stores/builder.js
+++ b/packages/client/src/stores/builder.js
@@ -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,
diff --git a/packages/client/src/utils/styleable.js b/packages/client/src/utils/styleable.js
index b07a3213d9..5cbe74bb68 100644
--- a/packages/client/src/utils/styleable.js
+++ b/packages/client/src/utils/styleable.js
@@ -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"
}