WIP component management and definition refactor

This commit is contained in:
Andrew Kingston 2021-01-12 20:00:35 +00:00
parent 9619240804
commit 2dc2e43a00
25 changed files with 589 additions and 700 deletions

View File

@ -5,7 +5,7 @@ import { getThemeStore } from "./store/theme"
import { derived, writable } from "svelte/store" import { derived, writable } from "svelte/store"
import analytics from "analytics" import analytics from "analytics"
import { FrontendTypes, LAYOUT_NAMES } from "../constants" import { FrontendTypes, LAYOUT_NAMES } from "../constants"
import { makePropsSafe } from "components/userInterface/assetParsing/createProps" import { findComponent } from "./storeUtils"
export const store = getFrontendStore() export const store = getFrontendStore()
export const backendUiStore = getBackendUiStore() export const backendUiStore = getBackendUiStore()
@ -25,31 +25,10 @@ export const currentAsset = derived(store, $store => {
export const selectedComponent = derived( export const selectedComponent = derived(
[store, currentAsset], [store, currentAsset],
([$store, $currentAsset]) => { ([$store, $currentAsset]) => {
if (!$currentAsset || !$store.selectedComponentId) return null if (!$currentAsset || !$store.selectedComponentId) {
return null
function traverse(node, callback) {
if (node._id === $store.selectedComponentId) return callback(node)
if (node._children) {
node._children.forEach(child => traverse(child, callback))
}
if (node.props) {
traverse(node.props, callback)
}
} }
return findComponent($currentAsset.props, $store.selectedComponentId)
let component
traverse($currentAsset, found => {
const componentIdentifier = found._component ?? found.props._component
const componentDef = componentIdentifier.startsWith("##")
? found
: $store.components[componentIdentifier]
component = makePropsSafe(componentDef, found)
})
return component
} }
) )

View File

@ -21,12 +21,9 @@ export const fetchComponentLibDefinitions = async appId => {
*/ */
export const fetchComponentLibModules = async application => { export const fetchComponentLibModules = async application => {
const allLibraries = {} const allLibraries = {}
for (let libraryName of application.componentLibraries) { for (let libraryName of application.componentLibraries) {
const LIBRARY_URL = `/${application._id}/componentlibrary?library=${libraryName}` const LIBRARY_URL = `/${application._id}/componentlibrary?library=${libraryName}`
const libraryModule = await import(LIBRARY_URL) allLibraries[libraryName] = await import(LIBRARY_URL)
allLibraries[libraryName] = libraryModule
} }
return allLibraries return allLibraries
} }

View File

@ -1,9 +1,5 @@
import { get, writable } from "svelte/store" import { get, writable } from "svelte/store"
import { cloneDeep } from "lodash/fp" import { cloneDeep } from "lodash/fp"
import {
createProps,
getBuiltin,
} from "components/userInterface/assetParsing/createProps"
import { import {
allScreens, allScreens,
backendUiStore, backendUiStore,
@ -15,14 +11,13 @@ import {
import { fetchComponentLibDefinitions } from "../loadComponentLibraries" import { fetchComponentLibDefinitions } from "../loadComponentLibraries"
import api from "../api" import api from "../api"
import { FrontendTypes } from "../../constants" import { FrontendTypes } from "../../constants"
import getNewComponentName from "../getNewComponentName"
import analytics from "analytics" import analytics from "analytics"
import { import {
findChildComponentType, findComponentType,
generateNewIdsForComponent, findComponentParent,
getComponentDefinition, findComponentPath,
findParent,
} from "../storeUtils" } from "../storeUtils"
import { uuid } from "../uuid"
const INITIAL_FRONTEND_STATE = { const INITIAL_FRONTEND_STATE = {
apps: [], apps: [],
@ -48,14 +43,7 @@ export const getFrontendStore = () => {
store.actions = { store.actions = {
initialise: async pkg => { initialise: async pkg => {
const { layouts, screens, application } = pkg const { layouts, screens, application } = pkg
store.update(state => {
state.appId = application._id
return state
})
const components = await fetchComponentLibDefinitions(pkg.application._id) const components = await fetchComponentLibDefinitions(pkg.application._id)
store.update(state => ({ store.update(state => ({
...state, ...state,
libraries: pkg.application.componentLibraries, libraries: pkg.application.componentLibraries,
@ -66,17 +54,14 @@ export const getFrontendStore = () => {
layouts, layouts,
screens, screens,
hasAppPackage: true, hasAppPackage: true,
builtins: [getBuiltin("##builtin/screenslot")],
appInstance: pkg.application.instance, appInstance: pkg.application.instance,
})) }))
await backendUiStore.actions.database.select(pkg.application.instance) await backendUiStore.actions.database.select(pkg.application.instance)
}, },
routing: { routing: {
fetch: async () => { fetch: async () => {
const response = await api.get("/api/routing") const response = await api.get("/api/routing")
const json = await response.json() const json = await response.json()
store.update(state => { store.update(state => {
state.routes = json.routes state.routes = json.routes
return state return state
@ -245,122 +230,194 @@ export const getFrontendStore = () => {
return state return state
}) })
}, },
create: (componentToAdd, presetProps) => { getDefinition: componentName => {
const selectedAsset = get(currentAsset) if (!componentName) {
return null
}
const name = componentName.startsWith("@budibase")
? componentName
: `@budibase/standard-components/${componentName}`
return get(store).components[name]
},
createInstance: (componentName, presetProps) => {
const definition = store.actions.components.getDefinition(componentName)
if (!definition) {
return null
}
store.update(state => { // Generate default props
function findSlot(component_array) { let props = { ...presetProps }
if (!component_array) { if (definition.settings) {
return false definition.settings.forEach(setting => {
if (setting.defaultValue !== undefined) {
props[setting.key] = setting.defaultValue
} }
for (let component of component_array) {
if (component._component === "##builtin/screenslot") {
return true
}
if (component._children) findSlot(component)
}
return false
}
if (
componentToAdd.startsWith("##") &&
findSlot(selectedAsset?.props._children)
) {
return state
}
const component = getComponentDefinition(state, componentToAdd)
const instanceId = get(backendUiStore).selectedDatabase._id
const instanceName = getNewComponentName(component, state)
const newComponent = createProps(component, {
...presetProps,
_instanceId: instanceId,
_instanceName: instanceName,
}) })
}
const selected = get(selectedComponent) // Add any extra properties the component needs
let extras = {}
if (definition.hasChildren) {
extras._children = []
}
const currentComponentDefinition = return {
state.components[selected._component] _id: uuid(),
_component: definition.component,
_styles: { normal: {}, hover: {}, active: {} },
_instanceName: `New ${definition.component.split("/")[2]}`,
...cloneDeep(props),
...extras,
}
},
create: (componentName, presetProps) => {
// Create new component
const componentInstance = store.actions.components.createInstance(
componentName,
presetProps
)
if (!componentInstance) {
return
}
const allowsChildren = currentComponentDefinition.children // Find parent node to attach this component to
let parentComponent
// Determine where to put the new component. const selected = get(selectedComponent)
let targetParent const asset = get(currentAsset)
if (allowsChildren) { if (!asset) {
// Child of the selected component return
targetParent = selected }
if (selected) {
// Use current screen or layout as parent if no component is selected
const definition = store.actions.components.getDefinition(
selected._component
)
if (definition?.hasChildren) {
// Use selected component if it allows children
parentComponent = selected
} else { } else {
// Sibling of selected component // Otherwise we need to use the parent of this component
targetParent = findParent(selectedAsset.props, selected) parentComponent = findComponentParent(asset.props, selected._id)
} }
} else {
// Use screen or layout if no component is selected
parentComponent = asset.props
}
// Don't continue if there's no parent // Attach component
if (!targetParent) return state if (!parentComponent) {
return
// Push the new component }
targetParent._children.push(newComponent.props) if (!parentComponent._children) {
parentComponent._children = []
store.actions.preview.saveSelected() }
parentComponent._children.push(componentInstance)
// Save components and update UI
store.actions.preview.saveSelected()
store.update(state => {
state.currentView = "component" state.currentView = "component"
state.selectedComponentId = newComponent.props._id state.selectedComponentId = componentInstance._id
analytics.captureEvent("Added Component", {
name: newComponent.props._component,
})
return state return state
}) })
// Log event
analytics.captureEvent("Added Component", {
name: componentInstance._component,
})
return componentInstance
},
delete: component => {
if (!component) {
return
}
const asset = get(currentAsset)
if (!asset) {
return
}
const parent = findComponentParent(asset.props, component._id)
if (parent) {
parent._children = parent._children.filter(
child => child._id !== component._id
)
store.actions.components.select(parent)
}
store.actions.preview.saveSelected()
}, },
copy: (component, cut = false) => { copy: (component, cut = false) => {
const selectedAsset = get(currentAsset) const selectedAsset = get(currentAsset)
if (!selectedAsset) {
return null
}
// Update store with copied component
store.update(state => { store.update(state => {
state.componentToPaste = cloneDeep(component) state.componentToPaste = cloneDeep(component)
state.componentToPaste.isCut = cut state.componentToPaste.isCut = cut
if (cut) { return state
const parent = findParent(selectedAsset.props, component._id) })
// Remove the component from its parent if we're cutting
if (cut) {
const parent = findComponentParent(selectedAsset.props, component._id)
if (parent) {
parent._children = parent._children.filter( parent._children = parent._children.filter(
child => child._id !== component._id child => child._id !== component._id
) )
store.actions.components.select(parent) store.actions.components.select(parent)
} }
}
return state
})
}, },
paste: async (targetComponent, mode) => { paste: async (targetComponent, mode) => {
const selectedAsset = get(currentAsset)
let promises = [] let promises = []
store.update(state => { store.update(state => {
if (!state.componentToPaste) return state // Stop if we have nothing to paste
if (!state.componentToPaste) {
const componentToPaste = cloneDeep(state.componentToPaste)
// retain the same ids as things may be referencing this component
if (componentToPaste.isCut) {
// in case we paste a second time
state.componentToPaste.isCut = false
} else {
generateNewIdsForComponent(componentToPaste, state)
}
delete componentToPaste.isCut
if (mode === "inside") {
targetComponent._children.push(componentToPaste)
return state return state
} }
const parent = findParent(selectedAsset.props, targetComponent) // Clone the component to paste
// Retain the same ID if cutting as things may be referencing this component
const cut = state.componentToPaste.isCut
delete state.componentToPaste.isCut
let componentToPaste = cloneDeep(state.componentToPaste)
if (cut) {
state.componentToPaste = null
} else {
componentToPaste._id = uuid()
}
const targetIndex = parent._children.indexOf(targetComponent) if (mode === "inside") {
const index = mode === "above" ? targetIndex : targetIndex + 1 // Paste inside target component if chosen
parent._children.splice(index, 0, cloneDeep(componentToPaste)) if (!targetComponent._children) {
targetComponent._children = []
}
targetComponent._children.push(componentToPaste)
} else {
// Otherwise find the parent so we can paste in the correct order
// in the parents child components
const selectedAsset = get(currentAsset)
if (!selectedAsset) {
return state
}
const parent = findComponentParent(
selectedAsset.props,
targetComponent._id
)
if (!parent) {
return state
}
// Insert the component in the correct position
const targetIndex = parent._children.indexOf(targetComponent)
const index = mode === "above" ? targetIndex : targetIndex + 1
parent._children.splice(index, 0, cloneDeep(componentToPaste))
}
// Save and select the new component
promises.push(store.actions.preview.saveSelected()) promises.push(store.actions.preview.saveSelected())
store.actions.components.select(componentToPaste) store.actions.components.select(componentToPaste)
return state return state
}) })
await Promise.all(promises) await Promise.all(promises)
@ -385,90 +442,68 @@ export const getFrontendStore = () => {
await store.actions.preview.saveSelected() await store.actions.preview.saveSelected()
}, },
updateProp: (name, value) => { updateProp: (name, value) => {
let component = get(selectedComponent)
if (!name || !component) {
return
}
component[name] = value
store.update(state => { store.update(state => {
let current_component = get(selectedComponent) state.selectedComponentId = component._id
current_component[name] = value
state.selectedComponentId = current_component._id
store.actions.preview.saveSelected()
return state return state
}) })
store.actions.preview.saveSelected()
}, },
findRoute: component => { findRoute: component => {
// Gets all the components to needed to construct a path.
const selectedAsset = get(currentAsset) const selectedAsset = get(currentAsset)
let pathComponents = [] if (!component || !selectedAsset) {
let parent = component return "/"
let root = false
while (!root) {
parent = findParent(selectedAsset.props, parent)
if (!parent) {
root = true
} else {
pathComponents.push(parent)
}
} }
// Remove root entry since it's the screen or layout. // Get the path to this component
// Reverse array since we need the correct order of the IDs const path = findComponentPath(selectedAsset.props, component._id) || []
const reversedComponents = pathComponents.reverse().slice(1)
// Add component // Remove root entry since it's the screen or layout
const allComponents = [...reversedComponents, component] return path.slice(1).join("/")
// Map IDs
const IdList = allComponents.map(c => c._id)
// Construct ID Path:
return IdList.join("/")
}, },
links: { links: {
save: async (url, title) => { save: async (url, title) => {
let promises = []
const layout = get(mainLayout) const layout = get(mainLayout)
store.update(state => { if (!layout) {
// Try to extract a nav component from the master layout return
const nav = findChildComponentType( }
layout,
"@budibase/standard-components/navigation"
)
if (nav) {
let newLink
// Clone an existing link if one exists // Find a nav bar in the main layout
if (nav._children && nav._children.length) { const nav = findComponentType(
// Clone existing link style layout,
newLink = cloneDeep(nav._children[0]) "@budibase/standard-components/navigation"
)
if (!nav) {
return
}
// Manipulate IDs to ensure uniqueness let newLink
generateNewIdsForComponent(newLink, state, false) if (nav._children && nav._children.length) {
// Clone an existing link if one exists
newLink = cloneDeep(nav._children[0])
// Set our new props // Set our new props
newLink._instanceName = `${title} Link` newLink._id = uuid()
newLink.url = url newLink._instanceName = `${title} Link`
newLink.text = title newLink.url = url
} else { newLink.text = title
// Otherwise create vanilla new link } else {
const component = getComponentDefinition( // Otherwise create vanilla new link
state, newLink = {
"@budibase/standard-components/link" ...store.actions.components.createInstance("link"),
) url,
const instanceId = get(backendUiStore).selectedDatabase._id text: title,
newLink = createProps(component, { _instanceName: `${title} Link`,
url,
text: title,
_instanceName: `${title} Link`,
_instanceId: instanceId,
}).props
}
// Save layout
nav._children = [...nav._children, newLink]
promises.push(store.actions.layouts.save(layout))
} }
return state }
})
await Promise.all(promises) // Save layout
nav._children = [...nav._children, newLink]
await store.actions.layouts.save(layout)
}, },
}, },
}, },

View File

@ -4,7 +4,6 @@ import rowListScreen from "./rowListScreen"
import emptyNewRowScreen from "./emptyNewRowScreen" import emptyNewRowScreen from "./emptyNewRowScreen"
import createFromScratchScreen from "./createFromScratchScreen" import createFromScratchScreen from "./createFromScratchScreen"
import emptyRowDetailScreen from "./emptyRowDetailScreen" import emptyRowDetailScreen from "./emptyRowDetailScreen"
import { generateNewIdsForComponent } from "../../storeUtils"
import { uuid } from "builderStore/uuid" import { uuid } from "builderStore/uuid"
const allTemplates = tables => [ const allTemplates = tables => [
@ -16,13 +15,21 @@ const allTemplates = tables => [
emptyRowDetailScreen, emptyRowDetailScreen,
] ]
// allows us to apply common behaviour to all create() functions // Recurses through a component tree and generates new unique ID's
const makeUniqueIds = component => {
if (!component) {
return
}
component._id = uuid()
if (component._children) {
component._children.forEach(makeUniqueIds)
}
}
// Allows us to apply common behaviour to all create() functions
const createTemplateOverride = (frontendState, create) => () => { const createTemplateOverride = (frontendState, create) => () => {
const screen = create() const screen = create()
for (let component of screen.props._children) { makeUniqueIds(screen.props)
generateNewIdsForComponent(component, frontendState, false)
}
screen.props._id = uuid()
screen.name = screen.props._id screen.name = screen.props._id
screen.routing.route = screen.routing.route.toLowerCase() screen.routing.route = screen.routing.route.toLowerCase()
return screen return screen

View File

@ -1,80 +1,82 @@
import { getBuiltin } from "components/userInterface/assetParsing/createProps" /**
import { uuid } from "./uuid" * Recursively searches for a specific component ID
import getNewComponentName from "./getNewComponentName" */
export const findComponent = (rootComponent, id) => {
return searchComponentTree(rootComponent, comp => comp._id === id)
}
/** /**
* Find the parent component of the passed in child. * Recursively searches for a specific component type
* @param {Object} rootProps - props to search for the parent in
* @param {String|Object} child - id of the child or the child itself to find the parent of
*/ */
export const findParent = (rootProps, child) => { export const findComponentType = (rootComponent, type) => {
let parent return searchComponentTree(rootComponent, comp => comp._component === type)
walkProps(rootProps, (props, breakWalk) => {
if (
props._children &&
(props._children.includes(child) ||
props._children.some(c => c._id === child))
) {
parent = props
breakWalk()
}
})
return parent
} }
export const walkProps = (props, action, cancelToken = null) => { /**
cancelToken = cancelToken || { cancelled: false } * Recursively searches for the parent component of a specific component ID
action(props, () => { */
cancelToken.cancelled = true export const findComponentParent = (rootComponent, id, parentComponent) => {
}) if (!rootComponent || !id) {
if (props._children) {
for (let child of props._children) {
if (cancelToken.cancelled) return
walkProps(child, action, cancelToken)
}
}
}
export const generateNewIdsForComponent = (
component,
state,
changeName = true
) =>
walkProps(component, prop => {
prop._id = uuid()
if (changeName) prop._instanceName = getNewComponentName(prop, state)
})
export const getComponentDefinition = (state, name) =>
name.startsWith("##") ? getBuiltin(name) : state.components[name]
export const findChildComponentType = (node, typeToFind) => {
// Stop recursion if invalid props
if (!node || !typeToFind) {
return null return null
} }
if (rootComponent._id === id) {
// Stop recursion if this element matches return parentComponent
if (node._component === typeToFind) {
return node
} }
if (!rootComponent._children) {
// Otherwise check if any children match
// Stop recursion if no valid children to process
const children = node._children || (node.props && node.props._children)
if (!children || !children.length) {
return null return null
} }
for (const child of rootComponent._children) {
// Recurse and check each child component const childResult = findComponentParent(child, id, rootComponent)
for (let child of children) { if (childResult) {
const childResult = findChildComponentType(child, typeToFind) return childResult
}
}
return null
}
/**
* Recursively searches for a specific component ID and records the component
* path to this component
*/
export const findComponentPath = (rootComponent, id, path = []) => {
if (!rootComponent || !id) {
return null
}
if (rootComponent._id === id) {
return [...path, id]
}
if (!rootComponent._children) {
return null
}
for (const child of rootComponent._children) {
const newPath = [...path, rootComponent._id]
const childResult = findComponentPath(child, id, newPath)
if (childResult != null) {
return childResult
}
}
return null
}
/**
* Recurses through a component tree evaluating a matching function against
* components until a match is found
*/
const searchComponentTree = (rootComponent, matchComponent) => {
if (!rootComponent || !matchComponent) {
return null
}
if (matchComponent(rootComponent)) {
return rootComponent
}
if (!rootComponent._children) {
return null
}
for (const child of rootComponent._children) {
const childResult = searchComponentTree(child, matchComponent)
if (childResult) { if (childResult) {
return childResult return childResult
} }
} }
// If we reach here then no children were valid
return null return null
} }

View File

@ -14,7 +14,7 @@
const screenPlaceholder = new Screen() const screenPlaceholder = new Screen()
.name("Screen Placeholder") .name("Screen Placeholder")
.route("*") .route("*")
.component("@budibase/standard-components/screenslotplaceholder") .component("@budibase/standard-components/screenslot")
.instanceName("Content Placeholder") .instanceName("Content Placeholder")
.json() .json()

View File

@ -2,10 +2,9 @@
import { goto } from "@sveltech/routify" import { goto } from "@sveltech/routify"
import { get } from "svelte/store" import { get } from "svelte/store"
import { store, currentAsset } from "builderStore" import { store, currentAsset } from "builderStore"
import { getComponentDefinition } from "builderStore/storeUtils"
import ConfirmDialog from "components/common/ConfirmDialog.svelte" import ConfirmDialog from "components/common/ConfirmDialog.svelte"
import { last } from "lodash/fp" import { last } from "lodash/fp"
import { findParent } from "builderStore/storeUtils" import { findComponentParent } from "builderStore/storeUtils"
import { DropdownMenu } from "@budibase/bbui" import { DropdownMenu } from "@budibase/bbui"
import { DropdownContainer, DropdownItem } from "components/common/Dropdowns" import { DropdownContainer, DropdownItem } from "components/common/Dropdowns"
@ -17,7 +16,7 @@
$: noChildrenAllowed = $: noChildrenAllowed =
!component || !component ||
!getComponentDefinition($store, component._component)?.children !store.actions.components.getDefinition(component._component)?.hasChildren
$: noPaste = !$store.componentToPaste $: noPaste = !$store.componentToPaste
const lastPartOfName = c => (c ? last(c._component.split("/")) : "") const lastPartOfName = c => (c ? last(c._component.split("/")) : "")
@ -35,7 +34,7 @@
const moveUpComponent = () => { const moveUpComponent = () => {
store.update(state => { store.update(state => {
const asset = get(currentAsset) const asset = get(currentAsset)
const parent = findParent(asset.props, component) const parent = findComponentParent(asset.props, component)
if (parent) { if (parent) {
const currentIndex = parent._children.indexOf(component) const currentIndex = parent._children.indexOf(component)
@ -55,7 +54,7 @@
const moveDownComponent = () => { const moveDownComponent = () => {
store.update(state => { store.update(state => {
const asset = get(currentAsset) const asset = get(currentAsset)
const parent = findParent(asset.props, component) const parent = findComponentParent(asset.props, component)
if (parent) { if (parent) {
const currentIndex = parent._children.indexOf(component) const currentIndex = parent._children.indexOf(component)
@ -78,18 +77,7 @@
} }
const deleteComponent = () => { const deleteComponent = () => {
store.update(state => { store.actions.components.delete(component)
const asset = get(currentAsset)
const parent = findParent(asset.props, component)
if (parent) {
parent._children = parent._children.filter(child => child !== component)
selectComponent(parent)
}
store.actions.preview.saveSelected()
return state
})
} }
const storeComponentForCopy = (cut = false) => { const storeComponentForCopy = (cut = false) => {

View File

@ -1,7 +1,6 @@
<script> <script>
import { goto } from "@sveltech/routify" import { goto } from "@sveltech/routify"
import { store, currentAssetId } from "builderStore" import { store, currentAssetId } from "builderStore"
import { getComponentDefinition } from "builderStore/storeUtils"
import { DropEffect, DropPosition } from "./dragDropStore" import { DropEffect, DropPosition } from "./dragDropStore"
import ComponentDropdownMenu from "../ComponentDropdownMenu.svelte" import ComponentDropdownMenu from "../ComponentDropdownMenu.svelte"
import NavItem from "components/common/NavItem.svelte" import NavItem from "components/common/NavItem.svelte"
@ -12,16 +11,11 @@
export let level = 0 export let level = 0
export let dragDropStore export let dragDropStore
const isScreenslot = name => name === "##builtin/screenslot" const isScreenslot = name => name?.endsWith("screenslot")
const selectComponent = component => { const selectComponent = component => {
// Set current component
store.actions.components.select(component) store.actions.components.select(component)
// Get ID path
const path = store.actions.components.findRoute(component) const path = store.actions.components.findRoute(component)
// Go to correct URL
$goto(`./${$currentAssetId}/${path}`) $goto(`./${$currentAssetId}/${path}`)
} }
@ -31,9 +25,11 @@
} }
const dragover = (component, index) => e => { const dragover = (component, index) => e => {
const definition = store.actions.components.getDefinition(
component._component
)
const canHaveChildrenButIsEmpty = const canHaveChildrenButIsEmpty =
getComponentDefinition($store, component._component).children && definition?.hasChildren && !component._children?.length
component._children.length === 0
e.dataTransfer.dropEffect = DropEffect.COPY e.dataTransfer.dropEffect = DropEffect.COPY

View File

@ -1,5 +1,6 @@
import { writable } from "svelte/store" import { writable, get } from "svelte/store"
import { store as frontendStore } from "builderStore" import { store as frontendStore } from "builderStore"
import { findComponentPath } from "builderStore/storeUtils"
export const DropEffect = { export const DropEffect = {
MOVE: "move", MOVE: "move",
@ -72,19 +73,32 @@ export default function() {
}) })
}, },
drop: () => { drop: () => {
store.update(state => { const state = get(store)
if (state.targetComponent !== state.dragged) {
frontendStore.actions.components.copy(state.dragged, true)
frontendStore.actions.components.paste(
state.targetComponent,
state.dropPosition
)
}
store.actions.reset() // Stop if the target and source are the same
if (state.targetComponent === state.dragged) {
console.log("same component")
return
}
// Stop if the target or source are null
if (!state.targetComponent || !state.dragged) {
console.log("null component")
return
}
// Stop if the target is a child of source
const path = findComponentPath(state.dragged, state.targetComponent._id)
if (path?.includes(state.targetComponent._id)) {
console.log("target is child of course")
return
}
return state // Cut and paste the component
}) frontendStore.actions.components.copy(state.dragged, true)
frontendStore.actions.components.paste(
state.targetComponent,
state.dropPosition
)
store.actions.reset()
}, },
} }

View File

@ -19,7 +19,9 @@
$store.currentView !== "component" $store.currentView !== "component"
? { ...$currentAsset, ...$selectedComponent } ? { ...$currentAsset, ...$selectedComponent }
: $selectedComponent : $selectedComponent
$: componentDefinition = $store.components[componentInstance._component] $: componentDefinition = store.actions.components.getDefinition(
componentInstance._component
)
$: componentPropDefinition = $: componentPropDefinition =
flattenedPanel.find( flattenedPanel.find(
// use for getting controls for each component property // use for getting controls for each component property
@ -37,7 +39,7 @@
$: isComponentOrScreen = $: isComponentOrScreen =
$store.currentView === "component" || $store.currentView === "component" ||
$store.currentFrontEndType === FrontendTypes.SCREEN $store.currentFrontEndType === FrontendTypes.SCREEN
$: isNotScreenslot = componentInstance._component !== "##builtin/screenslot" $: isNotScreenslot = !componentInstance._component.endsWith("screenslot")
$: displayName = $: displayName =
isComponentOrScreen && componentInstance._instanceName && isNotScreenslot isComponentOrScreen && componentInstance._instanceName && isNotScreenslot

View File

@ -6,47 +6,66 @@
selectedComponent, selectedComponent,
currentAssetId, currentAssetId,
} from "builderStore" } from "builderStore"
import components from "./temporaryPanelStructure.js" import structure from "./componentStructure.json"
import { DropdownMenu } from "@budibase/bbui" import { DropdownMenu } from "@budibase/bbui"
import { DropdownContainer, DropdownItem } from "components/common/Dropdowns" import { DropdownContainer, DropdownItem } from "components/common/Dropdowns"
const categories = components.categories $: enrichedStructure = enrichStructure(structure, $store.components)
let selectedIndex let selectedIndex
let anchors = [] let anchors = []
let popover let popover
$: anchor = selectedIndex === -1 ? null : anchors[selectedIndex] $: anchor = selectedIndex === -1 ? null : anchors[selectedIndex]
const close = () => { const enrichStructure = (structure, definitions) => {
popover.hide() let enrichedStructure = []
structure.forEach(item => {
if (typeof item === "string") {
const def = definitions[`@budibase/standard-components/${item}`]
if (def) {
enrichedStructure.push({
...def,
isCategory: false,
})
}
} else {
enrichedStructure.push({
...item,
isCategory: true,
children: enrichStructure(item.children || [], definitions),
})
}
})
return enrichedStructure
} }
const onCategoryChosen = (category, idx) => { const onItemChosen = (item, idx) => {
if (category.isCategory) { if (item.isCategory) {
// Select and open this category
selectedIndex = idx selectedIndex = idx
popover.show() popover.show()
} else { } else {
onComponentChosen(category) // Add this component
const newComponent = store.actions.components.create(item.component)
if (newComponent) {
const path = store.actions.components.findRoute(newComponent)
$goto(`./${$currentAssetId}/${path}`)
}
popover.hide()
} }
} }
const onComponentChosen = component => {
store.actions.components.create(component._component, component.presetProps)
const path = store.actions.components.findRoute($selectedComponent)
$goto(`./${$currentAssetId}/${path}`)
close()
}
</script> </script>
<div class="container"> <div class="container">
{#each categories as category, idx} {#each enrichedStructure as item, idx}
<div <div
bind:this={anchors[idx]} bind:this={anchors[idx]}
class="category" class="category"
on:click={() => onCategoryChosen(category, idx)} on:click={() => onItemChosen(item, idx)}
class:active={idx === selectedIndex}> class:active={idx === selectedIndex}>
{#if category.icon}<i class={category.icon} />{/if} {#if item.icon}<i class={item.icon} />{/if}
<span>{category.name}</span> <span>{item.name}</span>
{#if category.isCategory}<i class="ri-arrow-down-s-line arrow" />{/if} {#if item.isCategory}<i class="ri-arrow-down-s-line arrow" />{/if}
</div> </div>
{/each} {/each}
</div> </div>
@ -56,12 +75,12 @@
{anchor} {anchor}
align="left"> align="left">
<DropdownContainer> <DropdownContainer>
{#each categories[selectedIndex].children as item} {#each enrichedStructure[selectedIndex].children as item}
{#if !item.showOnAsset || item.showOnAsset.includes($currentAssetName)} {#if !item.showOnAsset || item.showOnAsset.includes($currentAssetName)}
<DropdownItem <DropdownItem
icon={item.icon} icon={item.icon}
title={item.name} title={item.name}
on:click={() => onComponentChosen(item)} /> on:click={() => onItemChosen(item)} />
{/if} {/if}
{/each} {/each}
</DropdownContainer> </DropdownContainer>

View File

@ -1,14 +1,11 @@
<script> <script>
import { get } from "lodash" import { get } from "lodash"
import { isEmpty } from "lodash/fp" import { isEmpty } from "lodash/fp"
import { FrontendTypes } from "constants"
import PropertyControl from "./PropertyControl.svelte" import PropertyControl from "./PropertyControl.svelte"
import LayoutSelect from "./LayoutSelect.svelte" import LayoutSelect from "./LayoutSelect.svelte"
import RoleSelect from "./RoleSelect.svelte" import RoleSelect from "./RoleSelect.svelte"
import Input from "./PropertyPanelControls/Input.svelte" import Input from "./PropertyPanelControls/Input.svelte"
import { excludeProps } from "./propertyCategories.js" import { excludeProps } from "./propertyCategories.js"
import { store, allScreens, currentAsset } from "builderStore"
import { walkProps } from "builderStore/storeUtils"
export let panelDefinition = [] export let panelDefinition = []
export let componentDefinition = {} export let componentDefinition = {}
@ -28,11 +25,7 @@
let duplicateName = false let duplicateName = false
const propExistsOnComponentDef = prop => const propExistsOnComponentDef = prop =>
assetProps.includes(prop) || prop in componentDefinition.props assetProps.includes(prop) || prop in (componentDefinition?.props ?? {})
function handleChange(key, data) {
data.target ? onChange(key, data.target.value) : onChange(key, data)
}
const screenDefinition = [ const screenDefinition = [
{ key: "description", label: "Description", control: Input }, { key: "description", label: "Description", control: Input },
@ -44,8 +37,6 @@
const layoutDefinition = [] const layoutDefinition = []
const canRenderControl = (key, dependsOn) => { const canRenderControl = (key, dependsOn) => {
let test = !isEmpty(componentInstance[dependsOn])
return ( return (
propExistsOnComponentDef(key) && propExistsOnComponentDef(key) &&
(!dependsOn || !isEmpty(componentInstance[dependsOn])) (!dependsOn || !isEmpty(componentInstance[dependsOn]))
@ -55,41 +46,8 @@
$: isLayout = assetInstance && assetInstance.favicon $: isLayout = assetInstance && assetInstance.favicon
$: assetDefinition = isLayout ? layoutDefinition : screenDefinition $: assetDefinition = isLayout ? layoutDefinition : screenDefinition
const isDuplicateName = name => {
let duplicate = false
const lookForDuplicate = rootProps => {
walkProps(rootProps, (inst, cancel) => {
if (inst._instanceName === name && inst._id !== componentInstance._id) {
duplicate = true
cancel()
}
})
}
// check against layouts
for (let layout of $store.layouts) {
lookForDuplicate(layout.props)
}
// if viewing screen, check current screen for duplicate
if ($store.currentFrontEndType === FrontendTypes.SCREEN) {
lookForDuplicate($currentAsset.props)
} else {
// need to dedupe against all screens
for (let screen of $allScreens) {
lookForDuplicate(screen.props)
}
}
return duplicate
}
const onInstanceNameChange = (_, name) => { const onInstanceNameChange = (_, name) => {
if (isDuplicateName(name)) { onChange("_instanceName", name)
duplicateName = true
} else {
duplicateName = false
onChange("_instanceName", name)
}
} }
</script> </script>

View File

@ -1,97 +0,0 @@
import { isString, isUndefined, cloneDeep } from "lodash/fp"
import { TYPE_MAP } from "./types"
import { assign } from "lodash"
import { uuid } from "builderStore/uuid"
export const getBuiltin = _component => {
const { props } = createProps({ _component })
return {
_component,
name: "Screenslot",
props,
}
}
/**
* @param {object} componentDefinition - component definition from a component library
* @param {object} derivedFromProps - extra props derived from a components given props.
* @return {object} the fully created properties for the component, and any property parsing errors
*/
export const createProps = (componentDefinition, derivedFromProps) => {
const errorOccurred = (propName, error) => errors.push({ propName, error })
const props = {
_id: uuid(),
_component: componentDefinition._component,
_styles: { normal: {}, hover: {}, active: {} },
}
const errors = []
if (!componentDefinition._component) {
errorOccurred("_component", "Component name not supplied")
}
for (let propName in componentDefinition.props) {
const parsedPropDef = parsePropDef(componentDefinition.props[propName])
if (parsedPropDef.error) {
errors.push({ propName, error: parsedPropDef.error })
} else {
props[propName] = parsedPropDef
}
}
if (derivedFromProps) {
assign(props, derivedFromProps)
}
if (isUndefined(props._children)) {
props._children = []
}
return {
props,
errors,
}
}
export const makePropsSafe = (componentDefinition, props) => {
if (!componentDefinition) {
console.error(
"No component definition passed to makePropsSafe. Please check the component definition is being passed correctly."
)
}
const safeProps = createProps(componentDefinition, props).props
for (let propName in safeProps) {
props[propName] = safeProps[propName]
}
for (let propName in props) {
if (safeProps[propName] === undefined) {
delete props[propName]
}
}
if (!props._styles) {
props._styles = { normal: {}, hover: {}, active: {} }
}
return props
}
const parsePropDef = propDef => {
const error = message => ({ error: message, propDef })
if (isString(propDef)) {
if (!TYPE_MAP[propDef]) return error(`Type ${propDef} is not recognised.`)
return cloneDeep(TYPE_MAP[propDef].default)
}
const type = TYPE_MAP[propDef.type]
if (!type) return error(`Type ${propDef.type} is not recognised.`)
return cloneDeep(propDef.default)
}

View File

@ -0,0 +1,11 @@
[
{
"name": "Category",
"icon": "ri-file-edit-line",
"children": [
"container"
]
},
"grid",
"screenslot"
]

View File

@ -20,93 +20,6 @@ import { all } from "./propertyCategories.js"
export default { export default {
categories: [ categories: [
{
_component: "@budibase/standard-components/container",
name: "Container",
description: "This component contains things within itself",
icon: "ri-layout-column-line",
commonProps: {},
children: [],
properties: {
design: { ...all },
settings: [
{
key: "type",
label: "Type",
control: OptionSelect,
options: [
"article",
"aside",
"details",
"div",
"figure",
"figcaption",
"footer",
"header",
"main",
"mark",
"nav",
"paragraph",
"summary",
],
},
],
},
},
{
name: "Grid",
_component: "@budibase/standard-components/datagrid",
description:
"a datagrid component with functionality to add, remove and edit rows.",
icon: "ri-grid-line",
properties: {
design: { ...all },
settings: [
{
label: "Source",
key: "datasource",
control: TableViewSelect,
},
{
label: "Detail URL",
key: "detailUrl",
control: DetailScreenSelect,
},
{
label: "Editable",
key: "editable",
valueKey: "checked",
control: Checkbox,
},
{
label: "Theme",
key: "theme",
control: OptionSelect,
options: [
"alpine",
"alpine-dark",
"balham",
"balham-dark",
"material",
],
placeholder: "alpine",
},
{
label: "Height",
key: "height",
defaultValue: "500",
control: Input,
},
{
label: "Pagination",
key: "pagination",
valueKey: "checked",
control: Checkbox,
},
],
},
children: [],
},
{ {
name: "Repeater", name: "Repeater",
_component: "@budibase/standard-components/list", _component: "@budibase/standard-components/list",
@ -152,8 +65,8 @@ export default {
isCategory: true, isCategory: true,
children: [ children: [
{ {
_component: "@budibase/standard-components/dataform", _component: "@budibase/standard-components/form",
name: "Form Basic", name: "Form",
icon: "ri-file-edit-line", icon: "ri-file-edit-line",
properties: { properties: {
design: { ...all }, design: { ...all },
@ -161,17 +74,8 @@ export default {
}, },
}, },
{ {
_component: "@budibase/standard-components/dataformwide", _component: "@budibase/standard-components/textfield",
name: "Form Wide", name: "Text Field",
icon: "ri-file-edit-line",
properties: {
design: { ...all },
settings: [],
},
},
{
_component: "@budibase/standard-components/input",
name: "Textfield",
description: description:
"A textfield component that allows the user to input text.", "A textfield component that allows the user to input text.",
icon: "ri-edit-box-line", icon: "ri-edit-box-line",
@ -199,19 +103,19 @@ export default {
// settings: [], // settings: [],
// }, // },
// }, // },
{ // {
_component: "@budibase/standard-components/datepicker", // _component: "@budibase/standard-components/datepicker",
name: "Date Picker", // name: "Date Picker",
description: "A basic date picker component", // description: "A basic date picker component",
icon: "ri-calendar-line", // icon: "ri-calendar-line",
children: [], // children: [],
properties: { // properties: {
design: { ...all }, // design: { ...all },
settings: [ // settings: [
{ label: "Placeholder", key: "placeholder", control: Input }, // { label: "Placeholder", key: "placeholder", control: Input },
], // ],
}, // },
}, // },
], ],
}, },
{ {

View File

@ -35,7 +35,10 @@
const getComponentConstructor = component => { const getComponentConstructor = component => {
const split = component?.split("/") const split = component?.split("/")
const name = split?.[split.length - 1] const name = split?.[split.length - 1]
return name === "screenslot" ? Router : ComponentLibrary[name] if (name === "screenslot" && $builderStore.previewType !== "layout") {
return Router
}
return ComponentLibrary[name]
} }
// Returns a unique key to let svelte know when to remount components. // Returns a unique key to let svelte know when to remount components.

View File

@ -21,17 +21,18 @@ exports.fetchAppComponentDefinitions = async function(ctx) {
appDirectory, appDirectory,
componentLibrary, componentLibrary,
ctx.isDev ? "" : "package", ctx.isDev ? "" : "package",
"components.json" "manifest.json"
)) ))
console.log(componentJson)
const result = {} const result = {}
// map over the components.json and add the library identifier as a key // map over the components.json and add the library identifier as a key
// button -> @budibase/standard-components/button // button -> @budibase/standard-components/button
for (let key of Object.keys(componentJson)) { for (let key of Object.keys(componentJson)) {
const fullComponentName = `${componentLibrary}/${key}` const fullComponentName = `${componentLibrary}/${key}`.toLowerCase()
result[fullComponentName] = { result[fullComponentName] = {
_component: fullComponentName, component: fullComponentName,
...componentJson[key], ...componentJson[key],
} }
} }

View File

@ -14,7 +14,7 @@ const EMPTY_LAYOUT = {
_children: [ _children: [
{ {
_id: "7fcf11e4-6f5b-4085-8e0d-9f3d44c98967", _id: "7fcf11e4-6f5b-4085-8e0d-9f3d44c98967",
_component: "##builtin/screenslot", _component: "@budibase/standard-components/screenslot",
_styles: { _styles: {
normal: { normal: {
flex: "1 1 auto", flex: "1 1 auto",
@ -151,7 +151,7 @@ const BASE_LAYOUTS = [
}, },
{ {
_id: "7fcf11e4-6f5b-4085-8e0d-9f3d44c98967", _id: "7fcf11e4-6f5b-4085-8e0d-9f3d44c98967",
_component: "##builtin/screenslot", _component: "@budibase/standard-components/screenslot",
_styles: { _styles: {
normal: { normal: {
flex: "1 1 auto", flex: "1 1 auto",
@ -206,7 +206,7 @@ const BASE_LAYOUTS = [
_children: [ _children: [
{ {
_id: "7fcf11e4-6f5b-4085-8e0d-9f3d44c98967", _id: "7fcf11e4-6f5b-4085-8e0d-9f3d44c98967",
_component: "##builtin/screenslot", _component: "@budibase/standard-components/screenslot",
_styles: { _styles: {
normal: { normal: {
flex: "1 1 auto", flex: "1 1 auto",

View File

@ -1,33 +1,43 @@
*Psst — looking for an app template? Go here --> [sveltejs/template](https://github.com/sveltejs/template)* ## Manifest
--- The `manifest.json` file exports the definitions of all components available in this version
of the client library. The manifest is used by the builder to correctly display components and
# component-template their settings, and know how to correctly interact with them.
A base for building shareable Svelte components. Clone it with [degit](https://github.com/Rich-Harris/degit):
```bash
npx degit sveltejs/component-template my-new-component
cd my-new-component
npm install # or yarn
```
Your component's source code lives in `src/index.html`.
TODO
* [ ] some firm opinions about the best way to test components
* [ ] update `degit` so that it automates some of the setup work
## Setting up
* Run `npm init` (or `yarn init`) ### Component Definitions
* Replace this README with your own
The object key is the name of the component, as exported by `index.js`.
- **name** - the name displayed in the builder
- **description** - not currently used
- **icon** - the icon displayed in the builder
- **hasChildren** - whether the component accepts children or not
- **styleable** - whether the component accepts design props or not
- **dataProvider** - whether the component provides a data context or not
- **bindable** - whether the components provides a bindable value or not
- **settings** - array of settings displayed in the builder
###Settings Definitions
The `type` field in each setting is used by the builder to know which component to use to display
the setting, so it's important that this field is correct. The valid options are:
- **text** - A text field
- **select** - A select dropdown. Accompany these with an `options` field to provide options
- **datasource** - A datasource (e.g. a table or a view)
- **boolean** - A boolean field
- **number** - A numeric text field
- **detailURL** - A URL to a page which displays details about a row.
Exclusively used for grids which link to row details.
## Consuming components The available fields in each setting definition are:
Your package.json has a `"svelte"` field pointing to `src/index.html`, which allows Svelte apps to import the source code directly, if they are using a bundler plugin like [rollup-plugin-svelte](https://github.com/rollup/rollup-plugin-svelte) or [svelte-loader](https://github.com/sveltejs/svelte-loader) (where [`resolve.mainFields`](https://webpack.js.org/configuration/resolve/#resolve-mainfields) in your webpack config includes `"svelte"`). **This is recommended.** - **type** - the type of field which determines which component the builder will use
to display the setting
For everyone else, `npm run build` will bundle your component's source code into a plain JavaScript module (`index.mjs`) and a UMD script (`index.js`). This will happen automatically when you publish your component to npm, courtesy of the `prepublishOnly` hook in package.json. - **key** - the key of this setting in the component
- **label** - the label displayed in the builder
- **defaultValue** - the default value of the setting
- **placeholder** - the placeholder for the setting

View File

@ -141,35 +141,7 @@
} }
} }
}, },
"datagrid": {
"name": "Grid",
"description": "a datagrid component with functionality to add, remove and edit rows.",
"data": true,
"props": {
"datasource": "tables",
"editable": "bool",
"theme": {
"type": "options",
"default": "alpine",
"options": [
"alpine",
"alpine-dark",
"balham",
"balham-dark",
"material"
]
},
"height": {
"type": "number",
"default": "540"
},
"pagination": {
"type": "bool",
"default": true
},
"detailUrl": "string"
}
},
"dataform": { "dataform": {
"name": "Form", "name": "Form",
"description": "an HTML table that fetches data from a table or view and displays it.", "description": "an HTML table that fetches data from a table or view and displays it.",
@ -623,38 +595,7 @@
"url": "string" "url": "string"
} }
}, },
"container": {
"name": "Container",
"children": true,
"description": "An element that contains and lays out other elements. e.g. <div>, <header> etc",
"props": {
"type": {
"type": "options",
"options": [
"article",
"aside",
"details",
"div",
"firgure",
"figcaption",
"footer",
"header",
"main",
"mark",
"nav",
"paragraph",
"summary"
],
"default": "div"
}
},
"baseComponent": true,
"tags": [
"div",
"container",
"layout"
]
},
"heading": { "heading": {
"name": "Heading", "name": "Heading",
"description": "An HTML H1 - H6 tag", "description": "An HTML H1 - H6 tag",

View File

@ -0,0 +1,79 @@
{
"container": {
"name": "Container",
"description": "This component contains things within itself",
"icon": "ri-layout-column-line",
"hasChildren": true,
"styleable": true,
"settings": [
{
"type": "select",
"key": "type",
"label": "Type",
"defaultValue": "div",
"options": [
"article",
"aside",
"details",
"div",
"figure",
"figcaption",
"footer",
"header",
"main",
"mark",
"nav",
"paragraph",
"summary"
]
}
]
},
"grid": {
"name": "Grid",
"description":
"A datagrid component with functionality to add, remove and edit rows.",
"icon": "ri-grid-line",
"styleable": true,
"settings": [
{
"type": "datasource",
"label": "Source",
"key": "datasource"
},
{
"type": "detailURL",
"label": "Detail URL",
"key": "detailUrl"
},
{
"type": "boolean",
"label": "Editable",
"key": "editable"
},
{
"type": "select",
"label": "Theme",
"key": "theme",
"options": ["alpine", "alpine-dark", "balham", "balham-dark", "material"],
"defaultValue": "alpine"
},
{
"type": "number",
"label": "Height",
"key": "height",
"defaultValue": "500"
},
{
"type": "boolean",
"label": "Pagination",
"key": "pagination"
}
]
},
"screenslot": {
"name": "Screenslot",
"description": "Contains your app screens",
"styleable": true
}
}

View File

@ -36,6 +36,11 @@
"@budibase/bbui": "^1.52.4", "@budibase/bbui": "^1.52.4",
"@budibase/svelte-ag-grid": "^0.0.16", "@budibase/svelte-ag-grid": "^0.0.16",
"@fortawesome/fontawesome-free": "^5.14.0", "@fortawesome/fontawesome-free": "^5.14.0",
"@spectrum-css/button": "^3.0.0-beta.6",
"@spectrum-css/icon": "^3.0.0-beta.2",
"@spectrum-css/page": "^3.0.0-beta.0",
"@spectrum-css/typography": "^3.0.0-beta.1",
"@spectrum-css/vars": "^3.0.0-beta.2",
"apexcharts": "^3.22.1", "apexcharts": "^3.22.1",
"flatpickr": "^4.6.6", "flatpickr": "^4.6.6",
"lodash.debounce": "^4.0.8", "lodash.debounce": "^4.0.8",

View File

@ -1,3 +1,8 @@
<script>
// This component is overridden when running in a real app.
// This simply serves as a placeholder component for the real screen router.
</script>
<div> <div>
<h1>Screen Slot</h1> <h1>Screen Slot</h1>
<span> <span>

View File

@ -1,28 +1,31 @@
import "@budibase/bbui/dist/bbui.css" import "@budibase/bbui/dist/bbui.css"
import "flatpickr/dist/flatpickr.css" import "flatpickr/dist/flatpickr.css"
import "@spectrum-css/vars/dist/spectrum-global.css"
import "@spectrum-css/vars/dist/spectrum-medium.css"
import "@spectrum-css/vars/dist/spectrum-light.css"
export { default as container } from "./Container.svelte" export { default as container } from "./Container.svelte"
export { default as text } from "./Text.svelte" export { default as grid } from "./grid/Component.svelte"
export { default as heading } from "./Heading.svelte" export { default as screenslot } from "./ScreenSlot.svelte"
export { default as input } from "./Input.svelte"
export { default as richtext } from "./RichText.svelte" // export { default as text } from "./Text.svelte"
export { default as button } from "./Button.svelte" // export { default as heading } from "./Heading.svelte"
export { default as login } from "./Login.svelte" // export { default as input } from "./Input.svelte"
export { default as link } from "./Link.svelte" // export { default as richtext } from "./RichText.svelte"
export { default as image } from "./Image.svelte" // export { default as button } from "./Button.svelte"
export { default as navigation } from "./Navigation.svelte" // export { default as login } from "./Login.svelte"
export { default as datagrid } from "./grid/Component.svelte" // export { default as link } from "./Link.svelte"
export { default as dataform } from "./DataForm.svelte" // export { default as image } from "./Image.svelte"
export { default as dataformwide } from "./DataFormWide.svelte" // export { default as navigation } from "./Navigation.svelte"
export { default as list } from "./List.svelte" // export { default as list } from "./List.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 card } from "./Card.svelte"
export { default as cardhorizontal } from "./CardHorizontal.svelte" // export { default as cardhorizontal } from "./CardHorizontal.svelte"
export { default as cardstat } from "./CardStat.svelte" // export { default as cardstat } from "./CardStat.svelte"
export { default as rowdetail } from "./RowDetail.svelte" // export { default as rowdetail } from "./RowDetail.svelte"
export { default as newrow } from "./NewRow.svelte" // export { default as newrow } from "./NewRow.svelte"
export { default as datepicker } from "./DatePicker.svelte" // export { default as datepicker } from "./DatePicker.svelte"
export { default as icon } from "./Icon.svelte" // export { default as icon } from "./Icon.svelte"
export { default as screenslotplaceholder } from "./ScreenSlotPlaceholder.svelte" // export * from "./charts"
export * from "./charts"

View File

@ -132,6 +132,33 @@
estree-walker "^1.0.1" estree-walker "^1.0.1"
picomatch "^2.2.2" picomatch "^2.2.2"
"@spectrum-css/button@^3.0.0-beta.6":
version "3.0.0-beta.6"
resolved "https://registry.yarnpkg.com/@spectrum-css/button/-/button-3.0.0-beta.6.tgz#007919d3e7a6692e506dc9addcd46aee6b203b1a"
integrity sha512-ZoJxezt5Pc006RR7SMG7PfC0VAdWqaGDpd21N8SEykGuz/KmNulqGW8RiSZQGMVX/jk5ZCAthPrH8cI/qtKbMg==
"@spectrum-css/icon@^3.0.0-beta.2":
version "3.0.0-beta.2"
resolved "https://registry.yarnpkg.com/@spectrum-css/icon/-/icon-3.0.0-beta.2.tgz#2dd7258ded74501b56e5fc42d0b6f0a3f4936aeb"
integrity sha512-BEHJ68YIXSwsNAqTdq/FrS4A+jtbKzqYrsGKXdDf93ql+fHWYXRCh1EVYGHx/1696mY73DhM4snMpKGIFtXGFA==
"@spectrum-css/page@^3.0.0-beta.0":
version "3.0.0-beta.0"
resolved "https://registry.yarnpkg.com/@spectrum-css/page/-/page-3.0.0-beta.0.tgz#885ea41b44861c5dc3aac904536f9e93c9109b58"
integrity sha512-+OD+l3aLisykxJnHfLkdkxMS1Uj1vKGYpKil7W0r5lSWU44eHyRgb8ZK5Vri1+sUO5SSf/CTybeVwtXME9wMLA==
dependencies:
"@spectrum-css/vars" "^3.0.0-beta.2"
"@spectrum-css/typography@^3.0.0-beta.1":
version "3.0.0-beta.1"
resolved "https://registry.yarnpkg.com/@spectrum-css/typography/-/typography-3.0.0-beta.1.tgz#c2c2097c49e2711e8d048afcbaa5ccfe1a6ea7f1"
integrity sha512-NnRvEnrTdt53ZUYh42v+ff6bUTUjG1qHctVJrIv8XrivFSc4L475x3lJgHmSVQeDoDLAsVOtISlJBXKrqK5eRQ==
"@spectrum-css/vars@^3.0.0-beta.2":
version "3.0.0-beta.2"
resolved "https://registry.yarnpkg.com/@spectrum-css/vars/-/vars-3.0.0-beta.2.tgz#f0b3a2db44aa57b1a82e47ab392c716a3056a157"
integrity sha512-HpcRDUkSjKVWUi7+jf6zp33YszXs3qFljaaNVTVOf0m0mqjWWXHxgLrvYlFFlHp5ITbNXds5Cb7EgiXCKmVIpA==
"@types/color-name@^1.1.1": "@types/color-name@^1.1.1":
version "1.1.1" version "1.1.1"
resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0" resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0"