2020-11-06 13:30:30 +01:00
|
|
|
import { get, writable } from "svelte/store"
|
2020-11-06 13:31:47 +01:00
|
|
|
import { cloneDeep } from "lodash/fp"
|
2020-12-01 17:22:06 +01:00
|
|
|
import {
|
|
|
|
allScreens,
|
|
|
|
currentAsset,
|
|
|
|
mainLayout,
|
2020-12-07 16:27:46 +01:00
|
|
|
selectedComponent,
|
2020-12-09 20:04:46 +01:00
|
|
|
selectedAccessRole,
|
2020-12-01 17:22:06 +01:00
|
|
|
} from "builderStore"
|
2021-03-23 13:31:18 +01:00
|
|
|
import {
|
|
|
|
datasources,
|
|
|
|
integrations,
|
|
|
|
queries,
|
|
|
|
database,
|
|
|
|
tables,
|
2021-04-01 11:29:47 +02:00
|
|
|
} from "stores/backend"
|
2022-01-20 19:42:30 +01:00
|
|
|
import { API } from "api"
|
2021-01-14 10:09:23 +01:00
|
|
|
import { FrontendTypes } from "constants"
|
2021-09-21 21:21:15 +02:00
|
|
|
import analytics, { Events } from "analytics"
|
2021-08-20 15:12:52 +02:00
|
|
|
import {
|
|
|
|
findComponentType,
|
|
|
|
findComponentParent,
|
|
|
|
findClosestMatchingComponent,
|
|
|
|
findAllMatchingComponents,
|
2021-10-01 16:01:16 +02:00
|
|
|
findComponent,
|
2021-11-04 12:30:43 +01:00
|
|
|
getComponentSettings,
|
2022-03-02 18:45:01 +01:00
|
|
|
makeComponentUnique,
|
2021-12-06 12:41:17 +01:00
|
|
|
} from "../componentUtils"
|
2022-01-20 12:19:37 +01:00
|
|
|
import { Helpers } from "@budibase/bbui"
|
2020-11-04 17:13:50 +01:00
|
|
|
|
|
|
|
const INITIAL_FRONTEND_STATE = {
|
|
|
|
apps: [],
|
|
|
|
name: "",
|
2021-01-14 18:01:31 +01:00
|
|
|
url: "",
|
2020-11-04 17:13:50 +01:00
|
|
|
description: "",
|
2020-12-02 17:15:14 +01:00
|
|
|
layouts: [],
|
2020-11-24 19:11:34 +01:00
|
|
|
screens: [],
|
2020-11-04 17:13:50 +01:00
|
|
|
components: [],
|
2021-07-07 14:54:21 +02:00
|
|
|
clientFeatures: {
|
|
|
|
spectrumThemes: false,
|
|
|
|
intelligentLoading: false,
|
2021-08-13 12:24:47 +02:00
|
|
|
deviceAwareness: false,
|
2021-09-02 12:38:41 +02:00
|
|
|
state: false,
|
2022-02-11 12:55:35 +01:00
|
|
|
rowSelection: false,
|
2021-09-02 12:38:41 +02:00
|
|
|
customThemes: false,
|
2021-09-08 10:46:20 +02:00
|
|
|
devicePreview: false,
|
2021-11-09 12:15:29 +01:00
|
|
|
messagePassing: false,
|
2022-03-25 10:26:15 +01:00
|
|
|
continueIfAction: false,
|
2021-07-07 14:54:21 +02:00
|
|
|
},
|
2020-11-04 17:13:50 +01:00
|
|
|
currentFrontEndType: "none",
|
2020-12-14 12:14:16 +01:00
|
|
|
selectedScreenId: "",
|
|
|
|
selectedLayoutId: "",
|
2020-12-07 16:27:46 +01:00
|
|
|
selectedComponentId: "",
|
2020-11-04 17:13:50 +01:00
|
|
|
errors: [],
|
|
|
|
hasAppPackage: false,
|
|
|
|
libraries: null,
|
|
|
|
appId: "",
|
2020-11-19 22:07:25 +01:00
|
|
|
routes: {},
|
2021-04-01 15:10:49 +02:00
|
|
|
clientLibPath: "",
|
2021-06-28 13:55:11 +02:00
|
|
|
theme: "",
|
2021-09-02 12:38:41 +02:00
|
|
|
customTheme: {},
|
2021-09-07 17:02:11 +02:00
|
|
|
previewDevice: "desktop",
|
2020-11-04 17:13:50 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
export const getFrontendStore = () => {
|
|
|
|
const store = writable({ ...INITIAL_FRONTEND_STATE })
|
|
|
|
|
|
|
|
store.actions = {
|
2022-02-04 10:29:51 +01:00
|
|
|
reset: () => {
|
|
|
|
store.set({ ...INITIAL_FRONTEND_STATE })
|
|
|
|
},
|
2021-05-04 12:32:22 +02:00
|
|
|
initialise: async pkg => {
|
2021-04-01 15:10:49 +02:00
|
|
|
const { layouts, screens, application, clientLibPath } = pkg
|
2022-01-20 19:42:30 +01:00
|
|
|
|
|
|
|
// Fetch component definitions.
|
|
|
|
// Allow errors to propagate.
|
|
|
|
let components = await API.fetchComponentLibDefinitions(application.appId)
|
|
|
|
|
|
|
|
// Reset store state
|
2021-05-04 12:32:22 +02:00
|
|
|
store.update(state => ({
|
2020-11-04 18:09:45 +01:00
|
|
|
...state,
|
2021-01-14 10:09:23 +01:00
|
|
|
libraries: application.componentLibraries,
|
2020-11-04 18:09:45 +01:00
|
|
|
components,
|
2021-07-07 14:54:21 +02:00
|
|
|
clientFeatures: {
|
2021-12-02 13:27:59 +01:00
|
|
|
...INITIAL_FRONTEND_STATE.clientFeatures,
|
2021-07-07 14:54:21 +02:00
|
|
|
...components.features,
|
|
|
|
},
|
2021-01-14 10:09:23 +01:00
|
|
|
name: application.name,
|
|
|
|
description: application.description,
|
2021-05-16 22:25:37 +02:00
|
|
|
appId: application.appId,
|
2021-01-20 12:14:36 +01:00
|
|
|
url: application.url,
|
2022-01-24 19:40:06 +01:00
|
|
|
layouts: layouts || [],
|
|
|
|
screens: screens || [],
|
2021-08-02 16:28:40 +02:00
|
|
|
theme: application.theme || "spectrum--light",
|
2021-09-02 12:38:41 +02:00
|
|
|
customTheme: application.customTheme,
|
2020-11-04 18:09:45 +01:00
|
|
|
hasAppPackage: true,
|
2021-01-14 10:09:23 +01:00
|
|
|
appInstance: application.instance,
|
2021-04-01 15:10:49 +02:00
|
|
|
clientLibPath,
|
2021-05-25 13:42:55 +02:00
|
|
|
previousTopNavPath: {},
|
2021-07-07 18:07:42 +02:00
|
|
|
version: application.version,
|
2021-07-07 18:35:28 +02:00
|
|
|
revertableVersion: application.revertableVersion,
|
2020-11-04 18:09:45 +01:00
|
|
|
}))
|
2021-03-22 12:02:55 +01:00
|
|
|
|
|
|
|
// Initialise backend stores
|
2021-03-22 16:33:08 +01:00
|
|
|
database.set(application.instance)
|
2022-01-20 19:42:30 +01:00
|
|
|
await datasources.init()
|
|
|
|
await integrations.init()
|
|
|
|
await queries.init()
|
|
|
|
await tables.init()
|
2020-11-04 17:13:50 +01:00
|
|
|
},
|
2021-06-28 13:55:11 +02:00
|
|
|
theme: {
|
|
|
|
save: async theme => {
|
|
|
|
const appId = get(store).appId
|
2022-01-24 19:40:06 +01:00
|
|
|
await API.saveAppMetadata({
|
|
|
|
appId,
|
|
|
|
metadata: { theme },
|
|
|
|
})
|
|
|
|
store.update(state => {
|
|
|
|
state.theme = theme
|
|
|
|
return state
|
|
|
|
})
|
2021-06-28 13:55:11 +02:00
|
|
|
},
|
|
|
|
},
|
2021-09-02 12:38:41 +02:00
|
|
|
customTheme: {
|
|
|
|
save: async customTheme => {
|
|
|
|
const appId = get(store).appId
|
2022-01-24 19:40:06 +01:00
|
|
|
await API.saveAppMetadata({
|
|
|
|
appId,
|
|
|
|
metadata: { customTheme },
|
|
|
|
})
|
|
|
|
store.update(state => {
|
|
|
|
state.customTheme = customTheme
|
|
|
|
return state
|
2021-09-02 12:38:41 +02:00
|
|
|
})
|
|
|
|
},
|
|
|
|
},
|
2020-11-19 22:07:25 +01:00
|
|
|
routing: {
|
|
|
|
fetch: async () => {
|
2022-01-24 19:40:06 +01:00
|
|
|
const response = await API.fetchAppRoutes()
|
2021-05-04 12:32:22 +02:00
|
|
|
store.update(state => {
|
2022-01-24 19:40:06 +01:00
|
|
|
state.routes = response.routes
|
2020-11-19 22:07:25 +01:00
|
|
|
return state
|
|
|
|
})
|
|
|
|
},
|
|
|
|
},
|
2020-11-04 17:13:50 +01:00
|
|
|
screens: {
|
2021-05-04 12:32:22 +02:00
|
|
|
select: screenId => {
|
|
|
|
store.update(state => {
|
2020-12-09 17:01:16 +01:00
|
|
|
let screens = get(allScreens)
|
|
|
|
let screen =
|
2021-05-04 12:32:22 +02:00
|
|
|
screens.find(screen => screen._id === screenId) || screens[0]
|
2020-12-08 17:55:43 +01:00
|
|
|
if (!screen) return state
|
2020-12-14 12:14:16 +01:00
|
|
|
|
|
|
|
// Update role to the screen's role setting so that it will always
|
|
|
|
// be visible
|
|
|
|
selectedAccessRole.set(screen.routing.roleId)
|
|
|
|
|
2020-12-01 17:22:06 +01:00
|
|
|
state.currentFrontEndType = FrontendTypes.SCREEN
|
2020-12-14 12:14:16 +01:00
|
|
|
state.selectedScreenId = screen._id
|
2020-11-04 17:13:50 +01:00
|
|
|
state.currentView = "detail"
|
2020-12-08 17:55:43 +01:00
|
|
|
state.selectedComponentId = screen.props?._id
|
2020-11-04 17:13:50 +01:00
|
|
|
return state
|
|
|
|
})
|
|
|
|
},
|
2021-05-04 12:32:22 +02:00
|
|
|
save: async screen => {
|
2020-11-09 11:28:49 +01:00
|
|
|
const creatingNewScreen = screen._id === undefined
|
2022-01-24 19:40:06 +01:00
|
|
|
const savedScreen = await API.saveScreen(screen)
|
2021-05-04 12:32:22 +02:00
|
|
|
store.update(state => {
|
2022-01-24 19:40:06 +01:00
|
|
|
const idx = state.screens.findIndex(x => x._id === savedScreen._id)
|
|
|
|
if (idx !== -1) {
|
|
|
|
state.screens.splice(idx, 1, savedScreen)
|
|
|
|
} else {
|
|
|
|
state.screens.push(savedScreen)
|
2020-11-25 18:56:09 +01:00
|
|
|
}
|
2020-11-04 17:13:50 +01:00
|
|
|
return state
|
|
|
|
})
|
2020-12-09 15:51:42 +01:00
|
|
|
|
2022-01-24 19:40:06 +01:00
|
|
|
// Refresh routes
|
|
|
|
await store.actions.routing.fetch()
|
2020-12-09 15:51:42 +01:00
|
|
|
|
2022-01-24 19:40:06 +01:00
|
|
|
// Select the new screen if creating a new one
|
2020-12-09 15:51:42 +01:00
|
|
|
if (creatingNewScreen) {
|
2022-01-24 19:40:06 +01:00
|
|
|
store.actions.screens.select(savedScreen._id)
|
2020-12-09 15:51:42 +01:00
|
|
|
}
|
2022-01-24 19:40:06 +01:00
|
|
|
return savedScreen
|
2020-11-04 17:13:50 +01:00
|
|
|
},
|
2021-05-04 12:32:22 +02:00
|
|
|
delete: async screens => {
|
2020-11-19 12:15:29 +01:00
|
|
|
const screensToDelete = Array.isArray(screens) ? screens : [screens]
|
|
|
|
|
2022-01-20 19:42:30 +01:00
|
|
|
// Build array of promises to speed up bulk deletions
|
|
|
|
const promises = []
|
|
|
|
screensToDelete.forEach(screen => {
|
|
|
|
// Delete the screen
|
|
|
|
promises.push(
|
|
|
|
API.deleteScreen({
|
|
|
|
screenId: screen._id,
|
|
|
|
screenRev: screen._rev,
|
|
|
|
})
|
|
|
|
)
|
|
|
|
// Remove links to this screen
|
|
|
|
promises.push(
|
|
|
|
store.actions.components.links.delete(
|
|
|
|
screen.routing.route,
|
|
|
|
screen.props._instanceName
|
2021-09-21 15:16:10 +02:00
|
|
|
)
|
2022-01-20 19:42:30 +01:00
|
|
|
)
|
|
|
|
})
|
|
|
|
|
2022-01-24 19:40:06 +01:00
|
|
|
await Promise.all(promises)
|
|
|
|
const deletedIds = screensToDelete.map(screen => screen._id)
|
|
|
|
store.update(state => {
|
|
|
|
// Remove deleted screens from state
|
|
|
|
state.screens = state.screens.filter(screen => {
|
|
|
|
return !deletedIds.includes(screen._id)
|
2022-01-20 19:42:30 +01:00
|
|
|
})
|
2022-01-24 19:40:06 +01:00
|
|
|
// Deselect the current screen if it was deleted
|
|
|
|
if (deletedIds.includes(state.selectedScreenId)) {
|
|
|
|
state.selectedScreenId = null
|
2020-11-04 17:13:50 +01:00
|
|
|
}
|
|
|
|
return state
|
|
|
|
})
|
2022-01-24 19:40:06 +01:00
|
|
|
|
|
|
|
// Refresh routes
|
|
|
|
await store.actions.routing.fetch()
|
2020-11-04 17:13:50 +01:00
|
|
|
},
|
|
|
|
},
|
|
|
|
preview: {
|
2020-12-07 21:29:41 +01:00
|
|
|
saveSelected: async () => {
|
2020-11-04 17:13:50 +01:00
|
|
|
const state = get(store)
|
2020-12-07 21:29:41 +01:00
|
|
|
const selectedAsset = get(currentAsset)
|
2020-12-01 17:22:06 +01:00
|
|
|
if (state.currentFrontEndType !== FrontendTypes.LAYOUT) {
|
2022-01-20 19:42:30 +01:00
|
|
|
return await store.actions.screens.save(selectedAsset)
|
2020-12-03 16:15:14 +01:00
|
|
|
} else {
|
2022-01-20 19:42:30 +01:00
|
|
|
return await store.actions.layouts.save(selectedAsset)
|
2020-11-09 16:55:36 +01:00
|
|
|
}
|
2020-11-04 17:13:50 +01:00
|
|
|
},
|
2021-09-07 17:02:11 +02:00
|
|
|
setDevice: device => {
|
|
|
|
store.update(state => {
|
|
|
|
state.previewDevice = device
|
|
|
|
return state
|
|
|
|
})
|
|
|
|
},
|
2020-11-04 18:09:45 +01:00
|
|
|
},
|
2020-11-24 19:11:34 +01:00
|
|
|
layouts: {
|
2021-05-04 12:32:22 +02:00
|
|
|
select: layoutId => {
|
|
|
|
store.update(state => {
|
2020-12-14 12:14:16 +01:00
|
|
|
const layout =
|
|
|
|
store.actions.layouts.find(layoutId) || get(store).layouts[0]
|
2020-12-09 16:57:32 +01:00
|
|
|
if (!layout) return
|
2020-12-01 17:22:06 +01:00
|
|
|
state.currentFrontEndType = FrontendTypes.LAYOUT
|
2020-11-04 18:09:45 +01:00
|
|
|
state.currentView = "detail"
|
2020-12-14 12:14:16 +01:00
|
|
|
state.selectedLayoutId = layout._id
|
2020-12-08 17:55:43 +01:00
|
|
|
state.selectedComponentId = layout.props?._id
|
2020-11-04 18:09:45 +01:00
|
|
|
return state
|
|
|
|
})
|
2020-11-04 17:13:50 +01:00
|
|
|
},
|
2021-05-04 12:32:22 +02:00
|
|
|
save: async layout => {
|
2022-01-24 19:40:06 +01:00
|
|
|
const creatingNewLayout = layout._id === undefined
|
|
|
|
const savedLayout = await API.saveLayout(layout)
|
2021-05-04 12:32:22 +02:00
|
|
|
store.update(state => {
|
2022-01-24 19:40:06 +01:00
|
|
|
const idx = state.layouts.findIndex(x => x._id === savedLayout._id)
|
|
|
|
if (idx !== -1) {
|
|
|
|
state.layouts.splice(idx, 1, savedLayout)
|
2020-12-05 00:16:07 +01:00
|
|
|
} else {
|
2020-12-09 15:51:42 +01:00
|
|
|
state.layouts.push(savedLayout)
|
2020-11-25 18:56:09 +01:00
|
|
|
}
|
2020-11-04 18:09:45 +01:00
|
|
|
return state
|
|
|
|
})
|
2020-12-09 15:51:42 +01:00
|
|
|
|
|
|
|
// Select layout if creating a new one
|
|
|
|
if (creatingNewLayout) {
|
|
|
|
store.actions.layouts.select(savedLayout._id)
|
|
|
|
}
|
|
|
|
return savedLayout
|
2020-11-04 18:09:45 +01:00
|
|
|
},
|
2021-05-04 12:32:22 +02:00
|
|
|
find: layoutId => {
|
2020-12-02 15:15:07 +01:00
|
|
|
if (!layoutId) {
|
2020-11-25 18:56:09 +01:00
|
|
|
return get(mainLayout)
|
|
|
|
}
|
|
|
|
const storeContents = get(store)
|
2021-05-04 12:32:22 +02:00
|
|
|
return storeContents.layouts.find(layout => layout._id === layoutId)
|
2020-11-25 18:56:09 +01:00
|
|
|
},
|
2022-01-20 19:42:30 +01:00
|
|
|
delete: async layout => {
|
|
|
|
if (!layout?._id) {
|
|
|
|
return
|
2020-12-05 00:42:22 +01:00
|
|
|
}
|
2022-01-24 19:40:06 +01:00
|
|
|
await API.deleteLayout({
|
|
|
|
layoutId: layout._id,
|
|
|
|
layoutRev: layout._rev,
|
|
|
|
})
|
2021-05-04 12:32:22 +02:00
|
|
|
store.update(state => {
|
2022-01-24 19:40:06 +01:00
|
|
|
// Select main layout if we deleted the selected layout
|
|
|
|
if (layout._id === state.selectedLayoutId) {
|
2020-12-14 12:14:16 +01:00
|
|
|
state.selectedLayoutId = get(mainLayout)._id
|
|
|
|
}
|
2022-01-24 19:40:06 +01:00
|
|
|
state.layouts = state.layouts.filter(x => x._id !== layout._id)
|
2020-12-05 00:42:22 +01:00
|
|
|
return state
|
|
|
|
})
|
|
|
|
},
|
2020-11-04 18:09:45 +01:00
|
|
|
},
|
|
|
|
components: {
|
2021-05-04 12:32:22 +02:00
|
|
|
select: component => {
|
2021-12-10 15:18:01 +01:00
|
|
|
const asset = get(currentAsset)
|
|
|
|
if (!asset || !component) {
|
2021-01-14 10:09:23 +01:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// If this is the root component, select the asset instead
|
|
|
|
const parent = findComponentParent(asset.props, component._id)
|
|
|
|
if (parent == null) {
|
|
|
|
const state = get(store)
|
|
|
|
const isLayout = state.currentFrontEndType === FrontendTypes.LAYOUT
|
|
|
|
if (isLayout) {
|
|
|
|
store.actions.layouts.select(asset._id)
|
|
|
|
} else {
|
|
|
|
store.actions.screens.select(asset._id)
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Otherwise select the component
|
2021-05-04 12:32:22 +02:00
|
|
|
store.update(state => {
|
2020-12-07 16:27:46 +01:00
|
|
|
state.selectedComponentId = component._id
|
2020-11-04 18:09:45 +01:00
|
|
|
state.currentView = "component"
|
|
|
|
return state
|
|
|
|
})
|
|
|
|
},
|
2021-05-04 12:32:22 +02:00
|
|
|
getDefinition: componentName => {
|
2021-01-12 21:00:35 +01:00
|
|
|
if (!componentName) {
|
|
|
|
return null
|
|
|
|
}
|
2021-01-14 10:09:23 +01:00
|
|
|
if (!componentName.startsWith("@budibase")) {
|
|
|
|
componentName = `@budibase/standard-components/${componentName}`
|
|
|
|
}
|
|
|
|
return get(store).components[componentName]
|
2021-01-12 21:00:35 +01:00
|
|
|
},
|
|
|
|
createInstance: (componentName, presetProps) => {
|
|
|
|
const definition = store.actions.components.getDefinition(componentName)
|
|
|
|
if (!definition) {
|
|
|
|
return null
|
|
|
|
}
|
2020-11-04 17:13:50 +01:00
|
|
|
|
2021-01-12 21:00:35 +01:00
|
|
|
// Generate default props
|
2021-11-04 12:30:43 +01:00
|
|
|
const settings = getComponentSettings(componentName)
|
2021-01-12 21:00:35 +01:00
|
|
|
let props = { ...presetProps }
|
2021-11-04 12:30:43 +01:00
|
|
|
settings.forEach(setting => {
|
|
|
|
if (setting.defaultValue !== undefined) {
|
|
|
|
props[setting.key] = setting.defaultValue
|
|
|
|
}
|
|
|
|
})
|
2020-11-04 17:13:50 +01:00
|
|
|
|
2021-01-12 21:00:35 +01:00
|
|
|
// Add any extra properties the component needs
|
|
|
|
let extras = {}
|
|
|
|
if (definition.hasChildren) {
|
|
|
|
extras._children = []
|
|
|
|
}
|
2021-08-20 15:12:52 +02:00
|
|
|
if (componentName.endsWith("/formstep")) {
|
|
|
|
const parentForm = findClosestMatchingComponent(
|
|
|
|
get(currentAsset).props,
|
|
|
|
get(selectedComponent)._id,
|
|
|
|
component => component._component.endsWith("/form")
|
|
|
|
)
|
|
|
|
const formSteps = findAllMatchingComponents(parentForm, component =>
|
|
|
|
component._component.endsWith("/formstep")
|
|
|
|
)
|
|
|
|
extras.step = formSteps.length + 1
|
|
|
|
extras._instanceName = `Step ${formSteps.length + 1}`
|
|
|
|
}
|
2020-11-04 17:13:50 +01:00
|
|
|
|
2021-01-12 21:00:35 +01:00
|
|
|
return {
|
2022-01-20 12:19:37 +01:00
|
|
|
_id: Helpers.uuid(),
|
2021-01-12 21:00:35 +01:00
|
|
|
_component: definition.component,
|
|
|
|
_styles: { normal: {}, hover: {}, active: {} },
|
2021-01-19 12:52:33 +01:00
|
|
|
_instanceName: `New ${definition.name}`,
|
2021-01-12 21:00:35 +01:00
|
|
|
...cloneDeep(props),
|
|
|
|
...extras,
|
|
|
|
}
|
|
|
|
},
|
2021-02-23 17:29:50 +01:00
|
|
|
create: async (componentName, presetProps) => {
|
2021-01-14 10:09:23 +01:00
|
|
|
const selected = get(selectedComponent)
|
|
|
|
const asset = get(currentAsset)
|
|
|
|
|
2021-01-12 21:00:35 +01:00
|
|
|
// Create new component
|
|
|
|
const componentInstance = store.actions.components.createInstance(
|
|
|
|
componentName,
|
|
|
|
presetProps
|
|
|
|
)
|
2022-01-20 19:42:30 +01:00
|
|
|
if (!componentInstance || !asset) {
|
2021-01-12 21:00:35 +01:00
|
|
|
return
|
|
|
|
}
|
2020-12-07 16:27:46 +01:00
|
|
|
|
2021-01-12 21:00:35 +01:00
|
|
|
// Find parent node to attach this component to
|
|
|
|
let parentComponent
|
|
|
|
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
|
2020-12-07 16:27:46 +01:00
|
|
|
} else {
|
2021-01-12 21:00:35 +01:00
|
|
|
// Otherwise we need to use the parent of this component
|
2022-03-08 18:57:36 +01:00
|
|
|
parentComponent = findComponentParent(asset?.props, selected._id)
|
2020-11-04 18:09:45 +01:00
|
|
|
}
|
2021-01-12 21:00:35 +01:00
|
|
|
} else {
|
|
|
|
// Use screen or layout if no component is selected
|
2022-03-08 18:57:36 +01:00
|
|
|
parentComponent = asset?.props
|
2021-01-12 21:00:35 +01:00
|
|
|
}
|
2020-11-04 17:13:50 +01:00
|
|
|
|
2021-01-12 21:00:35 +01:00
|
|
|
// Attach component
|
|
|
|
if (!parentComponent) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if (!parentComponent._children) {
|
|
|
|
parentComponent._children = []
|
|
|
|
}
|
|
|
|
parentComponent._children.push(componentInstance)
|
2020-11-04 18:09:45 +01:00
|
|
|
|
2021-01-12 21:00:35 +01:00
|
|
|
// Save components and update UI
|
2021-02-23 17:29:50 +01:00
|
|
|
await store.actions.preview.saveSelected()
|
2021-05-04 12:32:22 +02:00
|
|
|
store.update(state => {
|
2020-11-04 18:09:45 +01:00
|
|
|
state.currentView = "component"
|
2021-01-12 21:00:35 +01:00
|
|
|
state.selectedComponentId = componentInstance._id
|
2020-11-04 18:09:45 +01:00
|
|
|
return state
|
|
|
|
})
|
2021-01-12 21:00:35 +01:00
|
|
|
|
|
|
|
// Log event
|
2021-09-21 21:21:15 +02:00
|
|
|
analytics.captureEvent(Events.COMPONENT.CREATED, {
|
2021-01-12 21:00:35 +01:00
|
|
|
name: componentInstance._component,
|
|
|
|
})
|
|
|
|
|
|
|
|
return componentInstance
|
|
|
|
},
|
2021-05-04 12:32:22 +02:00
|
|
|
delete: async component => {
|
2021-01-12 21:00:35 +01:00
|
|
|
if (!component) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
const asset = get(currentAsset)
|
|
|
|
if (!asset) {
|
|
|
|
return
|
|
|
|
}
|
2021-10-01 16:01:16 +02:00
|
|
|
|
|
|
|
// Fetch full definition
|
|
|
|
component = findComponent(asset.props, component._id)
|
|
|
|
|
|
|
|
// Ensure we aren't deleting the screen slot
|
|
|
|
if (component._component?.endsWith("/screenslot")) {
|
2021-10-04 17:50:52 +02:00
|
|
|
throw "You can't delete the screen slot"
|
2021-10-01 16:01:16 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Ensure we aren't deleting something that contains the screen slot
|
|
|
|
const screenslot = findComponentType(
|
|
|
|
component,
|
|
|
|
"@budibase/standard-components/screenslot"
|
|
|
|
)
|
|
|
|
if (screenslot != null) {
|
2021-10-04 17:50:52 +02:00
|
|
|
throw "You can't delete a component that contains the screen slot"
|
2021-10-01 16:01:16 +02:00
|
|
|
}
|
|
|
|
|
2021-01-12 21:00:35 +01:00
|
|
|
const parent = findComponentParent(asset.props, component._id)
|
|
|
|
if (parent) {
|
|
|
|
parent._children = parent._children.filter(
|
2021-05-04 12:32:22 +02:00
|
|
|
child => child._id !== component._id
|
2021-01-12 21:00:35 +01:00
|
|
|
)
|
|
|
|
store.actions.components.select(parent)
|
|
|
|
}
|
2021-02-23 17:29:50 +01:00
|
|
|
await store.actions.preview.saveSelected()
|
2020-11-04 18:09:45 +01:00
|
|
|
},
|
|
|
|
copy: (component, cut = false) => {
|
2020-12-07 16:27:46 +01:00
|
|
|
const selectedAsset = get(currentAsset)
|
2021-01-12 21:00:35 +01:00
|
|
|
if (!selectedAsset) {
|
|
|
|
return null
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update store with copied component
|
2021-05-04 12:32:22 +02:00
|
|
|
store.update(state => {
|
2020-11-06 13:30:30 +01:00
|
|
|
state.componentToPaste = cloneDeep(component)
|
2020-11-04 18:09:45 +01:00
|
|
|
state.componentToPaste.isCut = cut
|
2021-01-12 21:00:35 +01:00
|
|
|
return state
|
|
|
|
})
|
|
|
|
|
|
|
|
// Remove the component from its parent if we're cutting
|
|
|
|
if (cut) {
|
|
|
|
const parent = findComponentParent(selectedAsset.props, component._id)
|
|
|
|
if (parent) {
|
2020-11-04 18:09:45 +01:00
|
|
|
parent._children = parent._children.filter(
|
2021-05-04 12:32:22 +02:00
|
|
|
child => child._id !== component._id
|
2020-11-04 18:09:45 +01:00
|
|
|
)
|
|
|
|
store.actions.components.select(parent)
|
|
|
|
}
|
2021-01-12 21:00:35 +01:00
|
|
|
}
|
2020-11-04 18:09:45 +01:00
|
|
|
},
|
2022-03-02 18:45:01 +01:00
|
|
|
paste: async (targetComponent, mode) => {
|
2020-11-25 18:56:09 +01:00
|
|
|
let promises = []
|
2021-05-04 12:32:22 +02:00
|
|
|
store.update(state => {
|
2021-01-12 21:00:35 +01:00
|
|
|
// Stop if we have nothing to paste
|
|
|
|
if (!state.componentToPaste) {
|
|
|
|
return state
|
|
|
|
}
|
2021-02-23 11:26:37 +01:00
|
|
|
const cut = state.componentToPaste.isCut
|
|
|
|
|
2022-03-02 18:45:01 +01:00
|
|
|
// Clone the component to paste and make unique if copying
|
2021-01-12 21:00:35 +01:00
|
|
|
delete state.componentToPaste.isCut
|
|
|
|
let componentToPaste = cloneDeep(state.componentToPaste)
|
|
|
|
if (cut) {
|
|
|
|
state.componentToPaste = null
|
2020-11-04 18:09:45 +01:00
|
|
|
} else {
|
2022-03-02 18:45:01 +01:00
|
|
|
makeComponentUnique(componentToPaste)
|
2020-11-04 18:09:45 +01:00
|
|
|
}
|
2020-11-04 17:13:50 +01:00
|
|
|
|
2020-11-04 18:09:45 +01:00
|
|
|
if (mode === "inside") {
|
2021-01-12 21:00:35 +01:00
|
|
|
// Paste inside target component if chosen
|
|
|
|
if (!targetComponent._children) {
|
|
|
|
targetComponent._children = []
|
|
|
|
}
|
2020-11-04 18:09:45 +01:00
|
|
|
targetComponent._children.push(componentToPaste)
|
2021-01-12 21:00:35 +01:00
|
|
|
} 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
|
|
|
|
}
|
2020-11-04 17:13:50 +01:00
|
|
|
|
2021-01-12 21:00:35 +01:00
|
|
|
// 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))
|
|
|
|
}
|
2020-11-04 17:13:50 +01:00
|
|
|
|
2021-01-12 21:00:35 +01:00
|
|
|
// Save and select the new component
|
2020-11-25 18:56:09 +01:00
|
|
|
promises.push(store.actions.preview.saveSelected())
|
2020-11-04 18:09:45 +01:00
|
|
|
store.actions.components.select(componentToPaste)
|
|
|
|
return state
|
|
|
|
})
|
2020-11-25 18:56:09 +01:00
|
|
|
await Promise.all(promises)
|
2020-11-04 18:09:45 +01:00
|
|
|
},
|
2021-06-22 10:14:17 +02:00
|
|
|
updateStyle: async (name, value) => {
|
2020-12-07 16:27:46 +01:00
|
|
|
const selected = get(selectedComponent)
|
2021-01-05 12:44:58 +01:00
|
|
|
if (value == null || value === "") {
|
2021-06-22 10:14:17 +02:00
|
|
|
delete selected._styles.normal[name]
|
2021-01-05 12:44:58 +01:00
|
|
|
} else {
|
2021-06-22 10:14:17 +02:00
|
|
|
selected._styles.normal[name] = value
|
2021-01-04 19:39:17 +01:00
|
|
|
}
|
|
|
|
await store.actions.preview.saveSelected()
|
|
|
|
},
|
2021-05-04 12:32:22 +02:00
|
|
|
updateCustomStyle: async style => {
|
2021-01-04 19:39:17 +01:00
|
|
|
const selected = get(selectedComponent)
|
|
|
|
selected._styles.custom = style
|
|
|
|
await store.actions.preview.saveSelected()
|
2020-11-04 18:09:45 +01:00
|
|
|
},
|
2021-07-21 15:03:49 +02:00
|
|
|
updateConditions: async conditions => {
|
|
|
|
const selected = get(selectedComponent)
|
|
|
|
selected._conditions = conditions
|
|
|
|
await store.actions.preview.saveSelected()
|
|
|
|
},
|
2021-02-23 17:29:50 +01:00
|
|
|
updateProp: async (name, value) => {
|
2021-01-12 21:00:35 +01:00
|
|
|
let component = get(selectedComponent)
|
|
|
|
if (!name || !component) {
|
|
|
|
return
|
|
|
|
}
|
2021-11-16 14:38:47 +01:00
|
|
|
if (component[name] === value) {
|
|
|
|
return
|
|
|
|
}
|
2021-01-12 21:00:35 +01:00
|
|
|
component[name] = value
|
2021-05-04 12:32:22 +02:00
|
|
|
store.update(state => {
|
2021-01-12 21:00:35 +01:00
|
|
|
state.selectedComponentId = component._id
|
2020-11-04 18:09:45 +01:00
|
|
|
return state
|
|
|
|
})
|
2021-02-23 17:29:50 +01:00
|
|
|
await store.actions.preview.saveSelected()
|
2020-11-04 18:09:45 +01:00
|
|
|
},
|
2020-11-05 12:44:18 +01:00
|
|
|
links: {
|
|
|
|
save: async (url, title) => {
|
2020-11-25 18:56:09 +01:00
|
|
|
const layout = get(mainLayout)
|
2021-01-12 21:00:35 +01:00
|
|
|
if (!layout) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-06-18 13:23:33 +02:00
|
|
|
// Add link setting to main layout
|
|
|
|
if (layout.props._component.endsWith("layout")) {
|
|
|
|
// If using a new SDK, add to the layout component settings
|
|
|
|
if (!layout.props.links) {
|
|
|
|
layout.props.links = []
|
|
|
|
}
|
|
|
|
layout.props.links.push({
|
2021-01-12 21:00:35 +01:00
|
|
|
text: title,
|
2021-06-18 13:23:33 +02:00
|
|
|
url,
|
|
|
|
})
|
|
|
|
} else {
|
|
|
|
// If using an old SDK, add to the navigation component
|
|
|
|
// TODO: remove this when we can assume everyone has updated
|
|
|
|
const nav = findComponentType(
|
|
|
|
layout.props,
|
|
|
|
"@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
|
2022-01-20 12:19:37 +01:00
|
|
|
newLink._id = Helpers.uuid()
|
2021-06-18 13:23:33 +02:00
|
|
|
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`,
|
|
|
|
}
|
|
|
|
nav._children = [...nav._children, newLink]
|
2020-11-05 12:44:18 +01:00
|
|
|
}
|
2021-01-12 21:00:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Save layout
|
|
|
|
await store.actions.layouts.save(layout)
|
2020-11-05 12:44:18 +01:00
|
|
|
},
|
2021-09-21 15:16:10 +02:00
|
|
|
delete: async (url, title) => {
|
|
|
|
const layout = get(mainLayout)
|
|
|
|
if (!layout) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add link setting to main layout
|
|
|
|
if (layout.props._component.endsWith("layout")) {
|
|
|
|
// If using a new SDK, add to the layout component settings
|
|
|
|
layout.props.links = layout.props.links.filter(
|
|
|
|
link => !(link.text === title && link.url === url)
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
// If using an old SDK, add to the navigation component
|
|
|
|
// TODO: remove this when we can assume everyone has updated
|
|
|
|
const nav = findComponentType(
|
|
|
|
layout.props,
|
|
|
|
"@budibase/standard-components/navigation"
|
|
|
|
)
|
|
|
|
if (!nav) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
nav._children = nav._children.filter(
|
|
|
|
child => !(child.url === url && child.text === title)
|
|
|
|
)
|
|
|
|
}
|
|
|
|
// Save layout
|
|
|
|
await store.actions.layouts.save(layout)
|
|
|
|
},
|
2020-11-05 12:44:18 +01:00
|
|
|
},
|
2020-11-04 17:13:50 +01:00
|
|
|
},
|
|
|
|
}
|
2020-11-04 18:09:45 +01:00
|
|
|
|
|
|
|
return store
|
2020-11-04 17:13:50 +01:00
|
|
|
}
|