506 lines
14 KiB
JavaScript
506 lines
14 KiB
JavaScript
import { values, cloneDeep } from "lodash/fp"
|
|
import getNewComponentName from "../getNewComponentName"
|
|
import { backendUiStore } from "builderStore"
|
|
import { writable, get } from "svelte/store"
|
|
import api from "../api"
|
|
import { DEFAULT_PAGES_OBJECT } from "../../constants"
|
|
import { getExactComponent } from "components/userInterface/pagesParsing/searchComponents"
|
|
import {
|
|
createProps,
|
|
makePropsSafe,
|
|
getBuiltin,
|
|
} from "components/userInterface/pagesParsing/createProps"
|
|
import { fetchComponentLibDefinitions } from "../loadComponentLibraries"
|
|
import { buildCodeForScreens } from "../buildCodeForScreens"
|
|
import { generate_screen_css } from "../generate_css"
|
|
import { insertCodeMetadata } from "../insertCodeMetadata"
|
|
import { uuid } from "../uuid"
|
|
import {
|
|
selectComponent as _selectComponent,
|
|
getParent,
|
|
walkProps,
|
|
savePage as _savePage,
|
|
saveCurrentPreviewItem as _saveCurrentPreviewItem,
|
|
saveScreenApi as _saveScreenApi,
|
|
regenerateCssForCurrentScreen,
|
|
generateNewIdsForComponent,
|
|
getComponentDefinition,
|
|
} from "../storeUtils"
|
|
export const getStore = () => {
|
|
const initial = {
|
|
apps: [],
|
|
name: "",
|
|
description: "",
|
|
pages: DEFAULT_PAGES_OBJECT,
|
|
mainUi: {},
|
|
unauthenticatedUi: {},
|
|
components: [],
|
|
currentPreviewItem: null,
|
|
currentComponentInfo: null,
|
|
currentFrontEndType: "none",
|
|
currentPageName: "",
|
|
currentComponentProps: null,
|
|
errors: [],
|
|
hasAppPackage: false,
|
|
libraries: null,
|
|
appId: "",
|
|
}
|
|
|
|
const store = writable(initial)
|
|
|
|
store.setPackage = setPackage(store, initial)
|
|
|
|
store.saveScreen = saveScreen(store)
|
|
store.setCurrentScreen = setCurrentScreen(store)
|
|
store.setCurrentPage = setCurrentPage(store)
|
|
store.createScreen = createScreen(store)
|
|
store.addStylesheet = addStylesheet(store)
|
|
store.removeStylesheet = removeStylesheet(store)
|
|
store.savePage = savePage(store)
|
|
store.addChildComponent = addChildComponent(store)
|
|
store.selectComponent = selectComponent(store)
|
|
store.setComponentProp = setComponentProp(store)
|
|
store.setPageOrScreenProp = setPageOrScreenProp(store)
|
|
store.setComponentStyle = setComponentStyle(store)
|
|
store.setComponentCode = setComponentCode(store)
|
|
store.setScreenType = setScreenType(store)
|
|
store.getPathToComponent = getPathToComponent(store)
|
|
store.addTemplatedComponent = addTemplatedComponent(store)
|
|
store.setMetadataProp = setMetadataProp(store)
|
|
store.editPageOrScreen = editPageOrScreen(store)
|
|
store.pasteComponent = pasteComponent(store)
|
|
store.storeComponentForCopy = storeComponentForCopy(store)
|
|
return store
|
|
}
|
|
|
|
export default getStore
|
|
|
|
const setPackage = (store, initial) => async pkg => {
|
|
const [main_screens, unauth_screens] = await Promise.all([
|
|
api
|
|
.get(`/_builder/api/${pkg.application._id}/pages/main/screens`)
|
|
.then(r => r.json()),
|
|
api
|
|
.get(`/_builder/api/${pkg.application._id}/pages/unauthenticated/screens`)
|
|
.then(r => r.json()),
|
|
])
|
|
|
|
pkg.pages = {
|
|
main: {
|
|
...pkg.pages.main,
|
|
_screens: Object.values(main_screens),
|
|
},
|
|
unauthenticated: {
|
|
...pkg.pages.unauthenticated,
|
|
_screens: Object.values(unauth_screens),
|
|
},
|
|
}
|
|
|
|
initial.libraries = pkg.application.componentLibraries
|
|
initial.components = await fetchComponentLibDefinitions(pkg.application._id)
|
|
initial.name = pkg.application.name
|
|
initial.description = pkg.application.description
|
|
initial.appId = pkg.application._id
|
|
initial.pages = pkg.pages
|
|
initial.hasAppPackage = true
|
|
initial.screens = values(pkg.screens)
|
|
initial.builtins = [getBuiltin("##builtin/screenslot")]
|
|
initial.appInstances = pkg.application.instances
|
|
initial.appId = pkg.application._id
|
|
store.set(initial)
|
|
await backendUiStore.actions.database.select(initial.appInstances[0])
|
|
return initial
|
|
}
|
|
|
|
const saveScreen = store => screen => {
|
|
store.update(state => {
|
|
return _saveScreen(store, state, screen)
|
|
})
|
|
}
|
|
|
|
const _saveScreen = async (store, s, screen) => {
|
|
const currentPageScreens = s.pages[s.currentPageName]._screens
|
|
|
|
await api
|
|
.post(`/_builder/api/${s.appId}/pages/${s.currentPageName}/screen`, screen)
|
|
.then(() => {
|
|
if (currentPageScreens.includes(screen)) return
|
|
|
|
const screens = [...currentPageScreens, screen]
|
|
|
|
store.update(innerState => {
|
|
innerState.pages[s.currentPageName]._screens = screens
|
|
innerState.screens = screens
|
|
innerState.currentPreviewItem = screen
|
|
const safeProps = makePropsSafe(
|
|
innerState.components[screen.props._component],
|
|
screen.props
|
|
)
|
|
innerState.currentComponentInfo = safeProps
|
|
screen.props = safeProps
|
|
|
|
_savePage(innerState)
|
|
return innerState
|
|
})
|
|
})
|
|
|
|
return s
|
|
}
|
|
|
|
const createScreen = store => (screenName, route, layoutComponentName) => {
|
|
store.update(state => {
|
|
const rootComponent = state.components[layoutComponentName]
|
|
|
|
const newScreen = {
|
|
description: "",
|
|
url: "",
|
|
_css: "",
|
|
props: createProps(rootComponent).props,
|
|
}
|
|
newScreen.route = route
|
|
newScreen.name = newScreen.props._id
|
|
newScreen.props._instanceName = screenName || ""
|
|
state.currentPreviewItem = newScreen
|
|
state.currentComponentInfo = newScreen.props
|
|
state.currentFrontEndType = "screen"
|
|
|
|
_saveScreen(store, state, newScreen)
|
|
|
|
return state
|
|
})
|
|
}
|
|
|
|
const setCurrentScreen = store => screenName => {
|
|
store.update(s => {
|
|
const screen = getExactComponent(s.screens, screenName, true)
|
|
s.currentPreviewItem = screen
|
|
s.currentFrontEndType = "screen"
|
|
s.currentView = "detail"
|
|
regenerateCssForCurrentScreen(s)
|
|
const safeProps = makePropsSafe(
|
|
s.components[screen.props._component],
|
|
screen.props
|
|
)
|
|
screen.props = safeProps
|
|
s.currentComponentInfo = safeProps
|
|
setCurrentPageFunctions(s)
|
|
return s
|
|
})
|
|
}
|
|
|
|
const savePage = store => async page => {
|
|
store.update(state => {
|
|
if (state.currentFrontEndType !== "page" || !state.currentPageName) {
|
|
return state
|
|
}
|
|
|
|
state.pages[state.currentPageName] = page
|
|
_savePage(state)
|
|
return state
|
|
})
|
|
}
|
|
|
|
const addStylesheet = store => stylesheet => {
|
|
store.update(s => {
|
|
s.pages.stylesheets.push(stylesheet)
|
|
_savePage(s)
|
|
return s
|
|
})
|
|
}
|
|
|
|
const removeStylesheet = store => stylesheet => {
|
|
store.update(state => {
|
|
state.pages.stylesheets = state.pages.stylesheets.filter(
|
|
s => s !== stylesheet
|
|
)
|
|
_savePage(state)
|
|
return state
|
|
})
|
|
}
|
|
|
|
const setCurrentPage = store => pageName => {
|
|
store.update(state => {
|
|
const current_screens = state.pages[pageName]._screens
|
|
|
|
const currentPage = state.pages[pageName]
|
|
|
|
state.currentFrontEndType = "page"
|
|
state.currentView = "detail"
|
|
state.currentPageName = pageName
|
|
state.screens = Array.isArray(current_screens)
|
|
? current_screens
|
|
: Object.values(current_screens)
|
|
const safeProps = makePropsSafe(
|
|
state.components[currentPage.props._component],
|
|
currentPage.props
|
|
)
|
|
state.currentComponentInfo = safeProps
|
|
currentPage.props = safeProps
|
|
state.currentPreviewItem = state.pages[pageName]
|
|
regenerateCssForCurrentScreen(state)
|
|
|
|
for (let screen of state.screens) {
|
|
screen._css = generate_screen_css([screen.props])
|
|
}
|
|
|
|
setCurrentPageFunctions(state)
|
|
return state
|
|
})
|
|
}
|
|
|
|
/**
|
|
* @param {string} componentToAdd - name of the component to add to the application
|
|
* @param {string} presetName - name of the component preset if defined
|
|
*/
|
|
const addChildComponent = store => (componentToAdd, presetProps = {}) => {
|
|
store.update(state => {
|
|
function findSlot(component_array) {
|
|
for (let i = 0; i < component_array.length; i += 1) {
|
|
if (component_array[i]._component === "##builtin/screenslot") {
|
|
return true
|
|
}
|
|
|
|
if (component_array[i]._children) findSlot(component_array[i])
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
if (
|
|
componentToAdd.startsWith("##") &&
|
|
findSlot(state.pages[state.currentPageName].props._children)
|
|
) {
|
|
return state
|
|
}
|
|
|
|
const component = getComponentDefinition(state, componentToAdd)
|
|
|
|
const instanceId = get(backendUiStore).selectedDatabase._id
|
|
const instanceName = getNewComponentName(componentToAdd, state)
|
|
|
|
const newComponent = createProps(
|
|
component,
|
|
{
|
|
...presetProps,
|
|
_instanceId: instanceId,
|
|
_instanceName: instanceName,
|
|
},
|
|
state
|
|
)
|
|
|
|
const currentComponent =
|
|
state.components[state.currentComponentInfo._component]
|
|
|
|
const targetParent = currentComponent.children
|
|
? state.currentComponentInfo
|
|
: getParent(state.currentPreviewItem.props, state.currentComponentInfo)
|
|
|
|
targetParent._children = targetParent._children.concat(newComponent.props)
|
|
|
|
state.currentFrontEndType === "page"
|
|
? _savePage(state)
|
|
: _saveScreenApi(state.currentPreviewItem, state)
|
|
|
|
state.currentView = "component"
|
|
state.currentComponentInfo = newComponent.props
|
|
|
|
return state
|
|
})
|
|
}
|
|
|
|
/**
|
|
* @param {string} props - props to add, as child of current component
|
|
*/
|
|
|
|
const addTemplatedComponent = store => props => {
|
|
store.update(state => {
|
|
walkProps(props, p => {
|
|
p._id = uuid()
|
|
})
|
|
state.currentComponentInfo._children = state.currentComponentInfo._children.concat(
|
|
props
|
|
)
|
|
regenerateCssForCurrentScreen(state)
|
|
|
|
setCurrentPageFunctions(state)
|
|
_saveCurrentPreviewItem(state)
|
|
|
|
return state
|
|
})
|
|
}
|
|
|
|
const selectComponent = store => component => {
|
|
store.update(state => {
|
|
return _selectComponent(state, component)
|
|
})
|
|
}
|
|
|
|
const setComponentProp = store => (name, value) => {
|
|
store.update(state => {
|
|
let current_component = state.currentComponentInfo
|
|
current_component[name] = value
|
|
|
|
state.currentComponentInfo = current_component
|
|
_saveCurrentPreviewItem(state)
|
|
return state
|
|
})
|
|
}
|
|
|
|
const setPageOrScreenProp = store => (name, value) => {
|
|
store.update(state => {
|
|
if (name === "_instanceName" && state.currentFrontEndType === "screen") {
|
|
state.currentPreviewItem.props[name] = value
|
|
} else {
|
|
state.currentPreviewItem[name] = value
|
|
}
|
|
_saveCurrentPreviewItem(state)
|
|
return state
|
|
})
|
|
}
|
|
|
|
const setComponentStyle = store => (type, name, value) => {
|
|
store.update(state => {
|
|
if (!state.currentComponentInfo._styles) {
|
|
state.currentComponentInfo._styles = {}
|
|
}
|
|
state.currentComponentInfo._styles[type][name] = value
|
|
|
|
regenerateCssForCurrentScreen(state)
|
|
|
|
// save without messing with the store
|
|
_saveCurrentPreviewItem(state)
|
|
return state
|
|
})
|
|
}
|
|
|
|
const setComponentCode = store => code => {
|
|
store.update(state => {
|
|
state.currentComponentInfo._code = code
|
|
|
|
setCurrentPageFunctions(state)
|
|
// save without messing with the store
|
|
_saveScreenApi(state.currentPreviewItem, state)
|
|
|
|
return state
|
|
})
|
|
}
|
|
|
|
const setCurrentPageFunctions = s => {
|
|
s.currentPageFunctions = buildPageCode(s.screens, s.pages[s.currentPageName])
|
|
insertCodeMetadata(s.currentPreviewItem.props)
|
|
}
|
|
|
|
const buildPageCode = (screens, page) => buildCodeForScreens([page, ...screens])
|
|
|
|
const setScreenType = store => type => {
|
|
store.update(state => {
|
|
state.currentFrontEndType = type
|
|
|
|
const pageOrScreen =
|
|
type === "page"
|
|
? state.pages[state.currentPageName]
|
|
: state.pages[state.currentPageName]._screens[0]
|
|
|
|
state.currentComponentInfo = pageOrScreen ? pageOrScreen.props : null
|
|
state.currentPreviewItem = pageOrScreen
|
|
state.currentView = "detail"
|
|
return state
|
|
})
|
|
}
|
|
|
|
const editPageOrScreen = store => (key, value, setOnComponent = false) => {
|
|
store.update(state => {
|
|
setOnComponent
|
|
? (state.currentPreviewItem.props[key] = value)
|
|
: (state.currentPreviewItem[key] = value)
|
|
_saveCurrentPreviewItem(state)
|
|
|
|
return state
|
|
})
|
|
}
|
|
|
|
const getPathToComponent = store => component => {
|
|
// Gets all the components to needed to construct a path.
|
|
const tempStore = get(store)
|
|
let pathComponents = []
|
|
let parent = component
|
|
let root = false
|
|
while (!root) {
|
|
parent = getParent(tempStore.currentPreviewItem.props, parent)
|
|
if (!parent) {
|
|
root = true
|
|
} else {
|
|
pathComponents.push(parent)
|
|
}
|
|
}
|
|
|
|
// Remove root entry since it's the screen or page layout.
|
|
// Reverse array since we need the correct order of the IDs
|
|
const reversedComponents = pathComponents.reverse().slice(1)
|
|
|
|
// Add component
|
|
const allComponents = [...reversedComponents, component]
|
|
|
|
// Map IDs
|
|
const IdList = allComponents.map(c => c._id)
|
|
|
|
// Construct ID Path:
|
|
const path = IdList.join("/")
|
|
|
|
return path
|
|
}
|
|
|
|
const setMetadataProp = store => (name, prop) => {
|
|
store.update(s => {
|
|
s.currentPreviewItem[name] = prop
|
|
return s
|
|
})
|
|
}
|
|
|
|
const storeComponentForCopy = store => (component, cut = false) => {
|
|
store.update(s => {
|
|
const copiedComponent = cloneDeep(component)
|
|
s.componentToPaste = copiedComponent
|
|
s.componentToPaste.isCut = cut
|
|
if (cut) {
|
|
const parent = getParent(s.currentPreviewItem.props, component._id)
|
|
parent._children = parent._children.filter(c => c._id !== component._id)
|
|
selectComponent(s, parent)
|
|
}
|
|
|
|
return s
|
|
})
|
|
}
|
|
|
|
const pasteComponent = store => (targetComponent, mode) => {
|
|
store.update(s => {
|
|
if (!s.componentToPaste) return s
|
|
|
|
const componentToPaste = cloneDeep(s.componentToPaste)
|
|
// retain the same ids as things may be referencing this component
|
|
if (componentToPaste.isCut) {
|
|
// in case we paste a second time
|
|
s.componentToPaste.isCut = false
|
|
} else {
|
|
generateNewIdsForComponent(componentToPaste, s)
|
|
}
|
|
delete componentToPaste.isCut
|
|
|
|
if (mode === "inside") {
|
|
targetComponent._children.push(componentToPaste)
|
|
return s
|
|
}
|
|
|
|
const parent = getParent(s.currentPreviewItem.props, targetComponent)
|
|
|
|
const targetIndex = parent._children.indexOf(targetComponent)
|
|
const index = mode === "above" ? targetIndex : targetIndex + 1
|
|
parent._children.splice(index, 0, cloneDeep(componentToPaste))
|
|
regenerateCssForCurrentScreen(s)
|
|
_saveCurrentPreviewItem(s)
|
|
selectComponent(s, componentToPaste)
|
|
|
|
return s
|
|
})
|
|
}
|