Move most grid specific logic into a dedicated file to avoid polluting rest of the codebase

This commit is contained in:
Andrew Kingston 2024-08-02 16:13:49 +01:00
parent dbfad2cb3a
commit 0ba00a5117
No known key found for this signature in database
9 changed files with 160 additions and 98 deletions

View File

@ -103,7 +103,7 @@
key: "props.cols",
label: "Columns",
control: Stepper,
defaultValue: 12,
defaultValue: 24,
props: {
min: 2,
max: 50,
@ -113,7 +113,7 @@
key: "props.rows",
label: "Rows",
control: Stepper,
defaultValue: 12,
defaultValue: 24,
props: {
min: 2,
max: 50,

View File

@ -1391,6 +1391,10 @@
"width": 25,
"height": 25
},
"grid": {
"hAlign": "center",
"vAlign": "center"
},
"settings": [
{
"type": "icon",
@ -1704,6 +1708,10 @@
"width": 260,
"height": 143
},
"grid": {
"hAlign": "center",
"vAlign": "center"
},
"settings": [
{
"type": "text",
@ -1737,6 +1745,10 @@
"width": 400,
"height": 100
},
"grid": {
"hAlign": "stretch",
"vAlign": "stretch"
},
"settings": [
{
"type": "text",
@ -5240,6 +5252,10 @@
"width": 300,
"height": 120
},
"grid": {
"hAlign": "center",
"vAlign": "center"
},
"settings": [
{
"type": "text",

View File

@ -284,7 +284,7 @@
visibility: hidden;
padding: 0;
margin: 0;
overflow: hidden;
overflow: clip;
width: 100%;
display: flex;
flex-direction: row;
@ -301,7 +301,7 @@
width: 100%;
height: 100%;
position: relative;
overflow: hidden;
overflow: clip;
background-color: transparent;
}
@ -311,7 +311,7 @@
}
#app-root {
overflow: hidden;
overflow: clip;
height: 100%;
width: 100%;
display: flex;

View File

@ -39,8 +39,7 @@
getActionContextKey,
getActionDependentContextKeys,
} from "../utils/buttonActions.js"
import { buildStyleString } from "utils/styleable.js"
import { getBaseGridVars } from "utils/grid.js"
import { gridLayout } from "utils/grid.js"
export let instance = {}
export let isLayout = false
@ -198,15 +197,18 @@
$: currentTheme = $context?.device?.theme
$: darkMode = !currentTheme?.includes("light")
// Build up full styles and split them into variables and non-variables
$: baseStyles = getBaseStyles(definition, errorState)
$: styles = {
...baseStyles,
$: normalStyles = {
...instance._styles?.normal,
...ephemeralStyles,
}
$: parsedStyles = parseStyles(styles)
$: wrapperCSS = buildStyleString(parsedStyles.variables)
$: gridMetadata = {
id,
interactive,
styles: normalStyles,
draggable,
definition,
errored: errorState,
}
// Update component context
$: store.set({
@ -214,8 +216,7 @@
children: children.length,
styles: {
...instance._styles,
normal: parsedStyles.nonVariables,
variables: parsedStyles.variables,
normal: normalStyles,
custom: customCSS,
id,
empty: emptyState,
@ -615,31 +616,6 @@
}
}
const handleWrapperClick = e => {
e.stopPropagation()
builderStore.actions.selectComponent(id)
}
// Splits component styles into variables and non-variables
const parseStyles = styles => {
let variables = {}
let nonVariables = {}
for (let style of Object.keys(styles || {})) {
const group = style.startsWith("--") ? variables : nonVariables
group[style] = styles[style]
}
return { variables, nonVariables }
}
// Generates any required base styles based on the component definition
const getBaseStyles = (definition, errored = false) => {
return {
"--default-width": errored ? 500 : definition.size?.width || 100,
"--default-height": errored ? 60 : definition.size?.height || 100,
...getBaseGridVars(definition, errored),
}
}
onMount(() => {
// Register this component instance for external access
if ($appStore.isDevApp) {
@ -683,14 +659,11 @@
class:parent={hasChildren}
class:block={isBlock}
class:error={errorState}
class:fill={definition.grid?.fill}
data-id={id}
data-name={name}
data-icon={icon}
data-parent={$component.id}
style={wrapperCSS}
{draggable}
on:click|self={interactive ? handleWrapperClick : null}
use:gridLayout={gridMetadata}
>
{#if errorState}
<ComponentErrorState

View File

@ -480,7 +480,7 @@
position: relative;
padding: 32px;
}
.main:has(> .grid) {
.main:has(.screenslot-dom > .component > .grid) {
padding-top: 0;
padding-bottom: 0;
}

View File

@ -68,16 +68,6 @@
position: relative;
height: 400px;
gap: 0;
/* Prevent cross-grid variable inheritance */
/* --grid-desktop-col-start: initial;
--grid-desktop-col-end: initial;
--grid-desktop-row-start: initial;
--grid-desktop-row-end: initial;
--grid-mobile-col-start: initial;
--grid-mobile-col-end: initial;
--grid-mobile-row-start: initial;
--grid-mobile-row-end: initial;*/
}
.grid,
.underlay {
@ -128,12 +118,8 @@
);
/* Flex vars */
--h-align: var(--grid-desktop-h-align, var(--grid-mobile-h-align, stretch));
--v-align: var(--grid-desktop-v-align, var(--grid-mobile-v-align, center));
--child-flex: var(
--grid-desktop-child-flex,
var(--grid-mobile-child-flex, 0 0 auto)
);
--h-align: var(--grid-desktop-h-align, var(--grid-mobile-h-align));
--v-align: var(--grid-desktop-v-align, var(--grid-mobile-v-align));
/* Ensure grid metadata falls within limits */
grid-column-start: min(max(1, var(--col-start)), var(--cols)) !important;
@ -171,16 +157,20 @@
);
/* Flex vars */
--h-align: var(--grid-mobile-h-align, var(--grid-desktop-h-align, stretch));
--v-align: var(--grid-mobile-v-align, var(--grid-desktop-v-align, center));
--child-flex: var(
--grid-mobile-child-flex,
var(--grid-desktop-child-flex, 0 0 auto)
);
--h-align: var(--grid-mobile-h-align, var(--grid-desktop-h-align));
--v-align: var(--grid-mobile-v-align, var(--grid-desktop-v-align));
}
/* Handle grid children which need to fill the outer component wrapper */
.grid :global(> .component > *) {
flex: var(--child-flex) !important;
flex: 0 0 auto !important;
}
.grid:not(.mobile) :global(> .component.grid-desktop-grow > *) {
flex: 1 1 0 !important;
height: 0 !important;
}
.grid.mobile :global(> .component.grid-mobile-grow > *) {
flex: 1 1 0 !important;
height: 0 !important;
}
</style>

View File

@ -2,7 +2,12 @@
import { onMount, onDestroy } from "svelte"
import { builderStore, componentStore } from "stores"
import { Utils, memo } from "@budibase/frontend-core"
import { isGridEvent, getGridParentID, getGridVar } from "utils/grid"
import {
isGridEvent,
getGridParentID,
gridCSSVars,
GridVars,
} from "utils/grid"
// Smallest possible 1x1 transparent GIF
const ghost = new Image(1, 1)
@ -15,10 +20,10 @@
// Grid CSS variables
$: vars = {
colStart: $getGridVar("col-start"),
colEnd: $getGridVar("col-end"),
rowStart: $getGridVar("row-start"),
rowEnd: $getGridVar("row-end"),
colStart: $gridCSSVars[GridVars.ColStart],
colEnd: $gridCSSVars[GridVars.ColEnd],
rowStart: $gridCSSVars[GridVars.RowStart],
rowEnd: $gridCSSVars[GridVars.RowEnd],
}
// Some memoisation of primitive types for performance

View File

@ -7,7 +7,7 @@
import { builderStore, componentStore, dndIsDragging } from "stores"
import { Utils } from "@budibase/frontend-core"
import { findComponentParent } from "utils/components"
import { getGridVar } from "utils/grid"
import { gridCSSVars, GridVars } from "utils/grid"
const verticalOffset = 36
const horizontalOffset = 2
@ -44,8 +44,8 @@
insideGrid &&
(definition?.grid?.hAlign !== "stretch" ||
definition?.grid?.vAlign !== "stretch")
$: gridHAlignVar = $getGridVar("h-align")
$: gridVAlignVar = $getGridVar("v-align")
$: gridHAlignVar = $gridCSSVars[GridVars.HAlign]
$: gridVAlignVar = $gridCSSVars[GridVars.VAlign]
$: gridStyles = $state?.styles
const getBarSettings = definition => {

View File

@ -1,5 +1,6 @@
import { builderStore, componentStore } from "stores"
import { derived, get, readable } from "svelte/store"
import { builderStore } from "stores"
import { derived } from "svelte/store"
import { buildStyleString } from "utils/styleable.js"
/**
* We use CSS variables on components to control positioning and layout of
@ -12,20 +13,42 @@ import { derived, get, readable } from "svelte/store"
* `grid.hAlign` and `grid.vAlign` keys in the manifest.
*/
// Enum representing the different CSS variables we use for grid metadata
export const GridVars = {
HAlign: "h-align",
VAlign: "v-align",
ColStart: "col-start",
ColEnd: "col-end",
RowStart: "row-start",
RowEnd: "row-end",
}
// Classes used in selectors inside grid containers to control child styles
export const GridClasses = {
DesktopFill: "grid-desktop-grow",
MobileFill: "grid-mobile-grow",
}
// Enum for device preview type, included in grid CSS variables
const Devices = {
Desktop: "desktop",
Mobile: "mobile",
}
// Generates the CSS variable for a certain grid param suffix, for the current
// device
// A derived map of all CSS variables for the current device
const previewDevice = derived(builderStore, $store => $store.previewDevice)
export const getGridVar = derived(previewDevice, device => suffix => {
const prefix = device === Devices.Mobile ? Devices.Mobile : Devices.Desktop
return `--grid-${prefix}-${suffix}`
export const gridCSSVars = derived(previewDevice, $device => {
const device = $device === Devices.Mobile ? Devices.Mobile : Devices.Desktop
let vars = {}
for (let type of Object.values(GridVars)) {
vars[type] = `--grid-${device}-${type}`
}
return vars
})
// Builds a CSS variable name for a certain piece of grid metadata
export const getGridCSSVar = (device, type) => `--grid-${device}-${type}`
// Generates the CSS variable for a certain grid param suffix, for the other
// device variant than the one included in this variable
export const getOtherDeviceGridVar = cssVar => {
@ -69,21 +92,6 @@ export const getGridParentID = node => {
return node?.parentNode?.closest(".grid")?.parentNode.dataset.id
}
// Generates the base set of grid CSS vars from a component definition
export const getBaseGridVars = (definition, errored = false) => {
const hAlign = errored ? "stretch" : definition?.grid?.hAlign || "stretch"
const vAlign = errored ? "stretch" : definition?.grid?.vAlign || "center"
const flexStyles = vAlign === "stretch" ? "1 1 0" : "0 0 auto"
return {
"--grid-desktop-h-align": hAlign,
"--grid-mobile-h-align": hAlign,
"--grid-desktop-v-align": vAlign,
"--grid-mobile-v-align": vAlign,
"--grid-desktop-child-flex": flexStyles,
"--grid-mobile-child-flex": flexStyles,
}
}
// Gets the current value of a certain grid CSS variable for a component
export const getGridVarValue = (styles, variable) => {
// Try the desired variable
@ -97,3 +105,73 @@ export const getGridVarValue = (styles, variable) => {
// Otherwise use the default
return val ? val : getDefaultGridVarValue(variable)
}
// Svelte action to apply required class names and styles to our component
// wrappers
export const gridLayout = (node, metadata) => {
let selectComponent
const applyMetadata = metadata => {
const { id, styles, interactive, errored, definition } = metadata
consol.log(styles)
// Callback to select the component when clicking on the wrapper
selectComponent = e => {
e.preventDefault()
builderStore.actions.selectComponent(id)
}
// Generate base set of grid CSS vars based for this component
const hAlign = errored ? "stretch" : definition?.grid?.hAlign || "stretch"
const vAlign = errored ? "stretch" : definition?.grid?.vAlign || "center"
const vars = {
"--default-width": errored ? 500 : definition.size?.width || 100,
"--default-height": errored ? 60 : definition.size?.height || 100,
"--grid-desktop-h-align": hAlign,
"--grid-mobile-h-align": hAlign,
"--grid-desktop-v-align": vAlign,
"--grid-mobile-v-align": vAlign,
}
// Extract any other CSS variables from the saved component styles
for (let style of Object.keys(styles)) {
if (style.startsWith("--")) {
vars[style] = styles[style]
delete styles[style]
}
}
// Apply all CSS variables to the wrapper
node.style = buildStyleString(vars)
// Toggle classes to specify whether our children should fill
const desktopVar = getGridCSSVar(Devices.Desktop, GridVars.VAlign)
const mobileVar = getGridCSSVar(Devices.Mobile, GridVars.VAlign)
node.classList.toggle(
GridClasses.DesktopFill,
vars[desktopVar] === "stretch"
)
node.classList.toggle(GridClasses.MobileFill, vars[mobileVar] === "stretch")
// Add a listener to select this node on click
if (interactive) {
node.addEventListener("click", selectComponent, false)
}
}
const removeListeners = () => {
node.removeEventListener("click", selectComponent)
}
applyMetadata(metadata)
return {
update(newMetadata) {
removeListeners()
applyMetadata(newMetadata)
},
destroy() {
removeListeners()
},
}
}