Merge branch 'feature/binding-interface' into feature/binding-interface-frontend
This commit is contained in:
commit
f615f5be3b
|
@ -1,4 +1,4 @@
|
||||||
import { values } from "lodash/fp"
|
import { values, cloneDeep } from "lodash/fp"
|
||||||
import getNewComponentName from "../getNewComponentName"
|
import getNewComponentName from "../getNewComponentName"
|
||||||
import { backendUiStore } from "builderStore"
|
import { backendUiStore } from "builderStore"
|
||||||
import * as backendStoreActions from "./backend"
|
import * as backendStoreActions from "./backend"
|
||||||
|
@ -24,8 +24,8 @@ import {
|
||||||
saveCurrentPreviewItem as _saveCurrentPreviewItem,
|
saveCurrentPreviewItem as _saveCurrentPreviewItem,
|
||||||
saveScreenApi as _saveScreenApi,
|
saveScreenApi as _saveScreenApi,
|
||||||
regenerateCssForCurrentScreen,
|
regenerateCssForCurrentScreen,
|
||||||
|
generateNewIdsForComponent,
|
||||||
} from "../storeUtils"
|
} from "../storeUtils"
|
||||||
|
|
||||||
export const getStore = () => {
|
export const getStore = () => {
|
||||||
const initial = {
|
const initial = {
|
||||||
apps: [],
|
apps: [],
|
||||||
|
@ -70,6 +70,8 @@ export const getStore = () => {
|
||||||
store.addTemplatedComponent = addTemplatedComponent(store)
|
store.addTemplatedComponent = addTemplatedComponent(store)
|
||||||
store.setMetadataProp = setMetadataProp(store)
|
store.setMetadataProp = setMetadataProp(store)
|
||||||
store.editPageOrScreen = editPageOrScreen(store)
|
store.editPageOrScreen = editPageOrScreen(store)
|
||||||
|
store.pasteComponent = pasteComponent(store)
|
||||||
|
store.storeComponentForCopy = storeComponentForCopy(store)
|
||||||
return store
|
return store
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -291,9 +293,14 @@ const addChildComponent = store => (componentToAdd, presetProps = {}) => {
|
||||||
state
|
state
|
||||||
)
|
)
|
||||||
|
|
||||||
state.currentComponentInfo._children = state.currentComponentInfo._children.concat(
|
const currentComponent =
|
||||||
newComponent.props
|
state.components[state.currentComponentInfo._component]
|
||||||
)
|
|
||||||
|
const targetParent = currentComponent.children
|
||||||
|
? state.currentComponentInfo
|
||||||
|
: getParent(state.currentPreviewItem.props, state.currentComponentInfo)
|
||||||
|
|
||||||
|
targetParent._children = targetParent._children.concat(newComponent.props)
|
||||||
|
|
||||||
state.currentFrontEndType === "page"
|
state.currentFrontEndType === "page"
|
||||||
? _savePage(state)
|
? _savePage(state)
|
||||||
|
@ -454,3 +461,50 @@ const setMetadataProp = store => (name, prop) => {
|
||||||
return s
|
return s
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const storeComponentForCopy = store => (component, cut = false) => {
|
||||||
|
store.update(s => {
|
||||||
|
const copiedComponent = cloneDeep(component)
|
||||||
|
s.componentToPaste = copiedComponent
|
||||||
|
s.componentToPaste.isCut = cut
|
||||||
|
if (cut) {
|
||||||
|
const parent = getParent(s.currentPreviewItem.props, component._id)
|
||||||
|
parent._children = parent._children.filter(c => c._id !== component._id)
|
||||||
|
selectComponent(s, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
return s
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const pasteComponent = store => (targetComponent, mode) => {
|
||||||
|
store.update(s => {
|
||||||
|
if (!s.componentToPaste) return s
|
||||||
|
|
||||||
|
const componentToPaste = cloneDeep(s.componentToPaste)
|
||||||
|
// retain the same ids as things may be referencing this component
|
||||||
|
if (componentToPaste.isCut) {
|
||||||
|
// in case we paste a second time
|
||||||
|
s.componentToPaste.isCut = false
|
||||||
|
} else {
|
||||||
|
generateNewIdsForComponent(componentToPaste, s)
|
||||||
|
}
|
||||||
|
delete componentToPaste.isCut
|
||||||
|
|
||||||
|
if (mode === "inside") {
|
||||||
|
targetComponent._children.push(componentToPaste)
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
const parent = getParent(s.currentPreviewItem.props, targetComponent)
|
||||||
|
|
||||||
|
const targetIndex = parent._children.indexOf(targetComponent)
|
||||||
|
const index = mode === "above" ? targetIndex : targetIndex + 1
|
||||||
|
parent._children.splice(index, 0, cloneDeep(componentToPaste))
|
||||||
|
regenerateCssForCurrentScreen(s)
|
||||||
|
_saveCurrentPreviewItem(s)
|
||||||
|
selectComponent(s, componentToPaste)
|
||||||
|
|
||||||
|
return s
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import { makePropsSafe } from "components/userInterface/pagesParsing/createProps"
|
import { makePropsSafe } from "components/userInterface/pagesParsing/createProps"
|
||||||
import api from "./api"
|
import api from "./api"
|
||||||
import { generate_screen_css } from "./generate_css"
|
import { generate_screen_css } from "./generate_css"
|
||||||
|
import { uuid } from "./uuid"
|
||||||
|
import getNewComponentName from "./getNewComponentName"
|
||||||
|
|
||||||
export const selectComponent = (state, component) => {
|
export const selectComponent = (state, component) => {
|
||||||
const componentDef = component._component.startsWith("##")
|
const componentDef = component._component.startsWith("##")
|
||||||
|
@ -79,3 +81,9 @@ export const regenerateCssForCurrentScreen = state => {
|
||||||
])
|
])
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const generateNewIdsForComponent = (c, state) =>
|
||||||
|
walkProps(c, p => {
|
||||||
|
p._id = uuid()
|
||||||
|
p._instanceName = getNewComponentName(p._component, state)
|
||||||
|
})
|
||||||
|
|
|
@ -99,7 +99,7 @@
|
||||||
<EditRowPopover {row} />
|
<EditRowPopover {row} />
|
||||||
</td>
|
</td>
|
||||||
{#each headers as header}
|
{#each headers as header}
|
||||||
<td class="hoverable">
|
<td>
|
||||||
{#if schema[header].type === 'link'}
|
{#if schema[header].type === 'link'}
|
||||||
<LinkedRecord field={schema[header]} ids={row[header]} />
|
<LinkedRecord field={schema[header]} ids={row[header]} />
|
||||||
{:else}{row[header] || ''}{/if}
|
{:else}{row[header] || ''}{/if}
|
||||||
|
@ -168,6 +168,9 @@
|
||||||
max-width: 200px;
|
max-width: 200px;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
border: 1px solid var(--grey-4);
|
border: 1px solid var(--grey-4);
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: pre;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
tbody tr {
|
tbody tr {
|
||||||
|
@ -187,6 +190,7 @@
|
||||||
|
|
||||||
.popovers {
|
.popovers {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
gap: var(--spacing-m);
|
||||||
}
|
}
|
||||||
|
|
||||||
.no-data {
|
.no-data {
|
||||||
|
|
|
@ -124,8 +124,14 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.field {
|
.field {
|
||||||
margin-top: var(--spacing-xl);
|
display: grid;
|
||||||
margin-bottom: var(--spacing-xl);
|
grid-template-columns: auto 20px 1fr;
|
||||||
|
align-items: center;
|
||||||
|
grid-gap: 5px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: var(--spacing-l);
|
||||||
|
font-family: var(--font-normal);
|
||||||
}
|
}
|
||||||
|
|
||||||
.button-margin-3 {
|
.button-margin-3 {
|
||||||
|
|
|
@ -31,6 +31,9 @@
|
||||||
{/each}
|
{/each}
|
||||||
</Select>
|
</Select>
|
||||||
{:else}
|
{:else}
|
||||||
|
{#if type === 'checkbox'}
|
||||||
|
<label>{label}</label>
|
||||||
|
{/if}
|
||||||
<Input
|
<Input
|
||||||
thin
|
thin
|
||||||
placeholder={label}
|
placeholder={label}
|
||||||
|
@ -41,3 +44,12 @@
|
||||||
on:input={handleInput}
|
on:input={handleInput}
|
||||||
on:change={handleInput} />
|
on:change={handleInput} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
label {
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: var(--font-size-s);
|
||||||
|
float: left;
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -45,8 +45,7 @@
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
h5 {
|
h5 {
|
||||||
padding: var(--spacing-xl) 0 0 var(--spacing-xl);
|
margin-bottom: var(--spacing-l);
|
||||||
margin: 0;
|
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,21 +11,6 @@
|
||||||
|
|
||||||
const { open, close } = getContext("simple-modal")
|
const { open, close } = getContext("simple-modal")
|
||||||
|
|
||||||
let HEADINGS = [
|
|
||||||
{
|
|
||||||
title: "Tables",
|
|
||||||
key: "TABLES",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Tables",
|
|
||||||
key: "NAVIGATE",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Add",
|
|
||||||
key: "ADD",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
$: selectedTab = $backendUiStore.tabs.NAVIGATION_PANEL
|
$: selectedTab = $backendUiStore.tabs.NAVIGATION_PANEL
|
||||||
|
|
||||||
function selectModel(model, fieldId) {
|
function selectModel(model, fieldId) {
|
||||||
|
@ -44,7 +29,7 @@
|
||||||
{#if $backendUiStore.selectedDatabase && $backendUiStore.selectedDatabase._id}
|
{#if $backendUiStore.selectedDatabase && $backendUiStore.selectedDatabase._id}
|
||||||
<div class="hierarchy">
|
<div class="hierarchy">
|
||||||
<div class="components-list-container">
|
<div class="components-list-container">
|
||||||
<h3>Tables</h3>
|
<h4>Tables</h4>
|
||||||
<CreateTablePopover />
|
<CreateTablePopover />
|
||||||
<div class="hierarchy-items-container">
|
<div class="hierarchy-items-container">
|
||||||
{#each $backendUiStore.models as model}
|
{#each $backendUiStore.models as model}
|
||||||
|
@ -63,6 +48,10 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
h4 {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
.items-root {
|
.items-root {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
import { MoreIcon } from "components/common/Icons"
|
import { MoreIcon } from "components/common/Icons"
|
||||||
import { store } from "builderStore"
|
import { store } from "builderStore"
|
||||||
import { getComponentDefinition } from "builderStore/store"
|
import { getComponentDefinition } from "builderStore/store"
|
||||||
import getNewComponentName from "builderStore/getNewComponentName"
|
|
||||||
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
||||||
import { last, cloneDeep } from "lodash/fp"
|
import { last, cloneDeep } from "lodash/fp"
|
||||||
import UIkit from "uikit"
|
import UIkit from "uikit"
|
||||||
|
@ -29,8 +28,7 @@
|
||||||
})
|
})
|
||||||
$: dropdown && UIkit.util.on(dropdown, "shown", () => (hidden = false))
|
$: dropdown && UIkit.util.on(dropdown, "shown", () => (hidden = false))
|
||||||
$: noChildrenAllowed =
|
$: noChildrenAllowed =
|
||||||
!component ||
|
!component || !getComponentDefinition($store, component._component).children
|
||||||
getComponentDefinition($store, component._component).children === false
|
|
||||||
$: noPaste = !$store.componentToPaste
|
$: noPaste = !$store.componentToPaste
|
||||||
|
|
||||||
const lastPartOfName = c => (c ? last(c._component.split("/")) : "")
|
const lastPartOfName = c => (c ? last(c._component.split("/")) : "")
|
||||||
|
@ -81,7 +79,9 @@
|
||||||
store.update(s => {
|
store.update(s => {
|
||||||
const parent = getParent(s.currentPreviewItem.props, component)
|
const parent = getParent(s.currentPreviewItem.props, component)
|
||||||
const copiedComponent = cloneDeep(component)
|
const copiedComponent = cloneDeep(component)
|
||||||
generateNewIdsForComponent(copiedComponent, s)
|
walkProps(copiedComponent, p => {
|
||||||
|
p._id = uuid()
|
||||||
|
})
|
||||||
parent._children = [...parent._children, copiedComponent]
|
parent._children = [...parent._children, copiedComponent]
|
||||||
saveCurrentPreviewItem(s)
|
saveCurrentPreviewItem(s)
|
||||||
s.currentComponentInfo = copiedComponent
|
s.currentComponentInfo = copiedComponent
|
||||||
|
@ -104,50 +104,14 @@
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const generateNewIdsForComponent = (c, state) =>
|
|
||||||
walkProps(c, p => {
|
|
||||||
p._id = uuid()
|
|
||||||
p._instanceName = getNewComponentName(p._component, state)
|
|
||||||
})
|
|
||||||
|
|
||||||
const storeComponentForCopy = (cut = false) => {
|
const storeComponentForCopy = (cut = false) => {
|
||||||
store.update(s => {
|
// lives in store - also used by drag drop
|
||||||
const copiedComponent = cloneDeep(component)
|
store.storeComponentForCopy(component, cut)
|
||||||
s.componentToPaste = copiedComponent
|
|
||||||
if (cut) {
|
|
||||||
const parent = getParent(s.currentPreviewItem.props, component._id)
|
|
||||||
parent._children = parent._children.filter(c => c._id !== component._id)
|
|
||||||
selectComponent(s, parent)
|
|
||||||
}
|
|
||||||
|
|
||||||
return s
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const pasteComponent = mode => {
|
const pasteComponent = mode => {
|
||||||
store.update(s => {
|
// lives in store - also used by drag drop
|
||||||
if (!s.componentToPaste) return s
|
store.pasteComponent(component, mode)
|
||||||
|
|
||||||
const componentToPaste = cloneDeep(s.componentToPaste)
|
|
||||||
generateNewIdsForComponent(componentToPaste, s)
|
|
||||||
delete componentToPaste._cutId
|
|
||||||
|
|
||||||
if (mode === "inside") {
|
|
||||||
component._children.push(componentToPaste)
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
const parent = getParent(s.currentPreviewItem.props, component)
|
|
||||||
|
|
||||||
const targetIndex = parent._children.indexOf(component)
|
|
||||||
const index = mode === "above" ? targetIndex : targetIndex + 1
|
|
||||||
parent._children.splice(index, 0, cloneDeep(componentToPaste))
|
|
||||||
regenerateCssForCurrentScreen(s)
|
|
||||||
saveCurrentPreviewItem(s)
|
|
||||||
selectComponent(s, componentToPaste)
|
|
||||||
|
|
||||||
return s
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -7,21 +7,27 @@
|
||||||
import { store } from "builderStore"
|
import { store } from "builderStore"
|
||||||
import { ArrowDownIcon, ShapeIcon } from "components/common/Icons/"
|
import { ArrowDownIcon, ShapeIcon } from "components/common/Icons/"
|
||||||
import ScreenDropdownMenu from "./ScreenDropdownMenu.svelte"
|
import ScreenDropdownMenu from "./ScreenDropdownMenu.svelte"
|
||||||
|
import { writable } from "svelte/store"
|
||||||
|
|
||||||
export let screens = []
|
export let screens = []
|
||||||
|
|
||||||
|
const dragDropStore = writable({})
|
||||||
|
|
||||||
let confirmDeleteDialog
|
let confirmDeleteDialog
|
||||||
let componentToDelete = ""
|
let componentToDelete = ""
|
||||||
|
|
||||||
const joinPath = join("/")
|
const joinPath = join("/")
|
||||||
|
|
||||||
const normalizedName = name =>
|
const normalizedName = name =>
|
||||||
pipe(name, [
|
pipe(
|
||||||
trimCharsStart("./"),
|
name,
|
||||||
trimCharsStart("~/"),
|
[
|
||||||
trimCharsStart("../"),
|
trimCharsStart("./"),
|
||||||
trimChars(" "),
|
trimCharsStart("~/"),
|
||||||
])
|
trimCharsStart("../"),
|
||||||
|
trimChars(" "),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
const changeScreen = screen => {
|
const changeScreen = screen => {
|
||||||
store.setCurrentScreen(screen.props._instanceName)
|
store.setCurrentScreen(screen.props._instanceName)
|
||||||
|
@ -57,7 +63,8 @@
|
||||||
{#if $store.currentPreviewItem.props._instanceName && $store.currentPreviewItem.props._instanceName === screen.props._instanceName && screen.props._children}
|
{#if $store.currentPreviewItem.props._instanceName && $store.currentPreviewItem.props._instanceName === screen.props._instanceName && screen.props._children}
|
||||||
<ComponentsHierarchyChildren
|
<ComponentsHierarchyChildren
|
||||||
components={screen.props._children}
|
components={screen.props._children}
|
||||||
currentComponent={$store.currentComponentInfo} />
|
currentComponent={$store.currentComponentInfo}
|
||||||
|
{dragDropStore} />
|
||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
|
|
||||||
|
@ -94,10 +101,6 @@
|
||||||
margin: 0 0 0 5px;
|
margin: 0 0 0 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
:global(svg) {
|
|
||||||
transition: 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rotate :global(svg) {
|
.rotate :global(svg) {
|
||||||
transform: rotate(-90deg);
|
transform: rotate(-90deg);
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,11 +15,19 @@
|
||||||
export let currentComponent
|
export let currentComponent
|
||||||
export let onSelect = () => {}
|
export let onSelect = () => {}
|
||||||
export let level = 0
|
export let level = 0
|
||||||
|
export let dragDropStore
|
||||||
|
|
||||||
|
let dropUnderComponent
|
||||||
|
let componentToDrop
|
||||||
|
|
||||||
const capitalise = s => s.substring(0, 1).toUpperCase() + s.substring(1)
|
const capitalise = s => s.substring(0, 1).toUpperCase() + s.substring(1)
|
||||||
const get_name = s => (!s ? "" : last(s.split("/")))
|
const get_name = s => (!s ? "" : last(s.split("/")))
|
||||||
|
|
||||||
const get_capitalised_name = name => pipe(name, [get_name, capitalise])
|
const get_capitalised_name = name =>
|
||||||
|
pipe(
|
||||||
|
name,
|
||||||
|
[get_name, capitalise]
|
||||||
|
)
|
||||||
const isScreenslot = name => name === "##builtin/screenslot"
|
const isScreenslot = name => name === "##builtin/screenslot"
|
||||||
|
|
||||||
const selectComponent = component => {
|
const selectComponent = component => {
|
||||||
|
@ -32,15 +40,92 @@
|
||||||
// Go to correct URL
|
// Go to correct URL
|
||||||
$goto(`./:page/:screen/${path}`)
|
$goto(`./:page/:screen/${path}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const dragstart = component => e => {
|
||||||
|
e.dataTransfer.dropEffect = "move"
|
||||||
|
dragDropStore.update(s => {
|
||||||
|
s.componentToDrop = component
|
||||||
|
return s
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const dragover = (component, index) => e => {
|
||||||
|
const canHaveChildrenButIsEmpty =
|
||||||
|
$store.components[component._component].children &&
|
||||||
|
component._children.length === 0
|
||||||
|
|
||||||
|
e.dataTransfer.dropEffect = "copy"
|
||||||
|
dragDropStore.update(s => {
|
||||||
|
const isBottomHalf = e.offsetY > e.currentTarget.offsetHeight / 2
|
||||||
|
s.targetComponent = component
|
||||||
|
// only allow dropping inside when container type
|
||||||
|
// is empty. If it has children, the user can drag over
|
||||||
|
// it's existing children
|
||||||
|
if (canHaveChildrenButIsEmpty) {
|
||||||
|
if (index === 0) {
|
||||||
|
// when its the first component in the screen,
|
||||||
|
// we divide into 3, so we can paste above, inside or below
|
||||||
|
const pos = e.offsetY / e.currentTarget.offsetHeight
|
||||||
|
if (pos < 0.4) {
|
||||||
|
s.dropPosition = "above"
|
||||||
|
} else if (pos > 0.8) {
|
||||||
|
// purposely giving this the least space as it is often covered
|
||||||
|
// by the component below's "above" space
|
||||||
|
s.dropPosition = "below"
|
||||||
|
} else {
|
||||||
|
s.dropPosition = "inside"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
s.dropPosition = isBottomHalf ? "below" : "inside"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
s.dropPosition = isBottomHalf ? "below" : "above"
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
})
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const drop = () => {
|
||||||
|
if ($dragDropStore.targetComponent !== $dragDropStore.componentToDrop) {
|
||||||
|
store.storeComponentForCopy($dragDropStore.componentToDrop, true)
|
||||||
|
store.pasteComponent(
|
||||||
|
$dragDropStore.targetComponent,
|
||||||
|
$dragDropStore.dropPosition
|
||||||
|
)
|
||||||
|
}
|
||||||
|
dragDropStore.update(s => {
|
||||||
|
s.dropPosition = ""
|
||||||
|
s.targetComponent = null
|
||||||
|
s.componentToDrop = null
|
||||||
|
return s
|
||||||
|
})
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
{#each components as component, index (component._id)}
|
{#each components as component, index (component._id)}
|
||||||
<li on:click|stopPropagation={() => selectComponent(component)}>
|
<li on:click|stopPropagation={() => selectComponent(component)}>
|
||||||
|
|
||||||
|
{#if $dragDropStore && $dragDropStore.targetComponent === component && $dragDropStore.dropPosition === 'above'}
|
||||||
|
<div
|
||||||
|
on:drop={drop}
|
||||||
|
ondragover="return false"
|
||||||
|
ondragenter="return false"
|
||||||
|
class="budibase__nav-item item drop-item"
|
||||||
|
style="margin-left: {level * 20 + 40}px" />
|
||||||
|
{/if}
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="budibase__nav-item item"
|
class="budibase__nav-item item"
|
||||||
class:selected={currentComponent === component}
|
class:selected={currentComponent === component}
|
||||||
style="padding-left: {level * 20 + 40}px">
|
style="padding-left: {level * 20 + 40}px"
|
||||||
|
draggable={true}
|
||||||
|
on:dragstart={dragstart(component)}
|
||||||
|
on:dragover={dragover(component, index)}
|
||||||
|
on:drop={drop}
|
||||||
|
ondragover="return false"
|
||||||
|
ondragenter="return false">
|
||||||
<div class="nav-item">
|
<div class="nav-item">
|
||||||
<i class="icon ri-arrow-right-circle-line" />
|
<i class="icon ri-arrow-right-circle-line" />
|
||||||
{isScreenslot(component._component) ? 'Screenslot' : component._instanceName}
|
{isScreenslot(component._component) ? 'Screenslot' : component._instanceName}
|
||||||
|
@ -55,8 +140,18 @@
|
||||||
components={component._children}
|
components={component._children}
|
||||||
{currentComponent}
|
{currentComponent}
|
||||||
{onSelect}
|
{onSelect}
|
||||||
|
{dragDropStore}
|
||||||
level={level + 1} />
|
level={level + 1} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
{#if $dragDropStore && $dragDropStore.targetComponent === component && ($dragDropStore.dropPosition === 'inside' || $dragDropStore.dropPosition === 'below')}
|
||||||
|
<div
|
||||||
|
on:drop={drop}
|
||||||
|
ondragover="return false"
|
||||||
|
ondragenter="return false"
|
||||||
|
class="budibase__nav-item item drop-item"
|
||||||
|
style="margin-left: {(level + ($dragDropStore.dropPosition === 'inside' ? 2 : 0)) * 20 + 40}px" />
|
||||||
|
{/if}
|
||||||
</li>
|
</li>
|
||||||
{/each}
|
{/each}
|
||||||
</ul>
|
</ul>
|
||||||
|
@ -78,6 +173,11 @@
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.drop-item {
|
||||||
|
background: var(--blue-light);
|
||||||
|
height: 36px;
|
||||||
|
}
|
||||||
|
|
||||||
.actions {
|
.actions {
|
||||||
display: none;
|
display: none;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
|
|
|
@ -16,12 +16,14 @@
|
||||||
import { pipe } from "components/common/core"
|
import { pipe } from "components/common/core"
|
||||||
import { store } from "builderStore"
|
import { store } from "builderStore"
|
||||||
import { ArrowDownIcon, GridIcon } from "components/common/Icons/"
|
import { ArrowDownIcon, GridIcon } from "components/common/Icons/"
|
||||||
|
import { writable } from "svelte/store"
|
||||||
|
|
||||||
export let layout
|
export let layout
|
||||||
|
|
||||||
let confirmDeleteDialog
|
let confirmDeleteDialog
|
||||||
let componentToDelete = ""
|
let componentToDelete = ""
|
||||||
|
|
||||||
|
const dragDropStore = writable({})
|
||||||
const joinPath = join("/")
|
const joinPath = join("/")
|
||||||
|
|
||||||
const lastPartOfName = c =>
|
const lastPartOfName = c =>
|
||||||
|
@ -57,7 +59,8 @@
|
||||||
<ComponentsHierarchyChildren
|
<ComponentsHierarchyChildren
|
||||||
thisComponent={_layout.component.props}
|
thisComponent={_layout.component.props}
|
||||||
components={_layout.component.props._children}
|
components={_layout.component.props._children}
|
||||||
currentComponent={$store.currentComponentInfo} />
|
currentComponent={$store.currentComponentInfo}
|
||||||
|
{dragDropStore} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
@ -81,10 +84,6 @@
|
||||||
color: var(--grey-7);
|
color: var(--grey-7);
|
||||||
}
|
}
|
||||||
|
|
||||||
:global(svg) {
|
|
||||||
transition: 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rotate :global(svg) {
|
.rotate :global(svg) {
|
||||||
transform: rotate(-90deg);
|
transform: rotate(-90deg);
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,7 +48,7 @@ export const createProps = (componentDefinition, derivedFromProps) => {
|
||||||
assign(props, derivedFromProps)
|
assign(props, derivedFromProps)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (componentDefinition.children !== false && isUndefined(props._children)) {
|
if (isUndefined(props._children)) {
|
||||||
props._children = []
|
props._children = []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -70,6 +70,20 @@ export const layout = [
|
||||||
{ label: "no wrap", value: "noWrap" },
|
{ label: "no wrap", value: "noWrap" },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: "Gap",
|
||||||
|
key: "gap",
|
||||||
|
control: OptionSelect,
|
||||||
|
options: [
|
||||||
|
{ label: "None", value: "0px" },
|
||||||
|
{ label: "4px", value: "4px" },
|
||||||
|
{ label: "8px", value: "8px" },
|
||||||
|
{ label: "16px", value: "16px" },
|
||||||
|
{ label: "20px", value: "20px" },
|
||||||
|
{ label: "32px", value: "32px" },
|
||||||
|
{ label: "64px", value: "64px" },
|
||||||
|
],
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
export const margin = [
|
export const margin = [
|
||||||
|
|
|
@ -313,6 +313,174 @@ export default {
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "Card",
|
||||||
|
description: "Card components",
|
||||||
|
icon: "ri-layout-bottom-line",
|
||||||
|
commonProps: {},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
_component: "@budibase/standard-components/card",
|
||||||
|
name: "Vertical",
|
||||||
|
description:
|
||||||
|
"A basic card component that can contain content and actions.",
|
||||||
|
icon: "ri-layout-column-line",
|
||||||
|
children: [],
|
||||||
|
properties: {
|
||||||
|
design: { ...all },
|
||||||
|
settings: [
|
||||||
|
{
|
||||||
|
label: "Image",
|
||||||
|
key: "imageUrl",
|
||||||
|
control: Input,
|
||||||
|
placeholder: "Image",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Heading",
|
||||||
|
key: "heading",
|
||||||
|
control: Input,
|
||||||
|
placeholder: "Heading",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Description",
|
||||||
|
key: "description",
|
||||||
|
control: Input,
|
||||||
|
placeholder: "Description",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Link Text",
|
||||||
|
key: "linkText",
|
||||||
|
control: Input,
|
||||||
|
placeholder: "Link Text",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Link Url",
|
||||||
|
key: "linkUrl",
|
||||||
|
control: Input,
|
||||||
|
placeholder: "Link URL",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Link Color",
|
||||||
|
key: "color",
|
||||||
|
control: Input,
|
||||||
|
placeholder: "Link Color",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Hover Color",
|
||||||
|
key: "linkHoverColor",
|
||||||
|
control: Input,
|
||||||
|
placeholder: "Hover Color",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Image Height",
|
||||||
|
key: "imageHeight",
|
||||||
|
control: OptionSelect,
|
||||||
|
options: ["12rem", "16rem", "20rem", "24rem"],
|
||||||
|
placeholder: "Image Height",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Card Width",
|
||||||
|
key: "cardWidth",
|
||||||
|
control: OptionSelect,
|
||||||
|
options: ["16rem", "20rem", "24rem"],
|
||||||
|
placeholder: "Card Width",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_component: "@budibase/standard-components/cardhorizontal",
|
||||||
|
name: "Horizontal",
|
||||||
|
description:
|
||||||
|
"A basic card component that can contain content and actions.",
|
||||||
|
icon: "ri-layout-row-line",
|
||||||
|
children: [],
|
||||||
|
properties: {
|
||||||
|
design: { ...all },
|
||||||
|
settings: [
|
||||||
|
{
|
||||||
|
label: "Image",
|
||||||
|
key: "imageUrl",
|
||||||
|
control: Input,
|
||||||
|
placeholder: "Image",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Heading",
|
||||||
|
key: "heading",
|
||||||
|
control: Input,
|
||||||
|
placeholder: "Heading",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Description",
|
||||||
|
key: "description",
|
||||||
|
control: Input,
|
||||||
|
placeholder: "Description",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Subtext",
|
||||||
|
key: "subtext",
|
||||||
|
control: Input,
|
||||||
|
placeholder: "Subtext",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Link Text",
|
||||||
|
key: "linkText",
|
||||||
|
control: Input,
|
||||||
|
placeholder: "Link Text",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Link Url",
|
||||||
|
key: "linkUrl",
|
||||||
|
control: Input,
|
||||||
|
placeholder: "Link URL",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Link Color",
|
||||||
|
key: "color",
|
||||||
|
control: Input,
|
||||||
|
placeholder: "Link Color",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Hover Color",
|
||||||
|
key: "linkHoverColor",
|
||||||
|
control: Input,
|
||||||
|
placeholder: "Hover Color",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Card Width",
|
||||||
|
key: "cardWidth",
|
||||||
|
control: OptionSelect,
|
||||||
|
options: [
|
||||||
|
"24rem",
|
||||||
|
"28rem",
|
||||||
|
"32rem",
|
||||||
|
"40rem",
|
||||||
|
"48rem",
|
||||||
|
"60rem",
|
||||||
|
"100%",
|
||||||
|
],
|
||||||
|
placeholder: "Card Height",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Image Width",
|
||||||
|
key: "imageWidth",
|
||||||
|
control: OptionSelect,
|
||||||
|
options: ["8rem", "12rem", "16rem"],
|
||||||
|
placeholder: "Image Width",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Image Height",
|
||||||
|
key: "imageHeight",
|
||||||
|
control: OptionSelect,
|
||||||
|
options: ["8rem", "12rem", "16rem", "auto"],
|
||||||
|
placeholder: "Image Height",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
_component: "@budibase/materialdesign-components/BasicCard",
|
_component: "@budibase/materialdesign-components/BasicCard",
|
||||||
name: "Card",
|
name: "Card",
|
||||||
|
|
|
@ -72,16 +72,6 @@ describe("createDefaultProps", () => {
|
||||||
expect(props._children).toEqual([])
|
expect(props._children).toEqual([])
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should create a object without _children array, when children===false ", () => {
|
|
||||||
const comp = getcomponent()
|
|
||||||
comp.children = false
|
|
||||||
|
|
||||||
const { props, errors } = createProps(comp)
|
|
||||||
|
|
||||||
expect(errors).toEqual([])
|
|
||||||
expect(props._children).not.toBeDefined()
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should create a object with single empty array, when prop definition is 'event' ", () => {
|
it("should create a object with single empty array, when prop definition is 'event' ", () => {
|
||||||
const comp = getcomponent()
|
const comp = getcomponent()
|
||||||
comp.props.onClick = "event"
|
comp.props.onClick = "event"
|
||||||
|
@ -104,24 +94,22 @@ describe("createDefaultProps", () => {
|
||||||
expect(props._children).toEqual([])
|
expect(props._children).toEqual([])
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should create a _children array when children not defined ", () => {
|
it("should always create _children ", () => {
|
||||||
const comp = getcomponent()
|
|
||||||
|
|
||||||
const { props, errors } = createProps(comp)
|
|
||||||
|
|
||||||
expect(errors).toEqual([])
|
|
||||||
expect(props._children).toBeDefined()
|
|
||||||
expect(props._children).toEqual([])
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should not create _children array when children=false ", () => {
|
|
||||||
const comp = getcomponent()
|
const comp = getcomponent()
|
||||||
comp.children = false
|
comp.children = false
|
||||||
|
|
||||||
const { props, errors } = createProps(comp)
|
const createRes1 = createProps(comp)
|
||||||
|
|
||||||
expect(errors).toEqual([])
|
expect(createRes1.errors).toEqual([])
|
||||||
expect(props._children).not.toBeDefined()
|
expect(createRes1.props._children).toBeDefined()
|
||||||
|
|
||||||
|
const comp2 = getcomponent()
|
||||||
|
comp2.children = true
|
||||||
|
|
||||||
|
const createRes2 = createProps(comp)
|
||||||
|
|
||||||
|
expect(createRes2.errors).toEqual([])
|
||||||
|
expect(createRes2.props._children).toBeDefined()
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should create an object with multiple prop names", () => {
|
it("should create an object with multiple prop names", () => {
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
"Navigation": {
|
"Navigation": {
|
||||||
"name": "Navigation",
|
"name": "Navigation",
|
||||||
"description": "A basic header navigation component",
|
"description": "A basic header navigation component",
|
||||||
|
"children": true,
|
||||||
"props": {
|
"props": {
|
||||||
"logoUrl": "string",
|
"logoUrl": "string",
|
||||||
"title": "string",
|
"title": "string",
|
||||||
|
@ -250,7 +251,8 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"list": {
|
"list": {
|
||||||
"description": "A configurable data list that attaches to your backend models.",
|
"description": "configurable data list that attaches to your backend models.",
|
||||||
|
"children": true,
|
||||||
"context": "model",
|
"context": "model",
|
||||||
"data": true,
|
"data": true,
|
||||||
"props": {
|
"props": {
|
||||||
|
@ -271,12 +273,88 @@
|
||||||
},
|
},
|
||||||
"recorddetail": {
|
"recorddetail": {
|
||||||
"description": "Loads a record, using an ID in the url",
|
"description": "Loads a record, using an ID in the url",
|
||||||
|
"children": true,
|
||||||
"context": "model",
|
"context": "model",
|
||||||
"data": true,
|
"data": true,
|
||||||
"props": {
|
"props": {
|
||||||
"model": "models"
|
"model": "models"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"card": {
|
||||||
|
"name": "Card",
|
||||||
|
"props": {
|
||||||
|
"imageUrl": "string",
|
||||||
|
"heading": "string",
|
||||||
|
"description": "string",
|
||||||
|
"linkText": "string",
|
||||||
|
"linkUrl": "string",
|
||||||
|
"color": "string",
|
||||||
|
"linkHoverColor": "string",
|
||||||
|
"imageHeight": {
|
||||||
|
"type": "options",
|
||||||
|
"default": "20rem",
|
||||||
|
"options": [
|
||||||
|
"12rem",
|
||||||
|
"16rem",
|
||||||
|
"20rem",
|
||||||
|
"24rem"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"cardWidth": {
|
||||||
|
"type": "options",
|
||||||
|
"default": "20rem",
|
||||||
|
"options": [
|
||||||
|
"16rem",
|
||||||
|
"20rem",
|
||||||
|
"24rem"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"cardhorizontal": {
|
||||||
|
"name": "Horizontal Card",
|
||||||
|
"props": {
|
||||||
|
"imageUrl": "string",
|
||||||
|
"heading": "string",
|
||||||
|
"description": "string",
|
||||||
|
"subtext": "string",
|
||||||
|
"linkText": "string",
|
||||||
|
"linkUrl": "string",
|
||||||
|
"color": "string",
|
||||||
|
"linkHoverColor": "string",
|
||||||
|
"imageWidth": {
|
||||||
|
"type": "options",
|
||||||
|
"default": "8rem",
|
||||||
|
"options": [
|
||||||
|
"8rem",
|
||||||
|
"12rem",
|
||||||
|
"16rem"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"cardWidth": {
|
||||||
|
"type": "options",
|
||||||
|
"default": "32rem",
|
||||||
|
"options": [
|
||||||
|
"24rem",
|
||||||
|
"28rem",
|
||||||
|
"32rem",
|
||||||
|
"40rem",
|
||||||
|
"48rem",
|
||||||
|
"60rem",
|
||||||
|
"100%"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"imageHeight": {
|
||||||
|
"type": "options",
|
||||||
|
"default": "8rem",
|
||||||
|
"options": [
|
||||||
|
"8rem",
|
||||||
|
"12rem",
|
||||||
|
"16rem"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"datamap": {
|
"datamap": {
|
||||||
"description": "shiny chart",
|
"description": "shiny chart",
|
||||||
"data": true,
|
"data": true,
|
||||||
|
@ -534,7 +612,7 @@
|
||||||
"bullet": {
|
"bullet": {
|
||||||
"description": "Bullet chart",
|
"description": "Bullet chart",
|
||||||
"data": true,
|
"data": true,
|
||||||
"props": {
|
"props": {
|
||||||
"model": "string",
|
"model": "string",
|
||||||
"color": "string",
|
"color": "string",
|
||||||
"customSubtitle": "string",
|
"customSubtitle": "string",
|
||||||
|
@ -608,6 +686,7 @@
|
||||||
},
|
},
|
||||||
"container": {
|
"container": {
|
||||||
"name": "Container",
|
"name": "Container",
|
||||||
|
"children": true,
|
||||||
"description": "An element that contains and lays out other elements. e.g. <div>, <header> etc",
|
"description": "An element that contains and lays out other elements. e.g. <div>, <header> etc",
|
||||||
"props": {
|
"props": {
|
||||||
"className": "string",
|
"className": "string",
|
||||||
|
|
|
@ -0,0 +1,80 @@
|
||||||
|
<script>
|
||||||
|
import { cssVars, createClasses } from "./cssVars"
|
||||||
|
|
||||||
|
export const className = ""
|
||||||
|
export let imageUrl = ""
|
||||||
|
export let heading = ""
|
||||||
|
export let description = ""
|
||||||
|
export let linkText = ""
|
||||||
|
export let linkUrl
|
||||||
|
export let color
|
||||||
|
export let linkHoverColor
|
||||||
|
export let imageHeight
|
||||||
|
export let cardWidth
|
||||||
|
|
||||||
|
$: cssVariables = {
|
||||||
|
color,
|
||||||
|
linkHoverColor,
|
||||||
|
imageHeight,
|
||||||
|
cardWidth,
|
||||||
|
}
|
||||||
|
|
||||||
|
$: showImage = !!imageUrl
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div use:cssVars={cssVariables} class="container">
|
||||||
|
{#if showImage}
|
||||||
|
<img class="image" src={imageUrl} alt="" />
|
||||||
|
{/if}
|
||||||
|
<div class="content">
|
||||||
|
<h2 class="heading">{heading}</h2>
|
||||||
|
<h4 class="text">{description}</h4>
|
||||||
|
<a href={linkUrl}>{linkText}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.container {
|
||||||
|
width: var(--cardWidth);
|
||||||
|
overflow: hidden !important;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image {
|
||||||
|
width: 100% !important;
|
||||||
|
max-width: 100%;
|
||||||
|
height: var(--imageHeight);
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
padding: 1.5rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.heading {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
font-weight: 700;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text {
|
||||||
|
font-size: 1rem;
|
||||||
|
margin: 0;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
margin: 0.5rem 0;
|
||||||
|
text-decoration: none;
|
||||||
|
color: var(--color);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
color: var(--linkHoverColor);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,104 @@
|
||||||
|
<script>
|
||||||
|
import { cssVars, createClasses } from "./cssVars"
|
||||||
|
|
||||||
|
export const className = ""
|
||||||
|
export let imageUrl = ""
|
||||||
|
export let heading = ""
|
||||||
|
export let description = ""
|
||||||
|
export let subtext = ""
|
||||||
|
export let linkText = ""
|
||||||
|
export let linkUrl
|
||||||
|
export let color
|
||||||
|
export let linkHoverColor
|
||||||
|
export let cardWidth
|
||||||
|
export let imageWidth
|
||||||
|
export let imageHeight
|
||||||
|
|
||||||
|
$: cssVariables = {
|
||||||
|
color,
|
||||||
|
linkHoverColor,
|
||||||
|
imageWidth,
|
||||||
|
cardWidth,
|
||||||
|
imageHeight,
|
||||||
|
}
|
||||||
|
|
||||||
|
$: showImage = !!imageUrl
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div use:cssVars={cssVariables} class="container">
|
||||||
|
{#if showImage}
|
||||||
|
<img class="image" src={imageUrl} alt="" />
|
||||||
|
{/if}
|
||||||
|
<div class="content">
|
||||||
|
<main>
|
||||||
|
<h2 class="heading">{heading}</h2>
|
||||||
|
<p class="text">{description}</p>
|
||||||
|
</main>
|
||||||
|
<footer>
|
||||||
|
<p class="subtext">{subtext}</p>
|
||||||
|
<a href={linkUrl}>{linkText}</a>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.container {
|
||||||
|
height: 100%;
|
||||||
|
max-width: var(--cardWidth);
|
||||||
|
display: flex !important;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image {
|
||||||
|
width: var(--imageWidth);
|
||||||
|
height: var(--imageHeight);
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.85rem 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.heading {
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 700;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
margin: 0.5rem 0 0 0;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtext {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
margin: 0;
|
||||||
|
font-weight: 400;
|
||||||
|
color: #757575;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
margin: 0.5rem 0 0 0;
|
||||||
|
text-decoration: none;
|
||||||
|
color: var(--color);
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
color: var(--linkHoverColor);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -23,5 +23,7 @@ export { default as list } from "./List.svelte"
|
||||||
export { default as datasearch } from "./DataSearch.svelte"
|
export { default as datasearch } from "./DataSearch.svelte"
|
||||||
export { default as embed } from "./Embed.svelte"
|
export { default as embed } from "./Embed.svelte"
|
||||||
export { default as stackedlist } from "./StackedList.svelte"
|
export { default as stackedlist } from "./StackedList.svelte"
|
||||||
|
export { default as card } from "./Card.svelte"
|
||||||
|
export { default as cardhorizontal } from "./CardHorizontal.svelte"
|
||||||
export { default as recorddetail } from "./RecordDetail.svelte"
|
export { default as recorddetail } from "./RecordDetail.svelte"
|
||||||
export * from "./Chart"
|
export * from "./Chart"
|
||||||
|
|
Loading…
Reference in New Issue