WIP component management and definition refactor
This commit is contained in:
parent
9619240804
commit
2dc2e43a00
|
@ -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
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
|
|
||||||
|
|
|
@ -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) => {
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "Category",
|
||||||
|
"icon": "ri-file-edit-line",
|
||||||
|
"children": [
|
||||||
|
"container"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"grid",
|
||||||
|
"screenslot"
|
||||||
|
]
|
|
@ -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 },
|
||||||
],
|
// ],
|
||||||
},
|
// },
|
||||||
},
|
// },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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",
|
||||||
|
|
|
@ -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>
|
|
@ -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"
|
|
||||||
|
|
|
@ -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"
|
||||||
|
|
Loading…
Reference in New Issue