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