|
|
@ -14,8 +14,7 @@ import { |
|
|
database, |
|
|
database, |
|
|
tables, |
|
|
tables, |
|
|
} from "stores/backend" |
|
|
} from "stores/backend" |
|
|
import { fetchComponentLibDefinitions } from "../loadComponentLibraries" |
|
|
import { API } from "api" |
|
|
import api from "../api" |
|
|
|
|
|
import { FrontendTypes } from "constants" |
|
|
import { FrontendTypes } from "constants" |
|
|
import analytics, { Events } from "analytics" |
|
|
import analytics, { Events } from "analytics" |
|
|
import { |
|
|
import { |
|
|
@ -26,7 +25,7 @@ import { |
|
|
findComponent, |
|
|
findComponent, |
|
|
getComponentSettings, |
|
|
getComponentSettings, |
|
|
} from "../componentUtils" |
|
|
} from "../componentUtils" |
|
|
import { uuid } from "../uuid" |
|
|
import { Helpers } from "@budibase/bbui" |
|
|
import { removeBindings } from "../dataBinding" |
|
|
import { removeBindings } from "../dataBinding" |
|
|
|
|
|
|
|
|
const INITIAL_FRONTEND_STATE = { |
|
|
const INITIAL_FRONTEND_STATE = { |
|
|
@ -70,15 +69,12 @@ export const getFrontendStore = () => { |
|
|
}, |
|
|
}, |
|
|
initialise: async pkg => { |
|
|
initialise: async pkg => { |
|
|
const { layouts, screens, application, clientLibPath } = pkg |
|
|
const { layouts, screens, application, clientLibPath } = pkg |
|
|
const components = await fetchComponentLibDefinitions(application.appId) |
|
|
|
|
|
// make sure app isn't locked
|
|
|
// Fetch component definitions.
|
|
|
if ( |
|
|
// Allow errors to propagate.
|
|
|
components && |
|
|
let components = await API.fetchComponentLibDefinitions(application.appId) |
|
|
components.status === 400 && |
|
|
|
|
|
components.message?.includes("lock") |
|
|
// Reset store state
|
|
|
) { |
|
|
|
|
|
throw { ok: false, reason: "locked" } |
|
|
|
|
|
} |
|
|
|
|
|
store.update(state => ({ |
|
|
store.update(state => ({ |
|
|
...state, |
|
|
...state, |
|
|
libraries: application.componentLibraries, |
|
|
libraries: application.componentLibraries, |
|
|
@ -91,8 +87,8 @@ export const getFrontendStore = () => { |
|
|
description: application.description, |
|
|
description: application.description, |
|
|
appId: application.appId, |
|
|
appId: application.appId, |
|
|
url: application.url, |
|
|
url: application.url, |
|
|
layouts, |
|
|
layouts: layouts || [], |
|
|
screens, |
|
|
screens: screens || [], |
|
|
theme: application.theme || "spectrum--light", |
|
|
theme: application.theme || "spectrum--light", |
|
|
customTheme: application.customTheme, |
|
|
customTheme: application.customTheme, |
|
|
hasAppPackage: true, |
|
|
hasAppPackage: true, |
|
|
@ -104,51 +100,43 @@ export const getFrontendStore = () => { |
|
|
})) |
|
|
})) |
|
|
|
|
|
|
|
|
// Initialise backend stores
|
|
|
// Initialise backend stores
|
|
|
const [_integrations] = await Promise.all([ |
|
|
|
|
|
api.get("/api/integrations").then(r => r.json()), |
|
|
|
|
|
]) |
|
|
|
|
|
datasources.init() |
|
|
|
|
|
integrations.set(_integrations) |
|
|
|
|
|
queries.init() |
|
|
|
|
|
database.set(application.instance) |
|
|
database.set(application.instance) |
|
|
tables.init() |
|
|
await datasources.init() |
|
|
|
|
|
await integrations.init() |
|
|
|
|
|
await queries.init() |
|
|
|
|
|
await tables.init() |
|
|
}, |
|
|
}, |
|
|
theme: { |
|
|
theme: { |
|
|
save: async theme => { |
|
|
save: async theme => { |
|
|
const appId = get(store).appId |
|
|
const appId = get(store).appId |
|
|
const response = await api.put(`/api/applications/${appId}`, { theme }) |
|
|
await API.saveAppMetadata({ |
|
|
if (response.status === 200) { |
|
|
appId, |
|
|
store.update(state => { |
|
|
metadata: { theme }, |
|
|
state.theme = theme |
|
|
}) |
|
|
return state |
|
|
store.update(state => { |
|
|
}) |
|
|
state.theme = theme |
|
|
} else { |
|
|
return state |
|
|
throw new Error("Error updating theme") |
|
|
}) |
|
|
} |
|
|
|
|
|
}, |
|
|
}, |
|
|
}, |
|
|
}, |
|
|
customTheme: { |
|
|
customTheme: { |
|
|
save: async customTheme => { |
|
|
save: async customTheme => { |
|
|
const appId = get(store).appId |
|
|
const appId = get(store).appId |
|
|
const response = await api.put(`/api/applications/${appId}`, { |
|
|
await API.saveAppMetadata({ |
|
|
customTheme, |
|
|
appId, |
|
|
|
|
|
metadata: { customTheme }, |
|
|
|
|
|
}) |
|
|
|
|
|
store.update(state => { |
|
|
|
|
|
state.customTheme = customTheme |
|
|
|
|
|
return state |
|
|
}) |
|
|
}) |
|
|
if (response.status === 200) { |
|
|
|
|
|
store.update(state => { |
|
|
|
|
|
state.customTheme = customTheme |
|
|
|
|
|
return state |
|
|
|
|
|
}) |
|
|
|
|
|
} else { |
|
|
|
|
|
throw new Error("Error updating theme") |
|
|
|
|
|
} |
|
|
|
|
|
}, |
|
|
}, |
|
|
}, |
|
|
}, |
|
|
routing: { |
|
|
routing: { |
|
|
fetch: async () => { |
|
|
fetch: async () => { |
|
|
const response = await api.get("/api/routing") |
|
|
const response = await API.fetchAppRoutes() |
|
|
const json = await response.json() |
|
|
|
|
|
store.update(state => { |
|
|
store.update(state => { |
|
|
state.routes = json.routes |
|
|
state.routes = response.routes |
|
|
return state |
|
|
return state |
|
|
}) |
|
|
}) |
|
|
}, |
|
|
}, |
|
|
@ -172,82 +160,76 @@ export const getFrontendStore = () => { |
|
|
return state |
|
|
return state |
|
|
}) |
|
|
}) |
|
|
}, |
|
|
}, |
|
|
create: async screen => { |
|
|
|
|
|
screen = await store.actions.screens.save(screen) |
|
|
|
|
|
store.update(state => { |
|
|
|
|
|
state.selectedScreenId = screen._id |
|
|
|
|
|
state.selectedComponentId = screen.props._id |
|
|
|
|
|
state.currentFrontEndType = FrontendTypes.SCREEN |
|
|
|
|
|
selectedAccessRole.set(screen.routing.roleId) |
|
|
|
|
|
return state |
|
|
|
|
|
}) |
|
|
|
|
|
return screen |
|
|
|
|
|
}, |
|
|
|
|
|
save: async screen => { |
|
|
save: async screen => { |
|
|
const creatingNewScreen = screen._id === undefined |
|
|
const creatingNewScreen = screen._id === undefined |
|
|
const response = await api.post(`/api/screens`, screen) |
|
|
const savedScreen = await API.saveScreen(screen) |
|
|
if (response.status !== 200) { |
|
|
|
|
|
return |
|
|
|
|
|
} |
|
|
|
|
|
screen = await response.json() |
|
|
|
|
|
await store.actions.routing.fetch() |
|
|
|
|
|
|
|
|
|
|
|
store.update(state => { |
|
|
store.update(state => { |
|
|
const foundScreen = state.screens.findIndex( |
|
|
const idx = state.screens.findIndex(x => x._id === savedScreen._id) |
|
|
el => el._id === screen._id |
|
|
if (idx !== -1) { |
|
|
) |
|
|
state.screens.splice(idx, 1, savedScreen) |
|
|
if (foundScreen !== -1) { |
|
|
} else { |
|
|
state.screens.splice(foundScreen, 1) |
|
|
state.screens.push(savedScreen) |
|
|
} |
|
|
} |
|
|
state.screens.push(screen) |
|
|
|
|
|
return state |
|
|
return state |
|
|
}) |
|
|
}) |
|
|
|
|
|
|
|
|
|
|
|
// Refresh routes
|
|
|
|
|
|
await store.actions.routing.fetch() |
|
|
|
|
|
|
|
|
|
|
|
// Select the new screen if creating a new one
|
|
|
if (creatingNewScreen) { |
|
|
if (creatingNewScreen) { |
|
|
store.actions.screens.select(screen._id) |
|
|
store.actions.screens.select(savedScreen._id) |
|
|
} |
|
|
} |
|
|
|
|
|
return savedScreen |
|
|
return screen |
|
|
|
|
|
}, |
|
|
}, |
|
|
delete: async screens => { |
|
|
delete: async screens => { |
|
|
const screensToDelete = Array.isArray(screens) ? screens : [screens] |
|
|
const screensToDelete = Array.isArray(screens) ? screens : [screens] |
|
|
|
|
|
|
|
|
const screenDeletePromises = [] |
|
|
// Build array of promises to speed up bulk deletions
|
|
|
store.update(state => { |
|
|
const promises = [] |
|
|
for (let screenToDelete of screensToDelete) { |
|
|
screensToDelete.forEach(screen => { |
|
|
state.screens = state.screens.filter( |
|
|
// Delete the screen
|
|
|
screen => screen._id !== screenToDelete._id |
|
|
promises.push( |
|
|
) |
|
|
API.deleteScreen({ |
|
|
screenDeletePromises.push( |
|
|
screenId: screen._id, |
|
|
api.delete( |
|
|
screenRev: screen._rev, |
|
|
`/api/screens/${screenToDelete._id}/${screenToDelete._rev}` |
|
|
}) |
|
|
) |
|
|
) |
|
|
) |
|
|
// Remove links to this screen
|
|
|
if (screenToDelete._id === state.selectedScreenId) { |
|
|
promises.push( |
|
|
state.selectedScreenId = null |
|
|
store.actions.components.links.delete( |
|
|
} |
|
|
screen.routing.route, |
|
|
//remove the link for this screen
|
|
|
screen.props._instanceName |
|
|
screenDeletePromises.push( |
|
|
|
|
|
store.actions.components.links.delete( |
|
|
|
|
|
screenToDelete.routing.route, |
|
|
|
|
|
screenToDelete.props._instanceName |
|
|
|
|
|
) |
|
|
|
|
|
) |
|
|
) |
|
|
|
|
|
) |
|
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
|
|
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) |
|
|
|
|
|
}) |
|
|
|
|
|
// Deselect the current screen if it was deleted
|
|
|
|
|
|
if (deletedIds.includes(state.selectedScreenId)) { |
|
|
|
|
|
state.selectedScreenId = null |
|
|
} |
|
|
} |
|
|
return state |
|
|
return state |
|
|
}) |
|
|
}) |
|
|
await Promise.all(screenDeletePromises) |
|
|
|
|
|
|
|
|
// Refresh routes
|
|
|
|
|
|
await store.actions.routing.fetch() |
|
|
}, |
|
|
}, |
|
|
}, |
|
|
}, |
|
|
preview: { |
|
|
preview: { |
|
|
saveSelected: async () => { |
|
|
saveSelected: async () => { |
|
|
const state = get(store) |
|
|
const state = get(store) |
|
|
const selectedAsset = get(currentAsset) |
|
|
const selectedAsset = get(currentAsset) |
|
|
|
|
|
|
|
|
if (state.currentFrontEndType !== FrontendTypes.LAYOUT) { |
|
|
if (state.currentFrontEndType !== FrontendTypes.LAYOUT) { |
|
|
await store.actions.screens.save(selectedAsset) |
|
|
return await store.actions.screens.save(selectedAsset) |
|
|
} else { |
|
|
} else { |
|
|
await store.actions.layouts.save(selectedAsset) |
|
|
return await store.actions.layouts.save(selectedAsset) |
|
|
} |
|
|
} |
|
|
}, |
|
|
}, |
|
|
setDevice: device => { |
|
|
setDevice: device => { |
|
|
@ -271,25 +253,13 @@ export const getFrontendStore = () => { |
|
|
}) |
|
|
}) |
|
|
}, |
|
|
}, |
|
|
save: async layout => { |
|
|
save: async layout => { |
|
|
const layoutToSave = cloneDeep(layout) |
|
|
const creatingNewLayout = layout._id === undefined |
|
|
const creatingNewLayout = layoutToSave._id === undefined |
|
|
const savedLayout = await API.saveLayout(layout) |
|
|
const response = await api.post(`/api/layouts`, layoutToSave) |
|
|
|
|
|
const savedLayout = await response.json() |
|
|
|
|
|
|
|
|
|
|
|
// Abort if saving failed
|
|
|
|
|
|
if (response.status !== 200) { |
|
|
|
|
|
return |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
store.update(state => { |
|
|
store.update(state => { |
|
|
const layoutIdx = state.layouts.findIndex( |
|
|
const idx = state.layouts.findIndex(x => x._id === savedLayout._id) |
|
|
stateLayout => stateLayout._id === savedLayout._id |
|
|
if (idx !== -1) { |
|
|
) |
|
|
state.layouts.splice(idx, 1, savedLayout) |
|
|
if (layoutIdx >= 0) { |
|
|
|
|
|
// update existing layout
|
|
|
|
|
|
state.layouts.splice(layoutIdx, 1, savedLayout) |
|
|
|
|
|
} else { |
|
|
} else { |
|
|
// save new layout
|
|
|
|
|
|
state.layouts.push(savedLayout) |
|
|
state.layouts.push(savedLayout) |
|
|
} |
|
|
} |
|
|
return state |
|
|
return state |
|
|
@ -299,7 +269,6 @@ export const getFrontendStore = () => { |
|
|
if (creatingNewLayout) { |
|
|
if (creatingNewLayout) { |
|
|
store.actions.layouts.select(savedLayout._id) |
|
|
store.actions.layouts.select(savedLayout._id) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
return savedLayout |
|
|
return savedLayout |
|
|
}, |
|
|
}, |
|
|
find: layoutId => { |
|
|
find: layoutId => { |
|
|
@ -309,21 +278,20 @@ export const getFrontendStore = () => { |
|
|
const storeContents = get(store) |
|
|
const storeContents = get(store) |
|
|
return storeContents.layouts.find(layout => layout._id === layoutId) |
|
|
return storeContents.layouts.find(layout => layout._id === layoutId) |
|
|
}, |
|
|
}, |
|
|
delete: async layoutToDelete => { |
|
|
delete: async layout => { |
|
|
const response = await api.delete( |
|
|
if (!layout?._id) { |
|
|
`/api/layouts/${layoutToDelete._id}/${layoutToDelete._rev}` |
|
|
return |
|
|
) |
|
|
|
|
|
if (response.status !== 200) { |
|
|
|
|
|
const json = await response.json() |
|
|
|
|
|
throw new Error(json.message) |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
await API.deleteLayout({ |
|
|
|
|
|
layoutId: layout._id, |
|
|
|
|
|
layoutRev: layout._rev, |
|
|
|
|
|
}) |
|
|
store.update(state => { |
|
|
store.update(state => { |
|
|
state.layouts = state.layouts.filter( |
|
|
// Select main layout if we deleted the selected layout
|
|
|
layout => layout._id !== layoutToDelete._id |
|
|
if (layout._id === state.selectedLayoutId) { |
|
|
) |
|
|
|
|
|
if (layoutToDelete._id === state.selectedLayoutId) { |
|
|
|
|
|
state.selectedLayoutId = get(mainLayout)._id |
|
|
state.selectedLayoutId = get(mainLayout)._id |
|
|
} |
|
|
} |
|
|
|
|
|
state.layouts = state.layouts.filter(x => x._id !== layout._id) |
|
|
return state |
|
|
return state |
|
|
}) |
|
|
}) |
|
|
}, |
|
|
}, |
|
|
@ -398,7 +366,7 @@ export const getFrontendStore = () => { |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
return { |
|
|
return { |
|
|
_id: uuid(), |
|
|
_id: Helpers.uuid(), |
|
|
_component: definition.component, |
|
|
_component: definition.component, |
|
|
_styles: { normal: {}, hover: {}, active: {} }, |
|
|
_styles: { normal: {}, hover: {}, active: {} }, |
|
|
_instanceName: `New ${definition.name}`, |
|
|
_instanceName: `New ${definition.name}`, |
|
|
@ -415,16 +383,12 @@ export const getFrontendStore = () => { |
|
|
componentName, |
|
|
componentName, |
|
|
presetProps |
|
|
presetProps |
|
|
) |
|
|
) |
|
|
if (!componentInstance) { |
|
|
if (!componentInstance || !asset) { |
|
|
return |
|
|
return |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// Find parent node to attach this component to
|
|
|
// Find parent node to attach this component to
|
|
|
let parentComponent |
|
|
let parentComponent |
|
|
|
|
|
|
|
|
if (!asset) { |
|
|
|
|
|
return |
|
|
|
|
|
} |
|
|
|
|
|
if (selected) { |
|
|
if (selected) { |
|
|
// Use current screen or layout as parent if no component is selected
|
|
|
// Use current screen or layout as parent if no component is selected
|
|
|
const definition = store.actions.components.getDefinition( |
|
|
const definition = store.actions.components.getDefinition( |
|
|
@ -552,7 +516,7 @@ export const getFrontendStore = () => { |
|
|
if (!component) { |
|
|
if (!component) { |
|
|
return |
|
|
return |
|
|
} |
|
|
} |
|
|
component._id = uuid() |
|
|
component._id = Helpers.uuid() |
|
|
component._children?.forEach(randomizeIds) |
|
|
component._children?.forEach(randomizeIds) |
|
|
} |
|
|
} |
|
|
randomizeIds(componentToPaste) |
|
|
randomizeIds(componentToPaste) |
|
|
@ -606,11 +570,6 @@ export const getFrontendStore = () => { |
|
|
selected._styles.custom = style |
|
|
selected._styles.custom = style |
|
|
await store.actions.preview.saveSelected() |
|
|
await store.actions.preview.saveSelected() |
|
|
}, |
|
|
}, |
|
|
resetStyles: async () => { |
|
|
|
|
|
const selected = get(selectedComponent) |
|
|
|
|
|
selected._styles = { normal: {}, hover: {}, active: {} } |
|
|
|
|
|
await store.actions.preview.saveSelected() |
|
|
|
|
|
}, |
|
|
|
|
|
updateConditions: async conditions => { |
|
|
updateConditions: async conditions => { |
|
|
const selected = get(selectedComponent) |
|
|
const selected = get(selectedComponent) |
|
|
selected._conditions = conditions |
|
|
selected._conditions = conditions |
|
|
@ -665,7 +624,7 @@ export const getFrontendStore = () => { |
|
|
newLink = cloneDeep(nav._children[0]) |
|
|
newLink = cloneDeep(nav._children[0]) |
|
|
|
|
|
|
|
|
// Set our new props
|
|
|
// Set our new props
|
|
|
newLink._id = uuid() |
|
|
newLink._id = Helpers.uuid() |
|
|
newLink._instanceName = `${title} Link` |
|
|
newLink._instanceName = `${title} Link` |
|
|
newLink.url = url |
|
|
newLink.url = url |
|
|
newLink.text = title |
|
|
newLink.text = title |
|
|
|