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