Merge pull request #826 from Budibase/tidy-up-store
Pages and Screens to couch as well as general store tidy up
This commit is contained in:
commit
960ca9df8e
|
@ -1,33 +0,0 @@
|
||||||
const buildCodeForSingleScreen = screen => {
|
|
||||||
let code = ""
|
|
||||||
const walkProps = props => {
|
|
||||||
if (props._code && props._code.trim().length > 0) {
|
|
||||||
code += buildComponentCode(props)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!props._children) return
|
|
||||||
|
|
||||||
for (let child of props._children) {
|
|
||||||
walkProps(child)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
walkProps(screen.props)
|
|
||||||
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
export const buildCodeForScreens = screens => {
|
|
||||||
let allfunctions = ""
|
|
||||||
for (let screen of screens) {
|
|
||||||
allfunctions += buildCodeForSingleScreen(screen)
|
|
||||||
}
|
|
||||||
|
|
||||||
return `({ ${allfunctions} });`
|
|
||||||
}
|
|
||||||
|
|
||||||
const buildComponentCode = componentProps =>
|
|
||||||
`"${componentProps._id}" : (render, context, state, route) => {
|
|
||||||
${componentProps._code}
|
|
||||||
},
|
|
||||||
`
|
|
|
@ -1,5 +1,7 @@
|
||||||
import { walkProps } from "./storeUtils"
|
import { walkProps } from "./storeUtils"
|
||||||
import { get_capitalised_name } from "../helpers"
|
import { get_capitalised_name } from "../helpers"
|
||||||
|
import { get } from "svelte/store"
|
||||||
|
import { allScreens } from "builderStore"
|
||||||
|
|
||||||
export default function(component, state) {
|
export default function(component, state) {
|
||||||
const capitalised = get_capitalised_name(
|
const capitalised = get_capitalised_name(
|
||||||
|
@ -25,7 +27,7 @@ export default function(component, state) {
|
||||||
findMatches(state.currentPreviewItem.props)
|
findMatches(state.currentPreviewItem.props)
|
||||||
} else {
|
} else {
|
||||||
// viewing master page - need to find against all screens
|
// viewing master page - need to find against all screens
|
||||||
for (let screen of state.screens) {
|
for (let screen of get(allScreens)) {
|
||||||
findMatches(screen.props)
|
findMatches(screen.props)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,7 +35,7 @@ export default function(component, state) {
|
||||||
let index = 1
|
let index = 1
|
||||||
let name
|
let name
|
||||||
while (!name) {
|
while (!name) {
|
||||||
const tryName = `${capitalised} ${index}`
|
const tryName = `${capitalised || "Copy"} ${index}`
|
||||||
if (!matchingComponents.includes(tryName)) name = tryName
|
if (!matchingComponents.includes(tryName)) name = tryName
|
||||||
index++
|
index++
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,36 @@
|
||||||
import { getStore } from "./store"
|
import { getFrontendStore } from "./store/frontend"
|
||||||
import { getBackendUiStore } from "./store/backend"
|
import { getBackendUiStore } from "./store/backend"
|
||||||
import { getAutomationStore } from "./store/automation/"
|
import { getAutomationStore } from "./store/automation/"
|
||||||
import { getThemeStore } from "./store/theme"
|
import { getThemeStore } from "./store/theme"
|
||||||
|
import { derived } from "svelte/store"
|
||||||
import analytics from "analytics"
|
import analytics from "analytics"
|
||||||
|
|
||||||
export const store = getStore()
|
export const store = getFrontendStore()
|
||||||
export const backendUiStore = getBackendUiStore()
|
export const backendUiStore = getBackendUiStore()
|
||||||
export const automationStore = getAutomationStore()
|
export const automationStore = getAutomationStore()
|
||||||
export const themeStore = getThemeStore()
|
export const themeStore = getThemeStore()
|
||||||
|
|
||||||
|
export const allScreens = derived(store, $store => {
|
||||||
|
let screens = []
|
||||||
|
if ($store.pages == null) {
|
||||||
|
return screens
|
||||||
|
}
|
||||||
|
for (let page of Object.values($store.pages)) {
|
||||||
|
screens = screens.concat(page._screens)
|
||||||
|
}
|
||||||
|
return screens
|
||||||
|
})
|
||||||
|
|
||||||
|
export const currentScreens = derived(store, $store => {
|
||||||
|
const currentScreens = $store.pages[$store.currentPageName]?._screens
|
||||||
|
if (currentScreens == null) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
return Array.isArray(currentScreens)
|
||||||
|
? currentScreens
|
||||||
|
: Object.values(currentScreens)
|
||||||
|
})
|
||||||
|
|
||||||
export const initialise = async () => {
|
export const initialise = async () => {
|
||||||
try {
|
try {
|
||||||
await analytics.activate()
|
await analytics.activate()
|
||||||
|
|
|
@ -1,17 +0,0 @@
|
||||||
export const insertCodeMetadata = props => {
|
|
||||||
if (props._code && props._code.length > 0) {
|
|
||||||
props._codeMeta = codeMetaData(props._code)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!props._children || props._children.length === 0) return
|
|
||||||
|
|
||||||
for (let child of props._children) {
|
|
||||||
insertCodeMetadata(child)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const codeMetaData = code => {
|
|
||||||
return {
|
|
||||||
dependsOnStore: RegExp(/(state.)/g).test(code),
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,529 @@
|
||||||
|
import { get, writable } from "svelte/store"
|
||||||
|
import { cloneDeep } from "lodash/fp"
|
||||||
|
import {
|
||||||
|
createProps,
|
||||||
|
getBuiltin,
|
||||||
|
makePropsSafe,
|
||||||
|
} from "components/userInterface/pagesParsing/createProps"
|
||||||
|
import { getExactComponent } from "components/userInterface/pagesParsing/searchComponents"
|
||||||
|
import { allScreens, backendUiStore } from "builderStore"
|
||||||
|
import { generate_screen_css } from "../generate_css"
|
||||||
|
import { fetchComponentLibDefinitions } from "../loadComponentLibraries"
|
||||||
|
import api from "../api"
|
||||||
|
import { DEFAULT_PAGES_OBJECT } from "../../constants"
|
||||||
|
import getNewComponentName from "../getNewComponentName"
|
||||||
|
import analytics from "analytics"
|
||||||
|
import {
|
||||||
|
findChildComponentType,
|
||||||
|
generateNewIdsForComponent,
|
||||||
|
getComponentDefinition,
|
||||||
|
getParent,
|
||||||
|
} from "../storeUtils"
|
||||||
|
|
||||||
|
const INITIAL_FRONTEND_STATE = {
|
||||||
|
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: "",
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getFrontendStore = () => {
|
||||||
|
const store = writable({ ...INITIAL_FRONTEND_STATE })
|
||||||
|
|
||||||
|
store.actions = {
|
||||||
|
// TODO: REFACTOR
|
||||||
|
initialise: async pkg => {
|
||||||
|
store.update(state => {
|
||||||
|
state.appId = pkg.application._id
|
||||||
|
return state
|
||||||
|
})
|
||||||
|
const screens = await api.get("/api/screens").then(r => r.json())
|
||||||
|
|
||||||
|
const mainScreens = screens.filter(screen =>
|
||||||
|
screen._id.includes(pkg.pages.main._id)
|
||||||
|
),
|
||||||
|
unauthScreens = screens.filter(screen =>
|
||||||
|
screen._id.includes(pkg.pages.unauthenticated._id)
|
||||||
|
)
|
||||||
|
pkg.pages = {
|
||||||
|
main: {
|
||||||
|
...pkg.pages.main,
|
||||||
|
_screens: mainScreens,
|
||||||
|
},
|
||||||
|
unauthenticated: {
|
||||||
|
...pkg.pages.unauthenticated,
|
||||||
|
_screens: unauthScreens,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the app has just been created
|
||||||
|
// we need to build the CSS and save
|
||||||
|
if (pkg.justCreated) {
|
||||||
|
for (let pageName of ["main", "unauthenticated"]) {
|
||||||
|
const page = pkg.pages[pageName]
|
||||||
|
store.actions.screens.regenerateCss(page)
|
||||||
|
for (let screen of page._screens) {
|
||||||
|
store.actions.screens.regenerateCss(screen)
|
||||||
|
}
|
||||||
|
|
||||||
|
await api.post(`/api/pages/${page._id}`, {
|
||||||
|
page: {
|
||||||
|
componentLibraries: pkg.application.componentLibraries,
|
||||||
|
...page,
|
||||||
|
},
|
||||||
|
screens: page._screens,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pkg.justCreated = false
|
||||||
|
|
||||||
|
const components = await fetchComponentLibDefinitions(pkg.application._id)
|
||||||
|
|
||||||
|
store.update(state => ({
|
||||||
|
...state,
|
||||||
|
libraries: pkg.application.componentLibraries,
|
||||||
|
components,
|
||||||
|
name: pkg.application.name,
|
||||||
|
description: pkg.application.description,
|
||||||
|
appId: pkg.application._id,
|
||||||
|
pages: pkg.pages,
|
||||||
|
hasAppPackage: true,
|
||||||
|
currentScreens: [],
|
||||||
|
builtins: [getBuiltin("##builtin/screenslot")],
|
||||||
|
appInstance: pkg.application.instance,
|
||||||
|
}))
|
||||||
|
|
||||||
|
await backendUiStore.actions.database.select(pkg.application.instance)
|
||||||
|
},
|
||||||
|
selectPageOrScreen: 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
|
||||||
|
})
|
||||||
|
},
|
||||||
|
screens: {
|
||||||
|
select: screenName => {
|
||||||
|
store.update(state => {
|
||||||
|
const screen = getExactComponent(get(allScreens), screenName, true)
|
||||||
|
state.currentPreviewItem = screen
|
||||||
|
state.currentFrontEndType = "screen"
|
||||||
|
state.currentView = "detail"
|
||||||
|
|
||||||
|
store.actions.screens.regenerateCssForCurrentScreen()
|
||||||
|
// this.regenerateCssForCurrentScreen()
|
||||||
|
// regenerateCssForCurrentScreen(s)
|
||||||
|
const safeProps = makePropsSafe(
|
||||||
|
state.components[screen.props._component],
|
||||||
|
screen.props
|
||||||
|
)
|
||||||
|
screen.props = safeProps
|
||||||
|
state.currentComponentInfo = safeProps
|
||||||
|
return state
|
||||||
|
})
|
||||||
|
},
|
||||||
|
create: async screen => {
|
||||||
|
let savePromise
|
||||||
|
store.update(state => {
|
||||||
|
state.currentPreviewItem = screen
|
||||||
|
state.currentComponentInfo = screen.props
|
||||||
|
state.currentFrontEndType = "screen"
|
||||||
|
|
||||||
|
if (state.currentPreviewItem) {
|
||||||
|
store.actions.screens.regenerateCss(state.currentPreviewItem)
|
||||||
|
}
|
||||||
|
|
||||||
|
savePromise = store.actions.screens.save(screen)
|
||||||
|
return state
|
||||||
|
})
|
||||||
|
|
||||||
|
await savePromise
|
||||||
|
},
|
||||||
|
save: async screen => {
|
||||||
|
const storeContents = get(store)
|
||||||
|
const pageName = storeContents.currentPageName || "main"
|
||||||
|
const currentPage = storeContents.pages[pageName]
|
||||||
|
const currentPageScreens = currentPage._screens
|
||||||
|
|
||||||
|
let savePromise
|
||||||
|
const response = await api.post(
|
||||||
|
`/api/screens/${currentPage._id}`,
|
||||||
|
screen
|
||||||
|
)
|
||||||
|
const json = await response.json()
|
||||||
|
screen._rev = json.rev
|
||||||
|
screen._id = json.id
|
||||||
|
const foundScreen = currentPageScreens.findIndex(
|
||||||
|
el => el._id === screen._id
|
||||||
|
)
|
||||||
|
if (currentPageScreens !== -1) {
|
||||||
|
currentPageScreens.splice(foundScreen, 1)
|
||||||
|
}
|
||||||
|
currentPageScreens.push(screen)
|
||||||
|
|
||||||
|
// TODO: should carry out all server updates to screen in a single call
|
||||||
|
store.update(state => {
|
||||||
|
state.pages[pageName]._screens = currentPageScreens
|
||||||
|
state.currentPreviewItem = screen
|
||||||
|
const safeProps = makePropsSafe(
|
||||||
|
state.components[screen.props._component],
|
||||||
|
screen.props
|
||||||
|
)
|
||||||
|
state.currentComponentInfo = safeProps
|
||||||
|
screen.props = safeProps
|
||||||
|
savePromise = store.actions.pages.save()
|
||||||
|
return state
|
||||||
|
})
|
||||||
|
await savePromise
|
||||||
|
},
|
||||||
|
regenerateCss: screen => {
|
||||||
|
screen._css = generate_screen_css([screen.props])
|
||||||
|
},
|
||||||
|
regenerateCssForCurrentScreen: () => {
|
||||||
|
const { currentPreviewItem } = get(store)
|
||||||
|
if (currentPreviewItem) {
|
||||||
|
store.actions.screens.regenerateCss(currentPreviewItem)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
delete: async (screensToDelete, pageName) => {
|
||||||
|
let deletePromise
|
||||||
|
|
||||||
|
store.update(state => {
|
||||||
|
if (pageName == null) {
|
||||||
|
pageName = state.pages.main.name
|
||||||
|
}
|
||||||
|
for (let screenToDelete of Array.isArray(screenToDelete)
|
||||||
|
? screenToDelete
|
||||||
|
: [screenToDelete]) {
|
||||||
|
// Remove screen from current page as well
|
||||||
|
// TODO: Should be done server side
|
||||||
|
state.pages[pageName]._screens = state.pages[
|
||||||
|
pageName
|
||||||
|
]._screens.filter(scr => scr.name !== screenToDelete.name)
|
||||||
|
deletePromise = api.delete(
|
||||||
|
`/api/screens/${screenToDelete._id}/${screenToDelete._rev}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return state
|
||||||
|
})
|
||||||
|
await deletePromise
|
||||||
|
},
|
||||||
|
},
|
||||||
|
preview: {
|
||||||
|
// _saveCurrentPreviewItem
|
||||||
|
saveSelected: () => {
|
||||||
|
const state = get(store)
|
||||||
|
state.currentFrontEndType === "page"
|
||||||
|
? store.actions.pages.save()
|
||||||
|
: store.actions.screens.save(state.currentPreviewItem)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
pages: {
|
||||||
|
select: pageName => {
|
||||||
|
store.update(state => {
|
||||||
|
const currentPage = state.pages[pageName]
|
||||||
|
|
||||||
|
state.currentScreens = currentPage._screens
|
||||||
|
state.currentFrontEndType = "page"
|
||||||
|
state.currentView = "detail"
|
||||||
|
state.currentPageName = pageName
|
||||||
|
|
||||||
|
// This is the root of many problems.
|
||||||
|
// Uncaught (in promise) TypeError: Cannot read property '_component' of undefined
|
||||||
|
// it appears that the currentPage sometimes has _props instead of props
|
||||||
|
// why
|
||||||
|
const safeProps = makePropsSafe(
|
||||||
|
state.components[currentPage.props._component],
|
||||||
|
currentPage.props
|
||||||
|
)
|
||||||
|
state.currentComponentInfo = safeProps
|
||||||
|
currentPage.props = safeProps
|
||||||
|
state.currentPreviewItem = state.pages[pageName]
|
||||||
|
store.actions.screens.regenerateCssForCurrentScreen()
|
||||||
|
|
||||||
|
for (let screen of get(allScreens)) {
|
||||||
|
screen._css = generate_screen_css([screen.props])
|
||||||
|
}
|
||||||
|
|
||||||
|
return state
|
||||||
|
})
|
||||||
|
},
|
||||||
|
save: async page => {
|
||||||
|
const storeContents = get(store)
|
||||||
|
const pageName = storeContents.currentPageName || "main"
|
||||||
|
const pageToSave = page || storeContents.pages[pageName]
|
||||||
|
|
||||||
|
// TODO: revisit. This sends down a very weird payload
|
||||||
|
const response = await api
|
||||||
|
.post(`/api/pages/${pageToSave._id}`, {
|
||||||
|
page: {
|
||||||
|
componentLibraries: storeContents.pages.componentLibraries,
|
||||||
|
...pageToSave,
|
||||||
|
},
|
||||||
|
screens: pageToSave._screens,
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
|
||||||
|
store.update(state => {
|
||||||
|
state.pages[pageName]._rev = response.rev
|
||||||
|
return state
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
select: component => {
|
||||||
|
store.update(state => {
|
||||||
|
const componentDef = component._component.startsWith("##")
|
||||||
|
? component
|
||||||
|
: state.components[component._component]
|
||||||
|
state.currentComponentInfo = makePropsSafe(componentDef, component)
|
||||||
|
state.currentView = "component"
|
||||||
|
return state
|
||||||
|
})
|
||||||
|
},
|
||||||
|
// addChildComponent
|
||||||
|
create: (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(component, state)
|
||||||
|
|
||||||
|
const newComponent = createProps(component, {
|
||||||
|
...presetProps,
|
||||||
|
_instanceId: instanceId,
|
||||||
|
_instanceName: instanceName,
|
||||||
|
})
|
||||||
|
|
||||||
|
const currentComponent =
|
||||||
|
state.components[state.currentComponentInfo._component]
|
||||||
|
|
||||||
|
const targetParent = currentComponent.children
|
||||||
|
? state.currentComponentInfo
|
||||||
|
: getParent(
|
||||||
|
state.currentPreviewItem.props,
|
||||||
|
state.currentComponentInfo
|
||||||
|
)
|
||||||
|
|
||||||
|
// Don't continue if there's no parent
|
||||||
|
if (!targetParent) {
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
|
||||||
|
targetParent._children = targetParent._children.concat(
|
||||||
|
newComponent.props
|
||||||
|
)
|
||||||
|
|
||||||
|
store.actions.preview.saveSelected()
|
||||||
|
|
||||||
|
state.currentView = "component"
|
||||||
|
state.currentComponentInfo = newComponent.props
|
||||||
|
analytics.captureEvent("Added Component", {
|
||||||
|
name: newComponent.props._component,
|
||||||
|
})
|
||||||
|
return state
|
||||||
|
})
|
||||||
|
},
|
||||||
|
copy: (component, cut = false) => {
|
||||||
|
store.update(state => {
|
||||||
|
state.componentToPaste = cloneDeep(component)
|
||||||
|
state.componentToPaste.isCut = cut
|
||||||
|
if (cut) {
|
||||||
|
const parent = getParent(
|
||||||
|
state.currentPreviewItem.props,
|
||||||
|
component._id
|
||||||
|
)
|
||||||
|
parent._children = parent._children.filter(
|
||||||
|
c => c._id !== component._id
|
||||||
|
)
|
||||||
|
store.actions.components.select(parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
return state
|
||||||
|
})
|
||||||
|
},
|
||||||
|
paste: (targetComponent, mode) => {
|
||||||
|
store.update(state => {
|
||||||
|
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
|
||||||
|
} else {
|
||||||
|
generateNewIdsForComponent(componentToPaste, state)
|
||||||
|
}
|
||||||
|
delete componentToPaste.isCut
|
||||||
|
|
||||||
|
if (mode === "inside") {
|
||||||
|
targetComponent._children.push(componentToPaste)
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
|
||||||
|
const parent = getParent(
|
||||||
|
state.currentPreviewItem.props,
|
||||||
|
targetComponent
|
||||||
|
)
|
||||||
|
|
||||||
|
const targetIndex = parent._children.indexOf(targetComponent)
|
||||||
|
const index = mode === "above" ? targetIndex : targetIndex + 1
|
||||||
|
parent._children.splice(index, 0, cloneDeep(componentToPaste))
|
||||||
|
|
||||||
|
store.actions.screens.regenerateCssForCurrentScreen()
|
||||||
|
store.actions.preview.saveSelected()
|
||||||
|
store.actions.components.select(componentToPaste)
|
||||||
|
|
||||||
|
return state
|
||||||
|
})
|
||||||
|
},
|
||||||
|
updateStyle: (type, name, value) => {
|
||||||
|
store.update(state => {
|
||||||
|
if (!state.currentComponentInfo._styles) {
|
||||||
|
state.currentComponentInfo._styles = {}
|
||||||
|
}
|
||||||
|
state.currentComponentInfo._styles[type][name] = value
|
||||||
|
|
||||||
|
store.actions.screens.regenerateCssForCurrentScreen()
|
||||||
|
|
||||||
|
// save without messing with the store
|
||||||
|
store.actions.preview.saveSelected()
|
||||||
|
return state
|
||||||
|
})
|
||||||
|
},
|
||||||
|
updateProp: (name, value) => {
|
||||||
|
store.update(state => {
|
||||||
|
let current_component = state.currentComponentInfo
|
||||||
|
current_component[name] = value
|
||||||
|
|
||||||
|
state.currentComponentInfo = current_component
|
||||||
|
store.actions.preview.saveSelected()
|
||||||
|
return state
|
||||||
|
})
|
||||||
|
},
|
||||||
|
findRoute: 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:
|
||||||
|
return IdList.join("/")
|
||||||
|
},
|
||||||
|
links: {
|
||||||
|
save: async (url, title) => {
|
||||||
|
let savePromise
|
||||||
|
store.update(state => {
|
||||||
|
// Try to extract a nav component from the master screen
|
||||||
|
const nav = findChildComponentType(
|
||||||
|
state.pages.main,
|
||||||
|
"@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 page and regenerate all CSS because otherwise weird things happen
|
||||||
|
nav._children = [...nav._children, newLink]
|
||||||
|
state.currentPageName = "main"
|
||||||
|
store.actions.screens.regenerateCss(state.pages.main)
|
||||||
|
for (let screen of state.pages.main._screens) {
|
||||||
|
store.actions.screens.regenerateCss(screen)
|
||||||
|
}
|
||||||
|
savePromise = store.actions.pages.save()
|
||||||
|
}
|
||||||
|
return state
|
||||||
|
})
|
||||||
|
await savePromise
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return store
|
||||||
|
}
|
|
@ -1,607 +0,0 @@
|
||||||
import { 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 analytics from "analytics"
|
|
||||||
import { uuid } from "../uuid"
|
|
||||||
import {
|
|
||||||
selectComponent as _selectComponent,
|
|
||||||
getParent,
|
|
||||||
walkProps,
|
|
||||||
savePage as _savePage,
|
|
||||||
saveCurrentPreviewItem as _saveCurrentPreviewItem,
|
|
||||||
saveScreenApi as _saveScreenApi,
|
|
||||||
regenerateCssForCurrentScreen,
|
|
||||||
regenerateCssForScreen,
|
|
||||||
generateNewIdsForComponent,
|
|
||||||
getComponentDefinition,
|
|
||||||
findChildComponentType,
|
|
||||||
} 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.deleteScreens = deleteScreens(store)
|
|
||||||
store.setCurrentPage = setCurrentPage(store)
|
|
||||||
store.createLink = createLink(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),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// if the app has just been created
|
|
||||||
// we need to build the CSS and save
|
|
||||||
if (pkg.justCreated) {
|
|
||||||
const generateInitialPageCss = async name => {
|
|
||||||
const page = pkg.pages[name]
|
|
||||||
regenerateCssForScreen(page)
|
|
||||||
for (let screen of page._screens) {
|
|
||||||
regenerateCssForScreen(screen)
|
|
||||||
}
|
|
||||||
|
|
||||||
await api.post(`/_builder/api/${pkg.application._id}/pages/${name}`, {
|
|
||||||
page: {
|
|
||||||
componentLibraries: pkg.application.componentLibraries,
|
|
||||||
...page,
|
|
||||||
},
|
|
||||||
screens: page._screens,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
generateInitialPageCss("main")
|
|
||||||
generateInitialPageCss("unauthenticated")
|
|
||||||
pkg.justCreated = false
|
|
||||||
}
|
|
||||||
|
|
||||||
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 = [
|
|
||||||
...Object.values(main_screens),
|
|
||||||
...Object.values(unauth_screens),
|
|
||||||
]
|
|
||||||
initial.builtins = [getBuiltin("##builtin/screenslot")]
|
|
||||||
initial.appInstance = pkg.application.instance
|
|
||||||
initial.appId = pkg.application._id
|
|
||||||
store.set(initial)
|
|
||||||
await backendUiStore.actions.database.select(initial.appInstance)
|
|
||||||
return initial
|
|
||||||
}
|
|
||||||
|
|
||||||
const saveScreen = store => screen => {
|
|
||||||
store.update(state => {
|
|
||||||
return _saveScreen(store, state, screen)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const _saveScreen = async (store, s, screen) => {
|
|
||||||
const pageName = s.currentPageName || "main"
|
|
||||||
const currentPageScreens = s.pages[pageName]._screens
|
|
||||||
|
|
||||||
await api
|
|
||||||
.post(`/_builder/api/${s.appId}/pages/${pageName}/screen`, screen)
|
|
||||||
.then(() => {
|
|
||||||
if (currentPageScreens.includes(screen)) return
|
|
||||||
|
|
||||||
const screens = [...currentPageScreens, screen]
|
|
||||||
|
|
||||||
store.update(innerState => {
|
|
||||||
innerState.pages[pageName]._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 => async screen => {
|
|
||||||
let savePromise
|
|
||||||
store.update(state => {
|
|
||||||
state.currentPreviewItem = screen
|
|
||||||
state.currentComponentInfo = screen.props
|
|
||||||
state.currentFrontEndType = "screen"
|
|
||||||
regenerateCssForCurrentScreen(state)
|
|
||||||
savePromise = _saveScreen(store, state, screen)
|
|
||||||
return state
|
|
||||||
})
|
|
||||||
await savePromise
|
|
||||||
}
|
|
||||||
|
|
||||||
const createLink = store => async (url, title) => {
|
|
||||||
let savePromise
|
|
||||||
store.update(state => {
|
|
||||||
// Try to extract a nav component from the master screen
|
|
||||||
const nav = findChildComponentType(
|
|
||||||
state.pages.main,
|
|
||||||
"@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 page and regenerate all CSS because otherwise weird things happen
|
|
||||||
nav._children = [...nav._children, newLink]
|
|
||||||
state.currentPageName = "main"
|
|
||||||
regenerateCssForScreen(state.pages.main)
|
|
||||||
for (let screen of state.pages.main._screens) {
|
|
||||||
regenerateCssForScreen(screen)
|
|
||||||
}
|
|
||||||
savePromise = _savePage(state)
|
|
||||||
}
|
|
||||||
return state
|
|
||||||
})
|
|
||||||
await savePromise
|
|
||||||
}
|
|
||||||
|
|
||||||
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 deleteScreens = store => (screens, pageName = null) => {
|
|
||||||
if (!(screens instanceof Array)) {
|
|
||||||
screens = [screens]
|
|
||||||
}
|
|
||||||
store.update(state => {
|
|
||||||
if (pageName == null) {
|
|
||||||
pageName = state.pages.main.name
|
|
||||||
}
|
|
||||||
for (let screen of screens) {
|
|
||||||
state.screens = state.screens.filter(c => c.name !== screen.name)
|
|
||||||
// Remove screen from current page as well
|
|
||||||
state.pages[pageName]._screens = state.pages[pageName]._screens.filter(
|
|
||||||
scr => scr.name !== screen.name
|
|
||||||
)
|
|
||||||
api.delete(`/_builder/api/pages/${pageName}/screens/${screen.name}`)
|
|
||||||
}
|
|
||||||
return state
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
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(component, 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)
|
|
||||||
|
|
||||||
// Don't continue if there's no parent
|
|
||||||
if (!targetParent) {
|
|
||||||
return state
|
|
||||||
}
|
|
||||||
|
|
||||||
targetParent._children = targetParent._children.concat(newComponent.props)
|
|
||||||
|
|
||||||
state.currentFrontEndType === "page"
|
|
||||||
? _savePage(state)
|
|
||||||
: _saveScreenApi(state.currentPreviewItem, state)
|
|
||||||
|
|
||||||
state.currentView = "component"
|
|
||||||
state.currentComponentInfo = newComponent.props
|
|
||||||
analytics.captureEvent("Added Component", {
|
|
||||||
name: newComponent.props._component,
|
|
||||||
})
|
|
||||||
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
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -1,21 +1,7 @@
|
||||||
import {
|
import { getBuiltin } from "components/userInterface/pagesParsing/createProps"
|
||||||
makePropsSafe,
|
|
||||||
getBuiltin,
|
|
||||||
} from "components/userInterface/pagesParsing/createProps"
|
|
||||||
import api from "./api"
|
|
||||||
import { generate_screen_css } from "./generate_css"
|
|
||||||
import { uuid } from "./uuid"
|
import { uuid } from "./uuid"
|
||||||
import getNewComponentName from "./getNewComponentName"
|
import getNewComponentName from "./getNewComponentName"
|
||||||
|
|
||||||
export const selectComponent = (state, component) => {
|
|
||||||
const componentDef = component._component.startsWith("##")
|
|
||||||
? component
|
|
||||||
: state.components[component._component]
|
|
||||||
state.currentComponentInfo = makePropsSafe(componentDef, component)
|
|
||||||
state.currentView = "component"
|
|
||||||
return state
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getParent = (rootProps, child) => {
|
export const getParent = (rootProps, child) => {
|
||||||
let parent
|
let parent
|
||||||
walkProps(rootProps, (p, breakWalk) => {
|
walkProps(rootProps, (p, breakWalk) => {
|
||||||
|
@ -30,41 +16,6 @@ export const getParent = (rootProps, child) => {
|
||||||
return parent
|
return parent
|
||||||
}
|
}
|
||||||
|
|
||||||
export const saveCurrentPreviewItem = s =>
|
|
||||||
s.currentFrontEndType === "page"
|
|
||||||
? savePage(s)
|
|
||||||
: saveScreenApi(s.currentPreviewItem, s)
|
|
||||||
|
|
||||||
export const savePage = async s => {
|
|
||||||
const pageName = s.currentPageName || "main"
|
|
||||||
const page = s.pages[pageName]
|
|
||||||
await api.post(`/_builder/api/${s.appId}/pages/${pageName}`, {
|
|
||||||
page: { componentLibraries: s.pages.componentLibraries, ...page },
|
|
||||||
uiFunctions: s.currentPageFunctions,
|
|
||||||
screens: page._screens,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export const saveScreenApi = (screen, s) => {
|
|
||||||
api
|
|
||||||
.post(`/_builder/api/${s.appId}/pages/${s.currentPageName}/screen`, screen)
|
|
||||||
.then(() => savePage(s))
|
|
||||||
}
|
|
||||||
|
|
||||||
export const renameCurrentScreen = (newname, state) => {
|
|
||||||
const oldname = state.currentPreviewItem.props._instanceName
|
|
||||||
state.currentPreviewItem.props._instanceName = newname
|
|
||||||
|
|
||||||
api.patch(
|
|
||||||
`/_builder/api/${state.appId}/pages/${state.currentPageName}/screen`,
|
|
||||||
{
|
|
||||||
oldname,
|
|
||||||
newname,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
return state
|
|
||||||
}
|
|
||||||
|
|
||||||
export const walkProps = (props, action, cancelToken = null) => {
|
export const walkProps = (props, action, cancelToken = null) => {
|
||||||
cancelToken = cancelToken || { cancelled: false }
|
cancelToken = cancelToken || { cancelled: false }
|
||||||
action(props, () => {
|
action(props, () => {
|
||||||
|
@ -79,21 +30,14 @@ export const walkProps = (props, action, cancelToken = null) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const regenerateCssForScreen = screen => {
|
export const generateNewIdsForComponent = (
|
||||||
screen._css = generate_screen_css([screen.props])
|
component,
|
||||||
}
|
state,
|
||||||
|
changeName = true
|
||||||
export const regenerateCssForCurrentScreen = state => {
|
) =>
|
||||||
if (state.currentPreviewItem) {
|
walkProps(component, prop => {
|
||||||
regenerateCssForScreen(state.currentPreviewItem)
|
prop._id = uuid()
|
||||||
}
|
if (changeName) prop._instanceName = getNewComponentName(prop, state)
|
||||||
return state
|
|
||||||
}
|
|
||||||
|
|
||||||
export const generateNewIdsForComponent = (c, state, changeName = true) =>
|
|
||||||
walkProps(c, p => {
|
|
||||||
p._id = uuid()
|
|
||||||
if (changeName) p._instanceName = getNewComponentName(p._component, state)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
export const getComponentDefinition = (state, name) =>
|
export const getComponentDefinition = (state, name) =>
|
||||||
|
|
|
@ -55,7 +55,7 @@
|
||||||
// Record the table that created this screen so we can link it later
|
// Record the table that created this screen so we can link it later
|
||||||
screen.autoTableId = table._id
|
screen.autoTableId = table._id
|
||||||
try {
|
try {
|
||||||
await store.createScreen(screen)
|
await store.actions.screens.create(screen)
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
// TODO: this is temporary
|
// TODO: this is temporary
|
||||||
// a cypress test is failing, because I added the
|
// a cypress test is failing, because I added the
|
||||||
|
@ -70,7 +70,7 @@
|
||||||
const listPage = screens.find(screen =>
|
const listPage = screens.find(screen =>
|
||||||
screen.props._instanceName.endsWith("List")
|
screen.props._instanceName.endsWith("List")
|
||||||
)
|
)
|
||||||
await store.createLink(listPage.route, table.name)
|
await store.actions.components.links.save(listPage.route, table.name)
|
||||||
|
|
||||||
// Navigate to new table
|
// Navigate to new table
|
||||||
$goto(`./table/${table._id}`)
|
$goto(`./table/${table._id}`)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import { backendUiStore, store } from "builderStore"
|
import { backendUiStore, store, allScreens } from "builderStore"
|
||||||
import { notifier } from "builderStore/store/notifications"
|
import { notifier } from "builderStore/store/notifications"
|
||||||
import { DropdownMenu, Button, Input } from "@budibase/bbui"
|
import { DropdownMenu, Button, Input } from "@budibase/bbui"
|
||||||
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
||||||
|
@ -28,7 +28,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function showModal() {
|
function showModal() {
|
||||||
const screens = $store.screens
|
const screens = $allScreens
|
||||||
templateScreens = screens.filter(screen => screen.autoTableId === table._id)
|
templateScreens = screens.filter(screen => screen.autoTableId === table._id)
|
||||||
willBeDeleted = ["All table data"].concat(
|
willBeDeleted = ["All table data"].concat(
|
||||||
templateScreens.map(screen => `Screen ${screen.props._instanceName}`)
|
templateScreens.map(screen => `Screen ${screen.props._instanceName}`)
|
||||||
|
@ -39,7 +39,7 @@
|
||||||
|
|
||||||
async function deleteTable() {
|
async function deleteTable() {
|
||||||
await backendUiStore.actions.tables.delete(table)
|
await backendUiStore.actions.tables.delete(table)
|
||||||
store.deleteScreens(templateScreens)
|
store.store.actions.screens.delete(templateScreens)
|
||||||
await backendUiStore.actions.tables.fetch()
|
await backendUiStore.actions.tables.fetch()
|
||||||
notifier.success("Table deleted")
|
notifier.success("Table deleted")
|
||||||
hideEditor()
|
hideEditor()
|
||||||
|
|
|
@ -128,7 +128,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function signUp() {
|
async function createNewApp() {
|
||||||
submitting = true
|
submitting = true
|
||||||
try {
|
try {
|
||||||
// Add API key if there is none.
|
// Add API key if there is none.
|
||||||
|
@ -154,7 +154,7 @@
|
||||||
if (applicationPkg.ok) {
|
if (applicationPkg.ok) {
|
||||||
backendUiStore.actions.reset()
|
backendUiStore.actions.reset()
|
||||||
pkg.justCreated = true
|
pkg.justCreated = true
|
||||||
await store.setPackage(pkg)
|
await store.actions.initialise(pkg)
|
||||||
automationStore.actions.fetch()
|
automationStore.actions.fetch()
|
||||||
} else {
|
} else {
|
||||||
throw new Error(pkg)
|
throw new Error(pkg)
|
||||||
|
@ -193,10 +193,6 @@
|
||||||
$: checkValidity($createAppStore.values, $createAppStore.currentStep)
|
$: checkValidity($createAppStore.values, $createAppStore.currentStep)
|
||||||
|
|
||||||
let onChange = () => {}
|
let onChange = () => {}
|
||||||
|
|
||||||
async function _onOkay() {
|
|
||||||
await createNewApp()
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
@ -239,7 +235,7 @@
|
||||||
<Button
|
<Button
|
||||||
medium
|
medium
|
||||||
blue
|
blue
|
||||||
on:click={signUp}
|
on:click={createNewApp}
|
||||||
disabled={!fullFormIsValid || submitting}>
|
disabled={!fullFormIsValid || submitting}>
|
||||||
{submitting ? 'Loading...' : 'Submit'}
|
{submitting ? 'Loading...' : 'Submit'}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
import { getComponentDefinition } from "builderStore/storeUtils"
|
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 { getParent, saveCurrentPreviewItem } from "builderStore/storeUtils"
|
import { getParent } 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"
|
||||||
|
|
||||||
|
@ -25,50 +25,50 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectComponent = component => {
|
const selectComponent = component => {
|
||||||
store.selectComponent(component)
|
store.actions.components.select(component)
|
||||||
const path = store.getPathToComponent(component)
|
const path = store.actions.components.findRoute(component)
|
||||||
$goto(`./:page/:screen/${path}`)
|
$goto(`./:page/:screen/${path}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const moveUpComponent = () => {
|
const moveUpComponent = () => {
|
||||||
store.update(s => {
|
store.update(state => {
|
||||||
const parent = getParent(s.currentPreviewItem.props, component)
|
const parent = getParent(state.currentPreviewItem.props, component)
|
||||||
|
|
||||||
if (parent) {
|
if (parent) {
|
||||||
const currentIndex = parent._children.indexOf(component)
|
const currentIndex = parent._children.indexOf(component)
|
||||||
if (currentIndex === 0) return s
|
if (currentIndex === 0) return state
|
||||||
|
|
||||||
const newChildren = parent._children.filter(c => c !== component)
|
const newChildren = parent._children.filter(c => c !== component)
|
||||||
newChildren.splice(currentIndex - 1, 0, component)
|
newChildren.splice(currentIndex - 1, 0, component)
|
||||||
parent._children = newChildren
|
parent._children = newChildren
|
||||||
}
|
}
|
||||||
s.currentComponentInfo = component
|
state.currentComponentInfo = component
|
||||||
saveCurrentPreviewItem(s)
|
store.actions.preview.saveSelected()
|
||||||
|
|
||||||
return s
|
return state
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const moveDownComponent = () => {
|
const moveDownComponent = () => {
|
||||||
store.update(s => {
|
store.update(state => {
|
||||||
const parent = getParent(s.currentPreviewItem.props, component)
|
const parent = getParent(state.currentPreviewItem.props, component)
|
||||||
|
|
||||||
if (parent) {
|
if (parent) {
|
||||||
const currentIndex = parent._children.indexOf(component)
|
const currentIndex = parent._children.indexOf(component)
|
||||||
if (currentIndex === parent._children.length - 1) return s
|
if (currentIndex === parent._children.length - 1) return state
|
||||||
|
|
||||||
const newChildren = parent._children.filter(c => c !== component)
|
const newChildren = parent._children.filter(c => c !== component)
|
||||||
newChildren.splice(currentIndex + 1, 0, component)
|
newChildren.splice(currentIndex + 1, 0, component)
|
||||||
parent._children = newChildren
|
parent._children = newChildren
|
||||||
}
|
}
|
||||||
s.currentComponentInfo = component
|
state.currentComponentInfo = component
|
||||||
saveCurrentPreviewItem(s)
|
store.actions.preview.saveSelected()
|
||||||
|
|
||||||
return s
|
return state
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const copyComponent = () => {
|
const duplicateComponent = () => {
|
||||||
storeComponentForCopy(false)
|
storeComponentForCopy(false)
|
||||||
pasteComponent("below")
|
pasteComponent("below")
|
||||||
}
|
}
|
||||||
|
@ -82,19 +82,19 @@
|
||||||
selectComponent(parent)
|
selectComponent(parent)
|
||||||
}
|
}
|
||||||
|
|
||||||
saveCurrentPreviewItem(state)
|
store.actions.preview.saveSelected()
|
||||||
return state
|
return state
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const storeComponentForCopy = (cut = false) => {
|
const storeComponentForCopy = (cut = false) => {
|
||||||
// lives in store - also used by drag drop
|
// lives in store - also used by drag drop
|
||||||
store.storeComponentForCopy(component, cut)
|
store.actions.components.copy(component, cut)
|
||||||
}
|
}
|
||||||
|
|
||||||
const pasteComponent = mode => {
|
const pasteComponent = mode => {
|
||||||
// lives in store - also used by drag drop
|
// lives in store - also used by drag drop
|
||||||
store.pasteComponent(component, mode)
|
store.actions.components.paste(component, mode)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -117,7 +117,7 @@
|
||||||
<DropdownItem
|
<DropdownItem
|
||||||
icon="ri-repeat-one-line"
|
icon="ri-repeat-one-line"
|
||||||
title="Duplicate"
|
title="Duplicate"
|
||||||
on:click={copyComponent} />
|
on:click={duplicateComponent} />
|
||||||
<DropdownItem
|
<DropdownItem
|
||||||
icon="ri-scissors-cut-line"
|
icon="ri-scissors-cut-line"
|
||||||
title="Cut"
|
title="Cut"
|
||||||
|
|
|
@ -27,7 +27,7 @@
|
||||||
componentPropDefinition.properties &&
|
componentPropDefinition.properties &&
|
||||||
componentPropDefinition.properties[selectedCategory.value]
|
componentPropDefinition.properties[selectedCategory.value]
|
||||||
|
|
||||||
const onStyleChanged = store.setComponentStyle
|
const onStyleChanged = store.actions.components.updateStyle
|
||||||
|
|
||||||
$: isComponentOrScreen =
|
$: isComponentOrScreen =
|
||||||
$store.currentView === "component" ||
|
$store.currentView === "component" ||
|
||||||
|
@ -58,6 +58,18 @@
|
||||||
return components
|
return components
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setPageOrScreenProp(name, value) {
|
||||||
|
store.update(state => {
|
||||||
|
if (name === "_instanceName" && state.currentFrontEndType === "screen") {
|
||||||
|
state.currentPreviewItem.props[name] = value
|
||||||
|
} else {
|
||||||
|
state.currentPreviewItem[name] = value
|
||||||
|
}
|
||||||
|
store.actions.preview.saveSelected()
|
||||||
|
return state
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
function getProps(obj, keys) {
|
function getProps(obj, keys) {
|
||||||
return keys.map((key, i) => [key, obj[key], obj.props._id + i])
|
return keys.map((key, i) => [key, obj[key], obj.props._id + i])
|
||||||
}
|
}
|
||||||
|
@ -81,8 +93,8 @@
|
||||||
{componentDefinition}
|
{componentDefinition}
|
||||||
{panelDefinition}
|
{panelDefinition}
|
||||||
displayNameField={displayName}
|
displayNameField={displayName}
|
||||||
onChange={store.setComponentProp}
|
onChange={store.actions.components.updateProp}
|
||||||
onScreenPropChange={store.setPageOrScreenProp}
|
onScreenPropChange={setPageOrScreenProp}
|
||||||
screenOrPageInstance={$store.currentView !== 'component' && $store.currentPreviewItem} />
|
screenOrPageInstance={$store.currentView !== 'component' && $store.currentPreviewItem} />
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -25,8 +25,8 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
const onComponentChosen = component => {
|
const onComponentChosen = component => {
|
||||||
store.addChildComponent(component._component, component.presetProps)
|
store.actions.components.create(component._component, component.presetProps)
|
||||||
const path = store.getPathToComponent($store.currentComponentInfo)
|
const path = store.actions.components.findRoute($store.currentComponentInfo)
|
||||||
$goto(`./:page/:screen/${path}`)
|
$goto(`./:page/:screen/${path}`)
|
||||||
close()
|
close()
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,7 @@
|
||||||
])
|
])
|
||||||
|
|
||||||
const changeScreen = screen => {
|
const changeScreen = screen => {
|
||||||
store.setCurrentScreen(screen.props._instanceName)
|
store.actions.screens.select(screen.props._instanceName)
|
||||||
$goto(`./:page/${screen.props._instanceName}`)
|
$goto(`./:page/${screen.props._instanceName}`)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -40,10 +40,10 @@
|
||||||
|
|
||||||
const selectComponent = component => {
|
const selectComponent = component => {
|
||||||
// Set current component
|
// Set current component
|
||||||
store.selectComponent(component)
|
store.actions.components.select(component)
|
||||||
|
|
||||||
// Get ID path
|
// Get ID path
|
||||||
const path = store.getPathToComponent(component)
|
const path = store.actions.components.findRoute(component)
|
||||||
|
|
||||||
// Go to correct URL
|
// Go to correct URL
|
||||||
$goto(`./:page/:screen/${path}`)
|
$goto(`./:page/:screen/${path}`)
|
||||||
|
@ -96,8 +96,8 @@
|
||||||
|
|
||||||
const drop = () => {
|
const drop = () => {
|
||||||
if ($dragDropStore.targetComponent !== $dragDropStore.componentToDrop) {
|
if ($dragDropStore.targetComponent !== $dragDropStore.componentToDrop) {
|
||||||
store.storeComponentForCopy($dragDropStore.componentToDrop, true)
|
store.actions.components.copy($dragDropStore.componentToDrop, true)
|
||||||
store.pasteComponent(
|
store.actions.components.paste(
|
||||||
$dragDropStore.targetComponent,
|
$dragDropStore.targetComponent,
|
||||||
$dragDropStore.dropPosition
|
$dragDropStore.dropPosition
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import { store } from "builderStore/"
|
import { store, allScreens } from "builderStore"
|
||||||
import ComponentPropertiesPanel from "./ComponentPropertiesPanel.svelte"
|
import ComponentPropertiesPanel from "./ComponentPropertiesPanel.svelte"
|
||||||
import ComponentSelectionList from "./ComponentSelectionList.svelte"
|
import ComponentSelectionList from "./ComponentSelectionList.svelte"
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="root">
|
<div class="root">
|
||||||
{#if $store.currentFrontEndType === 'page' || $store.screens.length}
|
{#if $store.currentFrontEndType === 'page' || $allScreens.length}
|
||||||
<div class="switcher">
|
<div class="switcher">
|
||||||
<button
|
<button
|
||||||
class:selected={selected === COMPONENT_SELECTION_TAB}
|
class:selected={selected === COMPONENT_SELECTION_TAB}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
import { DataList } from "@budibase/bbui"
|
import { DataList } from "@budibase/bbui"
|
||||||
import { createEventDispatcher } from "svelte"
|
import { createEventDispatcher } from "svelte"
|
||||||
import { store } from "builderStore"
|
import { allScreens } from "builderStore"
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@
|
||||||
|
|
||||||
const getUrls = () => {
|
const getUrls = () => {
|
||||||
return [
|
return [
|
||||||
...$store.screens
|
...$allScreens
|
||||||
.filter(
|
.filter(
|
||||||
screen =>
|
screen =>
|
||||||
screen.props._component.endsWith("/rowdetail") ||
|
screen.props._component.endsWith("/rowdetail") ||
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import { Input, DataList, Select } from "@budibase/bbui"
|
import { Input, DataList, Select } from "@budibase/bbui"
|
||||||
import { store, automationStore } from "builderStore"
|
import { automationStore, allScreens } from "builderStore"
|
||||||
|
|
||||||
export let parameter
|
export let parameter
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@
|
||||||
{:else if parameter.name === 'url'}
|
{:else if parameter.name === 'url'}
|
||||||
<DataList on:change bind:value={parameter.value}>
|
<DataList on:change bind:value={parameter.value}>
|
||||||
<option value="" />
|
<option value="" />
|
||||||
{#each $store.screens as screen}
|
{#each $allScreens as screen}
|
||||||
<option value={screen.route}>{screen.props._instanceName}</option>
|
<option value={screen.route}>{screen.props._instanceName}</option>
|
||||||
{/each}
|
{/each}
|
||||||
</DataList>
|
</DataList>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import { DataList, Label } from "@budibase/bbui"
|
import { DataList, Label } from "@budibase/bbui"
|
||||||
import { store } from "builderStore"
|
import { allScreens } from "builderStore"
|
||||||
|
|
||||||
export let parameters
|
export let parameters
|
||||||
</script>
|
</script>
|
||||||
|
@ -9,7 +9,7 @@
|
||||||
<Label size="m" color="dark">Screen</Label>
|
<Label size="m" color="dark">Screen</Label>
|
||||||
<DataList secondary bind:value={parameters.url}>
|
<DataList secondary bind:value={parameters.url}>
|
||||||
<option value="" />
|
<option value="" />
|
||||||
{#each $store.screens as screen}
|
{#each $allScreens as screen}
|
||||||
<option value={screen.route}>{screen.props._instanceName}</option>
|
<option value={screen.route}>{screen.props._instanceName}</option>
|
||||||
{/each}
|
{/each}
|
||||||
</DataList>
|
</DataList>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import { store } from "builderStore"
|
import { store, currentScreens } from "builderStore"
|
||||||
import ComponentsHierarchy from "components/userInterface/ComponentsHierarchy.svelte"
|
import ComponentsHierarchy from "components/userInterface/ComponentsHierarchy.svelte"
|
||||||
import PageLayout from "components/userInterface/PageLayout.svelte"
|
import PageLayout from "components/userInterface/PageLayout.svelte"
|
||||||
import PagesList from "components/userInterface/PagesList.svelte"
|
import PagesList from "components/userInterface/PagesList.svelte"
|
||||||
|
@ -16,7 +16,7 @@
|
||||||
<PagesList />
|
<PagesList />
|
||||||
<div class="nav-items-container">
|
<div class="nav-items-container">
|
||||||
<PageLayout layout={$store.pages[$store.currentPageName]} />
|
<PageLayout layout={$store.pages[$store.currentPageName]} />
|
||||||
<ComponentsHierarchy screens={$store.screens} />
|
<ComponentsHierarchy screens={$currentScreens} />
|
||||||
</div>
|
</div>
|
||||||
<Modal bind:this={modal}>
|
<Modal bind:this={modal}>
|
||||||
<NewScreenModal />
|
<NewScreenModal />
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import { goto } from "@sveltech/routify"
|
import { goto } from "@sveltech/routify"
|
||||||
import { store, backendUiStore } from "builderStore"
|
import { store, backendUiStore, allScreens } from "builderStore"
|
||||||
import {
|
import {
|
||||||
Input,
|
Input,
|
||||||
Button,
|
Button,
|
||||||
|
@ -24,7 +24,7 @@
|
||||||
|
|
||||||
$: templates = getTemplates($store, $backendUiStore.tables)
|
$: templates = getTemplates($store, $backendUiStore.tables)
|
||||||
|
|
||||||
$: route = !route && $store.screens.length === 0 ? "*" : route
|
$: route = !route && $allScreens.length === 0 ? "*" : route
|
||||||
|
|
||||||
$: baseComponents = Object.values($store.components)
|
$: baseComponents = Object.values($store.components)
|
||||||
.filter(componentDefinition => componentDefinition.baseComponent)
|
.filter(componentDefinition => componentDefinition.baseComponent)
|
||||||
|
@ -71,9 +71,9 @@
|
||||||
draftScreen.props._component = baseComponent
|
draftScreen.props._component = baseComponent
|
||||||
draftScreen.route = route
|
draftScreen.route = route
|
||||||
|
|
||||||
await store.createScreen(draftScreen)
|
await store.actions.screens.create(draftScreen)
|
||||||
if (createLink) {
|
if (createLink) {
|
||||||
await store.createLink(route, name)
|
await store.actions.components.links.save(route, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (templateIndex !== undefined) {
|
if (templateIndex !== undefined) {
|
||||||
|
@ -87,7 +87,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
const routeNameExists = route => {
|
const routeNameExists = route => {
|
||||||
return $store.screens.some(
|
return $allScreens.some(
|
||||||
screen => screen.route.toLowerCase() === route.toLowerCase()
|
screen => screen.route.toLowerCase() === route.toLowerCase()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
const setCurrentScreenToLayout = () => {
|
const setCurrentScreenToLayout = () => {
|
||||||
store.setScreenType("page")
|
store.actions.selectPageOrScreen("page")
|
||||||
$goto("./:page/page-layout")
|
$goto("./:page/page-layout")
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -2,8 +2,8 @@
|
||||||
import { params, goto } from "@sveltech/routify"
|
import { params, goto } from "@sveltech/routify"
|
||||||
import { store } from "builderStore"
|
import { store } from "builderStore"
|
||||||
|
|
||||||
const getPage = (s, name) => {
|
const getPage = (state, name) => {
|
||||||
const props = s.pages[name]
|
const props = state.pages[name]
|
||||||
return { name, props }
|
return { name, props }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,10 +19,10 @@
|
||||||
]
|
]
|
||||||
|
|
||||||
if (!$store.currentPageName)
|
if (!$store.currentPageName)
|
||||||
store.setCurrentPage($params.page ? $params.page : "main")
|
store.actions.pages.select($params.page ? $params.page : "main")
|
||||||
|
|
||||||
const changePage = id => {
|
const changePage = id => {
|
||||||
store.setCurrentPage(id)
|
store.actions.pages.select(id)
|
||||||
$goto(`./${id}/page-layout`)
|
$goto(`./${id}/page-layout`)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
import { goto } from "@sveltech/routify"
|
import { goto } from "@sveltech/routify"
|
||||||
import { store } from "builderStore"
|
import { store } from "builderStore"
|
||||||
|
import { notifier } from "builderStore/store/notifications"
|
||||||
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
||||||
import { DropdownMenu } from "@budibase/bbui"
|
import { DropdownMenu } from "@budibase/bbui"
|
||||||
import { DropdownContainer, DropdownItem } from "components/common/Dropdowns"
|
import { DropdownContainer, DropdownItem } from "components/common/Dropdowns"
|
||||||
|
@ -12,11 +13,12 @@
|
||||||
let anchor
|
let anchor
|
||||||
|
|
||||||
const deleteScreen = () => {
|
const deleteScreen = () => {
|
||||||
store.deleteScreens(screen, $store.currentPageName)
|
store.actions.screens.delete(screen, $store.currentPageName)
|
||||||
// update the page if required
|
// update the page if required
|
||||||
store.update(state => {
|
store.update(state => {
|
||||||
if (state.currentPreviewItem.name === screen.name) {
|
if (state.currentPreviewItem.name === screen.name) {
|
||||||
store.setCurrentPage($store.currentPageName)
|
store.actions.pages.select($store.currentPageName)
|
||||||
|
notifier.success(`Screen ${screen.name} deleted successfully.`)
|
||||||
$goto(`./:page/page-layout`)
|
$goto(`./:page/page-layout`)
|
||||||
}
|
}
|
||||||
return state
|
return state
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
import { DataList } from "@budibase/bbui"
|
import { DataList } from "@budibase/bbui"
|
||||||
import { createEventDispatcher } from "svelte"
|
import { createEventDispatcher } from "svelte"
|
||||||
import { store, backendUiStore } from "builderStore"
|
import { store, allScreens, backendUiStore } from "builderStore"
|
||||||
import fetchBindableProperties from "builderStore/fetchBindableProperties"
|
import fetchBindableProperties from "builderStore/fetchBindableProperties"
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
@ -17,7 +17,7 @@
|
||||||
// and substitute the :id param for the actual {{ ._id }} binding
|
// and substitute the :id param for the actual {{ ._id }} binding
|
||||||
const getUrls = () => {
|
const getUrls = () => {
|
||||||
const urls = [
|
const urls = [
|
||||||
...$store.screens
|
...$allScreens
|
||||||
.filter(screen => !screen.props._component.endsWith("/rowdetail"))
|
.filter(screen => !screen.props._component.endsWith("/rowdetail"))
|
||||||
.map(screen => ({
|
.map(screen => ({
|
||||||
name: screen.props._instanceName,
|
name: screen.props._instanceName,
|
||||||
|
@ -33,7 +33,7 @@
|
||||||
tables: $backendUiStore.tables,
|
tables: $backendUiStore.tables,
|
||||||
})
|
})
|
||||||
|
|
||||||
const detailScreens = $store.screens.filter(screen =>
|
const detailScreens = $allScreens.filter(screen =>
|
||||||
screen.props._component.endsWith("/rowdetail")
|
screen.props._component.endsWith("/rowdetail")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
import Input from "./PropertyPanelControls/Input.svelte"
|
import Input from "./PropertyPanelControls/Input.svelte"
|
||||||
import { goto } from "@sveltech/routify"
|
import { goto } from "@sveltech/routify"
|
||||||
import { excludeProps } from "./propertyCategories.js"
|
import { excludeProps } from "./propertyCategories.js"
|
||||||
import { store } from "builderStore"
|
import { store, allScreens } from "builderStore"
|
||||||
import { walkProps } from "builderStore/storeUtils"
|
import { walkProps } from "builderStore/storeUtils"
|
||||||
|
|
||||||
export let panelDefinition = []
|
export let panelDefinition = []
|
||||||
|
@ -67,7 +67,7 @@
|
||||||
lookForDuplicate($store.currentPreviewItem.props)
|
lookForDuplicate($store.currentPreviewItem.props)
|
||||||
} else {
|
} else {
|
||||||
// viewing master page - need to dedupe against all screens
|
// viewing master page - need to dedupe against all screens
|
||||||
for (let screen of $store.screens) {
|
for (let screen of $allScreens) {
|
||||||
lookForDuplicate(screen.props)
|
lookForDuplicate(screen.props)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,6 +59,11 @@ export const createProps = (componentDefinition, derivedFromProps) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const makePropsSafe = (componentDefinition, props) => {
|
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
|
const safeProps = createProps(componentDefinition, props).props
|
||||||
for (let propName in safeProps) {
|
for (let propName in safeProps) {
|
||||||
props[propName] = safeProps[propName]
|
props[propName] = safeProps[propName]
|
||||||
|
|
|
@ -29,8 +29,8 @@ export const searchAllComponents = (components, phrase) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getExactComponent = (components, name, isScreen = false) => {
|
export const getExactComponent = (components, name, isScreen = false) => {
|
||||||
return components.find(c =>
|
return components.find(comp =>
|
||||||
isScreen ? c.props._instanceName === name : c._instanceName === name
|
isScreen ? comp.props._instanceName === name : comp._instanceName === name
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,19 +1,15 @@
|
||||||
export const DEFAULT_PAGES_OBJECT = {
|
export const DEFAULT_PAGES_OBJECT = {
|
||||||
main: {
|
main: {
|
||||||
_props: {},
|
props: {
|
||||||
_screens: {},
|
_component: "@budibase/standard-components/container",
|
||||||
index: {
|
|
||||||
_component: "./components/indexHtml",
|
|
||||||
},
|
},
|
||||||
appBody: "bbapp.main.json",
|
_screens: {},
|
||||||
},
|
},
|
||||||
unauthenticated: {
|
unauthenticated: {
|
||||||
_props: {},
|
props: {
|
||||||
_screens: {},
|
_component: "@budibase/standard-components/container",
|
||||||
index: {
|
|
||||||
_component: "./components/indexHtml",
|
|
||||||
},
|
},
|
||||||
appBody: "bbapp.unauthenticated.json",
|
_screens: {},
|
||||||
},
|
},
|
||||||
componentLibraries: [],
|
componentLibraries: [],
|
||||||
stylesheets: [],
|
stylesheets: [],
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
|
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
backendUiStore.actions.reset()
|
backendUiStore.actions.reset()
|
||||||
await store.setPackage(pkg)
|
await store.actions.initialise(pkg)
|
||||||
await automationStore.actions.fetch()
|
await automationStore.actions.fetch()
|
||||||
return pkg
|
return pkg
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<script>
|
<script>
|
||||||
import { params } from "@sveltech/routify"
|
import { params } from "@sveltech/routify"
|
||||||
store.setCurrentPage($params.page)
|
store.actions.pages.select($params.page)
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
import { onMount } from "svelte"
|
import { onMount } from "svelte"
|
||||||
import { params, leftover, goto } from "@sveltech/routify"
|
import { params, leftover, goto } from "@sveltech/routify"
|
||||||
import { store } from "builderStore"
|
import { store, allScreens } from "builderStore"
|
||||||
|
|
||||||
// Get any leftover params not caught by Routifys params store.
|
// Get any leftover params not caught by Routifys params store.
|
||||||
const componentIds = $leftover.split("/").filter(id => id !== "")
|
const componentIds = $leftover.split("/").filter(id => id !== "")
|
||||||
|
@ -10,17 +10,17 @@
|
||||||
if ($params.screen !== "page-layout") {
|
if ($params.screen !== "page-layout") {
|
||||||
const currentScreenName = decodeURI($params.screen)
|
const currentScreenName = decodeURI($params.screen)
|
||||||
const validScreen =
|
const validScreen =
|
||||||
$store.screens.findIndex(
|
$allScreens.findIndex(
|
||||||
screen => screen.props._instanceName === currentScreenName
|
screen => screen.props._instanceName === currentScreenName
|
||||||
) !== -1
|
) !== -1
|
||||||
|
|
||||||
if (!validScreen) {
|
if (!validScreen) {
|
||||||
// Go to main layout if URL set to invalid screen
|
// Go to main layout if URL set to invalid screen
|
||||||
store.setCurrentPage("main")
|
store.actions.pages.select("main")
|
||||||
$goto("../../main")
|
$goto("../../main")
|
||||||
} else {
|
} else {
|
||||||
// Otherwise proceed to set screen
|
// Otherwise proceed to set screen
|
||||||
store.setCurrentScreen(currentScreenName)
|
store.actions.screens.select(currentScreenName)
|
||||||
|
|
||||||
// There are leftover stuff, like IDs, so navigate the components and find the ID and select it.
|
// There are leftover stuff, like IDs, so navigate the components and find the ID and select it.
|
||||||
if ($leftover) {
|
if ($leftover) {
|
||||||
|
@ -35,7 +35,7 @@
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// It's a page, so set the screentype to page.
|
// It's a page, so set the screentype to page.
|
||||||
store.setScreenType("page")
|
store.actions.selectPageOrScreen("page")
|
||||||
|
|
||||||
// There are leftover stuff, like IDs, so navigate the components and find the ID and select it.
|
// There are leftover stuff, like IDs, so navigate the components and find the ID and select it.
|
||||||
if ($leftover) {
|
if ($leftover) {
|
||||||
|
@ -64,7 +64,7 @@
|
||||||
})
|
})
|
||||||
|
|
||||||
// Select Component!
|
// Select Component!
|
||||||
if (componentToSelect) store.selectComponent(componentToSelect)
|
if (componentToSelect) store.actions.components.select(componentToSelect)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
import { params } from "@sveltech/routify"
|
import { params } from "@sveltech/routify"
|
||||||
import { store } from "builderStore"
|
import { store } from "builderStore"
|
||||||
|
|
||||||
store.setCurrentPage($params.page)
|
store.actions.pages.select($params.page)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<slot />
|
<slot />
|
||||||
|
|
|
@ -28,12 +28,12 @@
|
||||||
<Link
|
<Link
|
||||||
icon={CommunityIcon}
|
icon={CommunityIcon}
|
||||||
title="Community"
|
title="Community"
|
||||||
href="https://forum.budibase.com/" />
|
href="https://github.com/Budibase/budibase/discussions" />
|
||||||
|
|
||||||
<Link
|
<Link
|
||||||
icon={BugIcon}
|
icon={BugIcon}
|
||||||
title="Raise an issue"
|
title="Raise an issue"
|
||||||
href="https://github.com/Budibase/budibase" />
|
href="https://github.com/Budibase/budibase/issues/new/choose" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -1,63 +0,0 @@
|
||||||
import { buildCodeForScreens } from "../src/builderStore/buildCodeForScreens"
|
|
||||||
|
|
||||||
describe("buildCodeForScreen", () => {
|
|
||||||
it("should package _code into runnable function, for simple screen props", () => {
|
|
||||||
const screen = {
|
|
||||||
props: {
|
|
||||||
_id: "1234",
|
|
||||||
_code: "render('render argument');",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
let renderArg
|
|
||||||
const render = arg => {
|
|
||||||
renderArg = arg
|
|
||||||
}
|
|
||||||
const uiFunctions = getFunctions(screen)
|
|
||||||
|
|
||||||
const targetfunction = uiFunctions[screen.props._id]
|
|
||||||
expect(targetfunction).toBeDefined()
|
|
||||||
|
|
||||||
targetfunction(render)
|
|
||||||
|
|
||||||
expect(renderArg).toBe("render argument")
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should package _code into runnable function, for _children ", () => {
|
|
||||||
const screen = {
|
|
||||||
props: {
|
|
||||||
_id: "parent",
|
|
||||||
_code: "render('parent argument');",
|
|
||||||
_children: [
|
|
||||||
{
|
|
||||||
_id: "child1",
|
|
||||||
_code: "render('child 1 argument');",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
_id: "child2",
|
|
||||||
_code: "render('child 2 argument');",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
let renderArg
|
|
||||||
const render = arg => {
|
|
||||||
renderArg = arg
|
|
||||||
}
|
|
||||||
const uiFunctions = getFunctions(screen)
|
|
||||||
|
|
||||||
const targetfunction = uiFunctions["child2"]
|
|
||||||
expect(targetfunction).toBeDefined()
|
|
||||||
|
|
||||||
targetfunction(render)
|
|
||||||
|
|
||||||
expect(renderArg).toBe("child 2 argument")
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
const getFunctions = screen => {
|
|
||||||
const code = buildCodeForScreens([screen])
|
|
||||||
const func = new Function(`return ${code}`)()
|
|
||||||
return func
|
|
||||||
}
|
|
|
@ -23,6 +23,12 @@ yargs
|
||||||
},
|
},
|
||||||
async args => {
|
async args => {
|
||||||
console.log("Exporting app..")
|
console.log("Exporting app..")
|
||||||
|
if (args.name == null || args.appId == null) {
|
||||||
|
console.error(
|
||||||
|
"Unable to export without a name and app ID being specified, check help for more info."
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
const exportPath = await exportTemplateFromApp({
|
const exportPath = await exportTemplateFromApp({
|
||||||
templateName: args.name,
|
templateName: args.name,
|
||||||
appId: args.appId,
|
appId: args.appId,
|
||||||
|
|
|
@ -1,21 +1,29 @@
|
||||||
const CouchDB = require("../../db")
|
const CouchDB = require("../../db")
|
||||||
const { getPackageForBuilder, buildPage } = require("../../utilities/builder")
|
const compileStaticAssetsForPage = require("../../utilities/builder/compileStaticAssetsForPage")
|
||||||
const env = require("../../environment")
|
const env = require("../../environment")
|
||||||
const { copy, existsSync, readFile, writeFile } = require("fs-extra")
|
const { existsSync } = require("fs-extra")
|
||||||
const { budibaseAppsDir } = require("../../utilities/budibaseDir")
|
const { budibaseAppsDir } = require("../../utilities/budibaseDir")
|
||||||
const sqrl = require("squirrelly")
|
|
||||||
const setBuilderToken = require("../../utilities/builder/setBuilderToken")
|
const setBuilderToken = require("../../utilities/builder/setBuilderToken")
|
||||||
const fs = require("fs-extra")
|
const fs = require("fs-extra")
|
||||||
const { join, resolve } = require("../../utilities/centralPath")
|
const { join, resolve } = require("../../utilities/centralPath")
|
||||||
const { promisify } = require("util")
|
|
||||||
const chmodr = require("chmodr")
|
|
||||||
const packageJson = require("../../../package.json")
|
const packageJson = require("../../../package.json")
|
||||||
const { createLinkView } = require("../../db/linkedRows")
|
const { createLinkView } = require("../../db/linkedRows")
|
||||||
const { downloadTemplate } = require("../../utilities/templates")
|
const { downloadTemplate } = require("../../utilities/templates")
|
||||||
const { generateAppID, DocumentTypes, SEPARATOR } = require("../../db/utils")
|
const {
|
||||||
|
generateAppID,
|
||||||
|
DocumentTypes,
|
||||||
|
SEPARATOR,
|
||||||
|
getPageParams,
|
||||||
|
generatePageID,
|
||||||
|
generateScreenID,
|
||||||
|
} = require("../../db/utils")
|
||||||
const {
|
const {
|
||||||
downloadExtractComponentLibraries,
|
downloadExtractComponentLibraries,
|
||||||
} = require("../../utilities/createAppPackage")
|
} = require("../../utilities/createAppPackage")
|
||||||
|
const { MAIN, UNAUTHENTICATED, PageTypes } = require("../../constants/pages")
|
||||||
|
const { HOME_SCREEN } = require("../../constants/screens")
|
||||||
|
const { cloneDeep } = require("lodash/fp")
|
||||||
|
|
||||||
const APP_PREFIX = DocumentTypes.APP + SEPARATOR
|
const APP_PREFIX = DocumentTypes.APP + SEPARATOR
|
||||||
|
|
||||||
async function createInstance(template) {
|
async function createInstance(template) {
|
||||||
|
@ -60,13 +68,31 @@ exports.fetch = async function(ctx) {
|
||||||
exports.fetchAppPackage = async function(ctx) {
|
exports.fetchAppPackage = async function(ctx) {
|
||||||
const db = new CouchDB(ctx.params.appId)
|
const db = new CouchDB(ctx.params.appId)
|
||||||
const application = await db.get(ctx.params.appId)
|
const application = await db.get(ctx.params.appId)
|
||||||
ctx.body = await getPackageForBuilder(ctx.config, application)
|
|
||||||
|
let pages = await db.allDocs(
|
||||||
|
getPageParams(null, {
|
||||||
|
include_docs: true,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
pages = pages.rows.map(row => row.doc)
|
||||||
|
|
||||||
|
const mainPage = pages.find(page => page.name === PageTypes.MAIN)
|
||||||
|
const unauthPage = pages.find(page => page.name === PageTypes.UNAUTHENTICATED)
|
||||||
|
ctx.body = {
|
||||||
|
application,
|
||||||
|
pages: {
|
||||||
|
main: mainPage,
|
||||||
|
unauthenticated: unauthPage,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
await setBuilderToken(ctx, ctx.params.appId, application.version)
|
await setBuilderToken(ctx, ctx.params.appId, application.version)
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.create = async function(ctx) {
|
exports.create = async function(ctx) {
|
||||||
const instance = await createInstance(ctx.request.body.template)
|
const instance = await createInstance(ctx.request.body.template)
|
||||||
const appId = instance._id
|
const appId = instance._id
|
||||||
|
const version = packageJson.version
|
||||||
const newApplication = {
|
const newApplication = {
|
||||||
_id: appId,
|
_id: appId,
|
||||||
type: "app",
|
type: "app",
|
||||||
|
@ -84,6 +110,7 @@ exports.create = async function(ctx) {
|
||||||
await downloadExtractComponentLibraries(newAppFolder)
|
await downloadExtractComponentLibraries(newAppFolder)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await setBuilderToken(ctx, appId, version)
|
||||||
ctx.status = 200
|
ctx.status = 200
|
||||||
ctx.body = newApplication
|
ctx.body = newApplication
|
||||||
ctx.message = `Application ${ctx.request.body.name} created successfully`
|
ctx.message = `Application ${ctx.request.body.name} created successfully`
|
||||||
|
@ -120,99 +147,38 @@ exports.delete = async function(ctx) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const createEmptyAppPackage = async (ctx, app) => {
|
const createEmptyAppPackage = async (ctx, app) => {
|
||||||
const templateFolder = resolve(
|
|
||||||
__dirname,
|
|
||||||
"..",
|
|
||||||
"..",
|
|
||||||
"utilities",
|
|
||||||
"appDirectoryTemplate"
|
|
||||||
)
|
|
||||||
|
|
||||||
const appsFolder = budibaseAppsDir()
|
const appsFolder = budibaseAppsDir()
|
||||||
const newAppFolder = resolve(appsFolder, app._id)
|
const newAppFolder = resolve(appsFolder, app._id)
|
||||||
|
|
||||||
|
const db = new CouchDB(app._id)
|
||||||
|
|
||||||
if (existsSync(newAppFolder)) {
|
if (existsSync(newAppFolder)) {
|
||||||
ctx.throw(400, "App folder already exists for this application")
|
ctx.throw(400, "App folder already exists for this application")
|
||||||
}
|
}
|
||||||
|
|
||||||
await fs.ensureDir(join(newAppFolder, "pages", "main", "screens"), 0o777)
|
fs.mkdirpSync(newAppFolder)
|
||||||
await fs.ensureDir(
|
|
||||||
join(newAppFolder, "pages", "unauthenticated", "screens"),
|
|
||||||
0o777
|
|
||||||
)
|
|
||||||
|
|
||||||
await copy(templateFolder, newAppFolder)
|
const mainPage = cloneDeep(MAIN)
|
||||||
|
mainPage._id = generatePageID()
|
||||||
|
mainPage.title = app.name
|
||||||
|
|
||||||
// this line allows full permission on copied files
|
const unauthPage = cloneDeep(UNAUTHENTICATED)
|
||||||
// we have an unknown problem without this, whereby the
|
unauthPage._id = generatePageID()
|
||||||
// files get weird permissions and cant be written to :(
|
unauthPage.title = app.name
|
||||||
const chmodrPromise = promisify(chmodr)
|
unauthPage.props._children[0].title = `Log in to ${app.name}`
|
||||||
await chmodrPromise(newAppFolder, 0o777)
|
|
||||||
|
|
||||||
await updateJsonFile(join(appsFolder, app._id, "package.json"), {
|
const homeScreen = cloneDeep(HOME_SCREEN)
|
||||||
name: npmFriendlyAppName(app.name),
|
homeScreen._id = generateScreenID(mainPage._id)
|
||||||
|
await db.bulkDocs([mainPage, unauthPage, homeScreen])
|
||||||
|
|
||||||
|
await compileStaticAssetsForPage(app._id, "main", {
|
||||||
|
page: mainPage,
|
||||||
|
screens: [homeScreen],
|
||||||
})
|
})
|
||||||
|
await compileStaticAssetsForPage(app._id, "unauthenticated", {
|
||||||
// if this app is being created from a template,
|
page: unauthPage,
|
||||||
// copy the frontend page definition files from
|
screens: [],
|
||||||
// the template directory.
|
|
||||||
if (app.template) {
|
|
||||||
const templatePageDefinitions = join(
|
|
||||||
appsFolder,
|
|
||||||
"templates",
|
|
||||||
app.template.key,
|
|
||||||
"pages"
|
|
||||||
)
|
|
||||||
await copy(templatePageDefinitions, join(appsFolder, app._id, "pages"))
|
|
||||||
}
|
|
||||||
|
|
||||||
const mainJson = await updateJsonFile(
|
|
||||||
join(appsFolder, app._id, "pages", "main", "page.json"),
|
|
||||||
app
|
|
||||||
)
|
|
||||||
|
|
||||||
await buildPage(ctx.config, app._id, "main", {
|
|
||||||
page: mainJson,
|
|
||||||
screens: await loadScreens(newAppFolder, "main"),
|
|
||||||
})
|
|
||||||
|
|
||||||
const unauthenticatedJson = await updateJsonFile(
|
|
||||||
join(appsFolder, app._id, "pages", "unauthenticated", "page.json"),
|
|
||||||
app
|
|
||||||
)
|
|
||||||
|
|
||||||
await buildPage(ctx.config, app._id, "unauthenticated", {
|
|
||||||
page: unauthenticatedJson,
|
|
||||||
screens: await loadScreens(newAppFolder, "unauthenticated"),
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return newAppFolder
|
return newAppFolder
|
||||||
}
|
}
|
||||||
|
|
||||||
const loadScreens = async (appFolder, page) => {
|
|
||||||
const screensFolder = join(appFolder, "pages", page, "screens")
|
|
||||||
|
|
||||||
const screenFiles = (await fs.readdir(screensFolder)).filter(s =>
|
|
||||||
s.endsWith(".json")
|
|
||||||
)
|
|
||||||
|
|
||||||
let screens = []
|
|
||||||
for (let file of screenFiles) {
|
|
||||||
screens.push(await fs.readJSON(join(screensFolder, file)))
|
|
||||||
}
|
|
||||||
return screens
|
|
||||||
}
|
|
||||||
|
|
||||||
const updateJsonFile = async (filePath, app) => {
|
|
||||||
const json = await readFile(filePath, "utf8")
|
|
||||||
const newJson = sqrl.Render(json, app)
|
|
||||||
await writeFile(filePath, newJson, "utf8")
|
|
||||||
return JSON.parse(newJson)
|
|
||||||
}
|
|
||||||
|
|
||||||
const npmFriendlyAppName = name =>
|
|
||||||
name
|
|
||||||
.replace(/_/g, "")
|
|
||||||
.replace(/./g, "")
|
|
||||||
.replace(/ /g, "")
|
|
||||||
.toLowerCase()
|
|
||||||
|
|
|
@ -42,6 +42,13 @@ exports.isInvalidationComplete = async function(
|
||||||
return resp.Invalidation.Status === "Completed"
|
return resp.Invalidation.Status === "Completed"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finalises the deployment, updating the quota for the user API key
|
||||||
|
* The verification process returns the levels to update to.
|
||||||
|
* Calls the "deployment-success" lambda.
|
||||||
|
* @param {object} quota The usage quota levels returned from the verifyDeploy
|
||||||
|
* @returns {Promise<object>} The usage has been updated against the user API key.
|
||||||
|
*/
|
||||||
exports.updateDeploymentQuota = async function(quota) {
|
exports.updateDeploymentQuota = async function(quota) {
|
||||||
const DEPLOYMENT_SUCCESS_URL =
|
const DEPLOYMENT_SUCCESS_URL =
|
||||||
env.DEPLOYMENT_CREDENTIALS_URL + "deploy/success"
|
env.DEPLOYMENT_CREDENTIALS_URL + "deploy/success"
|
||||||
|
@ -67,7 +74,8 @@ exports.updateDeploymentQuota = async function(quota) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Verifies the users API key and
|
* Verifies the users API key and
|
||||||
* Verifies that the deployment fits within the quota of the user,
|
* Verifies that the deployment fits within the quota of the user
|
||||||
|
* Links to the "check-api-key" lambda.
|
||||||
* @param {String} appId - appId being deployed
|
* @param {String} appId - appId being deployed
|
||||||
* @param {String} appId - appId being deployed
|
* @param {String} appId - appId being deployed
|
||||||
* @param {quota} quota - current quota being changed with this application
|
* @param {quota} quota - current quota being changed with this application
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
const CouchDB = require("../../db/client")
|
||||||
|
const { generatePageID } = require("../../db/utils")
|
||||||
|
const compileStaticAssetsForPage = require("../../utilities/builder/compileStaticAssetsForPage")
|
||||||
|
|
||||||
|
exports.save = async function(ctx) {
|
||||||
|
const db = new CouchDB(ctx.user.appId)
|
||||||
|
|
||||||
|
const appPackage = ctx.request.body
|
||||||
|
|
||||||
|
const page = await db.get(ctx.params.pageId)
|
||||||
|
await compileStaticAssetsForPage(ctx.user.appId, page.name, ctx.request.body)
|
||||||
|
|
||||||
|
// remove special doc props which couch will complain about
|
||||||
|
delete appPackage.page._css
|
||||||
|
delete appPackage.page._screens
|
||||||
|
appPackage.page._id = appPackage.page._id || generatePageID()
|
||||||
|
ctx.body = await db.put(appPackage.page)
|
||||||
|
ctx.status = 200
|
||||||
|
}
|
|
@ -1,17 +1,48 @@
|
||||||
/**
|
const CouchDB = require("../../db")
|
||||||
* This controller is not currently fully implemented. Screens are
|
const { getScreenParams, generateScreenID } = require("../../db/utils")
|
||||||
* currently managed as part of the pages API, please look in api/routes/page.js
|
|
||||||
* for routes and controllers.
|
|
||||||
*/
|
|
||||||
|
|
||||||
exports.fetch = async ctx => {
|
exports.fetch = async ctx => {
|
||||||
ctx.throw(501)
|
const db = new CouchDB(ctx.user.appId)
|
||||||
|
|
||||||
|
const screens = await db.allDocs(
|
||||||
|
getScreenParams(null, {
|
||||||
|
include_docs: true,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
ctx.body = screens.rows.map(element => element.doc)
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.find = async ctx => {
|
||||||
|
const db = new CouchDB(ctx.user.appId)
|
||||||
|
|
||||||
|
const screens = await db.allDocs(
|
||||||
|
getScreenParams(ctx.params.pageId, {
|
||||||
|
include_docs: true,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
ctx.body = screens.response.rows
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.save = async ctx => {
|
exports.save = async ctx => {
|
||||||
ctx.throw(501)
|
const appId = ctx.user.appId
|
||||||
|
const db = new CouchDB(appId)
|
||||||
|
const screen = ctx.request.body
|
||||||
|
|
||||||
|
if (!screen._id) {
|
||||||
|
screen._id = generateScreenID(ctx.params.pageId)
|
||||||
|
}
|
||||||
|
delete screen._css
|
||||||
|
const response = await db.put(screen)
|
||||||
|
|
||||||
|
ctx.message = `Screen ${screen.name} saved.`
|
||||||
|
ctx.body = response
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.destroy = async ctx => {
|
exports.destroy = async ctx => {
|
||||||
ctx.throw(501)
|
const db = new CouchDB(ctx.user.appId)
|
||||||
|
await db.remove(ctx.params.screenId, ctx.params.revId)
|
||||||
|
ctx.message = "Screen deleted successfully"
|
||||||
|
ctx.status = 200
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,25 +2,34 @@ const fetch = require("node-fetch")
|
||||||
const {
|
const {
|
||||||
downloadTemplate,
|
downloadTemplate,
|
||||||
exportTemplateFromApp,
|
exportTemplateFromApp,
|
||||||
|
getLocalTemplates,
|
||||||
} = require("../../utilities/templates")
|
} = require("../../utilities/templates")
|
||||||
|
const env = require("../../environment")
|
||||||
|
|
||||||
|
// development flag, can be used to test against templates exported locally
|
||||||
const DEFAULT_TEMPLATES_BUCKET =
|
const DEFAULT_TEMPLATES_BUCKET =
|
||||||
"prod-budi-templates.s3-eu-west-1.amazonaws.com"
|
"prod-budi-templates.s3-eu-west-1.amazonaws.com"
|
||||||
|
|
||||||
exports.fetch = async function(ctx) {
|
exports.fetch = async function(ctx) {
|
||||||
const { type = "app" } = ctx.query
|
const { type = "app" } = ctx.query
|
||||||
|
|
||||||
const response = await fetch(
|
if (env.LOCAL_TEMPLATES) {
|
||||||
`https://${DEFAULT_TEMPLATES_BUCKET}/manifest.json`
|
ctx.body = Object.values(getLocalTemplates()[type])
|
||||||
)
|
} else {
|
||||||
const json = await response.json()
|
const response = await fetch(
|
||||||
ctx.body = Object.values(json.templates[type])
|
`https://${DEFAULT_TEMPLATES_BUCKET}/manifest.json`
|
||||||
|
)
|
||||||
|
const json = await response.json()
|
||||||
|
ctx.body = Object.values(json.templates[type])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.downloadTemplate = async function(ctx) {
|
exports.downloadTemplate = async function(ctx) {
|
||||||
const { type, name } = ctx.params
|
const { type, name } = ctx.params
|
||||||
|
|
||||||
await downloadTemplate(type, name)
|
if (!env.LOCAL_TEMPLATES) {
|
||||||
|
await downloadTemplate(type, name)
|
||||||
|
}
|
||||||
|
|
||||||
ctx.body = {
|
ctx.body = {
|
||||||
message: `template ${type}:${name} downloaded successfully.`,
|
message: `template ${type}:${name} downloaded successfully.`,
|
||||||
|
|
|
@ -37,15 +37,22 @@ exports.create = async function(ctx) {
|
||||||
accessLevelId,
|
accessLevelId,
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await db.post(user)
|
try {
|
||||||
|
const response = await db.post(user)
|
||||||
ctx.status = 200
|
ctx.status = 200
|
||||||
ctx.message = "User created successfully."
|
ctx.message = "User created successfully."
|
||||||
ctx.userId = response._id
|
ctx.userId = response._id
|
||||||
ctx.body = {
|
ctx.body = {
|
||||||
_rev: response.rev,
|
_rev: response.rev,
|
||||||
username,
|
username,
|
||||||
name,
|
name,
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err.status === 409) {
|
||||||
|
ctx.throw(400, "User exists already")
|
||||||
|
} else {
|
||||||
|
ctx.throw(err.status, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@ const { isDev } = require("../utilities")
|
||||||
const {
|
const {
|
||||||
authRoutes,
|
authRoutes,
|
||||||
pageRoutes,
|
pageRoutes,
|
||||||
|
screenRoutes,
|
||||||
userRoutes,
|
userRoutes,
|
||||||
deployRoutes,
|
deployRoutes,
|
||||||
applicationRoutes,
|
applicationRoutes,
|
||||||
|
@ -97,6 +98,9 @@ router.use(templatesRoutes.allowedMethods())
|
||||||
router.use(pageRoutes.routes())
|
router.use(pageRoutes.routes())
|
||||||
router.use(pageRoutes.allowedMethods())
|
router.use(pageRoutes.allowedMethods())
|
||||||
|
|
||||||
|
router.use(screenRoutes.routes())
|
||||||
|
router.use(screenRoutes.allowedMethods())
|
||||||
|
|
||||||
router.use(applicationRoutes.routes())
|
router.use(applicationRoutes.routes())
|
||||||
router.use(applicationRoutes.allowedMethods())
|
router.use(applicationRoutes.allowedMethods())
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
const authRoutes = require("./auth")
|
const authRoutes = require("./auth")
|
||||||
const pageRoutes = require("./pages")
|
const pageRoutes = require("./pages")
|
||||||
|
const screenRoutes = require("./screen")
|
||||||
const userRoutes = require("./user")
|
const userRoutes = require("./user")
|
||||||
const applicationRoutes = require("./application")
|
const applicationRoutes = require("./application")
|
||||||
const tableRoutes = require("./table")
|
const tableRoutes = require("./table")
|
||||||
|
@ -19,6 +20,7 @@ module.exports = {
|
||||||
deployRoutes,
|
deployRoutes,
|
||||||
authRoutes,
|
authRoutes,
|
||||||
pageRoutes,
|
pageRoutes,
|
||||||
|
screenRoutes,
|
||||||
userRoutes,
|
userRoutes,
|
||||||
applicationRoutes,
|
applicationRoutes,
|
||||||
rowRoutes,
|
rowRoutes,
|
||||||
|
|
|
@ -1,117 +1,10 @@
|
||||||
const Router = require("@koa/router")
|
const Router = require("@koa/router")
|
||||||
const StatusCodes = require("../../utilities/statusCodes")
|
|
||||||
const joiValidator = require("../../middleware/joi-validator")
|
|
||||||
const Joi = require("joi")
|
|
||||||
const {
|
|
||||||
listScreens,
|
|
||||||
saveScreen,
|
|
||||||
buildPage,
|
|
||||||
renameScreen,
|
|
||||||
deleteScreen,
|
|
||||||
} = require("../../utilities/builder")
|
|
||||||
const authorized = require("../../middleware/authorized")
|
const authorized = require("../../middleware/authorized")
|
||||||
const { BUILDER } = require("../../utilities/accessLevels")
|
const { BUILDER } = require("../../utilities/accessLevels")
|
||||||
|
const controller = require("../controllers/page")
|
||||||
|
|
||||||
const router = Router()
|
const router = Router()
|
||||||
|
|
||||||
function generateSaveValidation() {
|
router.post("/api/pages/:pageId", authorized(BUILDER), controller.save)
|
||||||
// prettier-ignore
|
|
||||||
return joiValidator.body(Joi.object({
|
|
||||||
_css: Joi.string().allow(""),
|
|
||||||
name: Joi.string().required(),
|
|
||||||
route: Joi.string().required(),
|
|
||||||
props: Joi.object({
|
|
||||||
_id: Joi.string().required(),
|
|
||||||
_component: Joi.string().required(),
|
|
||||||
_children: Joi.array().required(),
|
|
||||||
_instanceName: Joi.string().required(),
|
|
||||||
_styles: Joi.object().required(),
|
|
||||||
type: Joi.string().optional(),
|
|
||||||
table: Joi.string().optional(),
|
|
||||||
}).required().unknown(true),
|
|
||||||
}).unknown(true))
|
|
||||||
}
|
|
||||||
|
|
||||||
function generatePatchValidation() {
|
|
||||||
return joiValidator.body(
|
|
||||||
Joi.object({
|
|
||||||
oldname: Joi.string().required(),
|
|
||||||
newname: Joi.string().required(),
|
|
||||||
}).unknown(true)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
router.post(
|
|
||||||
"/_builder/api/:appId/pages/:pageName",
|
|
||||||
authorized(BUILDER),
|
|
||||||
async ctx => {
|
|
||||||
await buildPage(
|
|
||||||
ctx.config,
|
|
||||||
ctx.params.appId,
|
|
||||||
ctx.params.pageName,
|
|
||||||
ctx.request.body
|
|
||||||
)
|
|
||||||
ctx.response.status = StatusCodes.OK
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
router.get(
|
|
||||||
"/_builder/api/:appId/pages/:pagename/screens",
|
|
||||||
authorized(BUILDER),
|
|
||||||
async ctx => {
|
|
||||||
ctx.body = await listScreens(
|
|
||||||
ctx.config,
|
|
||||||
ctx.params.appId,
|
|
||||||
ctx.params.pagename
|
|
||||||
)
|
|
||||||
ctx.response.status = StatusCodes.OK
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
router.post(
|
|
||||||
"/_builder/api/:appId/pages/:pagename/screen",
|
|
||||||
authorized(BUILDER),
|
|
||||||
generateSaveValidation(),
|
|
||||||
async ctx => {
|
|
||||||
ctx.body = await saveScreen(
|
|
||||||
ctx.config,
|
|
||||||
ctx.params.appId,
|
|
||||||
ctx.params.pagename,
|
|
||||||
ctx.request.body
|
|
||||||
)
|
|
||||||
ctx.response.status = StatusCodes.OK
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
router.patch(
|
|
||||||
"/_builder/api/:appname/pages/:pagename/screen",
|
|
||||||
authorized(BUILDER),
|
|
||||||
generatePatchValidation(),
|
|
||||||
async ctx => {
|
|
||||||
await renameScreen(
|
|
||||||
ctx.config,
|
|
||||||
ctx.params.appname,
|
|
||||||
ctx.params.pagename,
|
|
||||||
ctx.request.body.oldname,
|
|
||||||
ctx.request.body.newname
|
|
||||||
)
|
|
||||||
ctx.response.status = StatusCodes.OK
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
router.delete(
|
|
||||||
"/_builder/api/pages/:pagename/screens/:id",
|
|
||||||
authorized(BUILDER),
|
|
||||||
async ctx => {
|
|
||||||
await deleteScreen(
|
|
||||||
ctx.config,
|
|
||||||
ctx.user.appId,
|
|
||||||
ctx.params.pagename,
|
|
||||||
ctx.params.id
|
|
||||||
)
|
|
||||||
|
|
||||||
ctx.response.status = StatusCodes.OK
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
module.exports = router
|
module.exports = router
|
||||||
|
|
|
@ -2,12 +2,42 @@ const Router = require("@koa/router")
|
||||||
const controller = require("../controllers/screen")
|
const controller = require("../controllers/screen")
|
||||||
const authorized = require("../../middleware/authorized")
|
const authorized = require("../../middleware/authorized")
|
||||||
const { BUILDER } = require("../../utilities/accessLevels")
|
const { BUILDER } = require("../../utilities/accessLevels")
|
||||||
|
const joiValidator = require("../../middleware/joi-validator")
|
||||||
|
const Joi = require("joi")
|
||||||
|
|
||||||
const router = Router()
|
const router = Router()
|
||||||
|
|
||||||
|
function generateSaveValidation() {
|
||||||
|
// prettier-ignore
|
||||||
|
return joiValidator.body(Joi.object({
|
||||||
|
_css: Joi.string().allow(""),
|
||||||
|
name: Joi.string().required(),
|
||||||
|
route: Joi.string().required(),
|
||||||
|
props: Joi.object({
|
||||||
|
_id: Joi.string().required(),
|
||||||
|
_component: Joi.string().required(),
|
||||||
|
_children: Joi.array().required(),
|
||||||
|
_instanceName: Joi.string().required(),
|
||||||
|
_styles: Joi.object().required(),
|
||||||
|
type: Joi.string().optional(),
|
||||||
|
table: Joi.string().optional(),
|
||||||
|
}).required().unknown(true),
|
||||||
|
}).unknown(true))
|
||||||
|
}
|
||||||
|
|
||||||
router
|
router
|
||||||
.get("/api/screens", authorized(BUILDER), controller.fetch)
|
.get("/api/screens", authorized(BUILDER), controller.fetch)
|
||||||
.post("/api/screens", authorized(BUILDER), controller.save)
|
.get("/api/screens/:pageId", authorized(BUILDER), controller.find)
|
||||||
.delete("/api/:screenId/:revId", authorized(BUILDER), controller.destroy)
|
.post(
|
||||||
|
"/api/screens/:pageId",
|
||||||
|
authorized(BUILDER),
|
||||||
|
generateSaveValidation(),
|
||||||
|
controller.save
|
||||||
|
)
|
||||||
|
.delete(
|
||||||
|
"/api/screens/:screenId/:revId",
|
||||||
|
authorized(BUILDER),
|
||||||
|
controller.destroy
|
||||||
|
)
|
||||||
|
|
||||||
module.exports = router
|
module.exports = router
|
||||||
|
|
|
@ -0,0 +1,221 @@
|
||||||
|
const PageTypes = {
|
||||||
|
MAIN: "main",
|
||||||
|
UNAUTHENTICATED: "unauthenticated",
|
||||||
|
}
|
||||||
|
|
||||||
|
const MAIN = {
|
||||||
|
componentLibraries: ["@budibase/standard-components"],
|
||||||
|
title: "{{ name }}",
|
||||||
|
favicon: "./_shared/favicon.png",
|
||||||
|
stylesheets: [],
|
||||||
|
name: PageTypes.MAIN,
|
||||||
|
props: {
|
||||||
|
_id: "private-master-root",
|
||||||
|
_component: "@budibase/standard-components/container",
|
||||||
|
_children: [
|
||||||
|
{
|
||||||
|
_id: "c74f07266980c4b6eafc33e2a6caa783d",
|
||||||
|
_component: "@budibase/standard-components/container",
|
||||||
|
_styles: {
|
||||||
|
normal: {
|
||||||
|
display: "flex",
|
||||||
|
"flex-direction": "row",
|
||||||
|
"justify-content": "flex-start",
|
||||||
|
"align-items": "flex-start",
|
||||||
|
background: "#fff",
|
||||||
|
width: "100%",
|
||||||
|
"box-shadow": "0 1px 2px 0 rgba(0, 0, 0, 0.05)",
|
||||||
|
},
|
||||||
|
hover: {},
|
||||||
|
active: {},
|
||||||
|
selected: {},
|
||||||
|
},
|
||||||
|
_code: "",
|
||||||
|
className: "",
|
||||||
|
onLoad: [],
|
||||||
|
type: "div",
|
||||||
|
_appId: "inst_app_80b_f158d4057d2c4bedb0042d42fda8abaf",
|
||||||
|
_instanceName: "Header",
|
||||||
|
_children: [
|
||||||
|
{
|
||||||
|
_id: "49e0e519-9e5e-4127-885a-ee6a0a49e2c1",
|
||||||
|
_component: "@budibase/standard-components/Navigation",
|
||||||
|
_styles: {
|
||||||
|
normal: {
|
||||||
|
"max-width": "1400px",
|
||||||
|
"margin-left": "auto",
|
||||||
|
"margin-right": "auto",
|
||||||
|
padding: "20px",
|
||||||
|
color: "#757575",
|
||||||
|
"font-weight": "400",
|
||||||
|
"font-size": "16px",
|
||||||
|
flex: "1 1 auto",
|
||||||
|
},
|
||||||
|
hover: {},
|
||||||
|
active: {},
|
||||||
|
selected: {},
|
||||||
|
},
|
||||||
|
_code: "",
|
||||||
|
logoUrl:
|
||||||
|
"https://d33wubrfki0l68.cloudfront.net/aac32159d7207b5085e74a7ef67afbb7027786c5/2b1fd/img/logo/bb-emblem.svg",
|
||||||
|
title: "",
|
||||||
|
backgroundColor: "",
|
||||||
|
color: "",
|
||||||
|
borderWidth: "",
|
||||||
|
borderColor: "",
|
||||||
|
borderStyle: "",
|
||||||
|
_appId: "inst_cf8ace4_69efc0d72e6f443db2d4c902c14d9394",
|
||||||
|
_instanceName: "Navigation",
|
||||||
|
_children: [
|
||||||
|
{
|
||||||
|
_id: "48b35328-4c91-4343-a6a3-1a1fd77b3386",
|
||||||
|
_component: "@budibase/standard-components/link",
|
||||||
|
_styles: {
|
||||||
|
normal: {
|
||||||
|
"font-family": "Inter",
|
||||||
|
"font-weight": "500",
|
||||||
|
color: "#000000",
|
||||||
|
"text-decoration-line": "none",
|
||||||
|
"font-size": "16px",
|
||||||
|
},
|
||||||
|
hover: {
|
||||||
|
color: "#4285f4",
|
||||||
|
},
|
||||||
|
active: {},
|
||||||
|
selected: {},
|
||||||
|
},
|
||||||
|
_code: "",
|
||||||
|
url: "/",
|
||||||
|
openInNewTab: false,
|
||||||
|
text: "Home",
|
||||||
|
color: "",
|
||||||
|
hoverColor: "",
|
||||||
|
underline: false,
|
||||||
|
fontSize: "",
|
||||||
|
fontFamily: "initial",
|
||||||
|
_appId: "inst_cf8ace4_69efc0d72e6f443db2d4c902c14d9394",
|
||||||
|
_instanceName: "Home Link",
|
||||||
|
_children: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_id: "7fcf11e4-6f5b-4085-8e0d-9f3d44c98967",
|
||||||
|
_component: "##builtin/screenslot",
|
||||||
|
_styles: {
|
||||||
|
normal: {
|
||||||
|
flex: "1 1 auto",
|
||||||
|
display: "flex",
|
||||||
|
"flex-direction": "column",
|
||||||
|
"justify-content": "flex-start",
|
||||||
|
"align-items": "stretch",
|
||||||
|
"max-width": "100%",
|
||||||
|
"margin-left": "20px",
|
||||||
|
"margin-right": "20px",
|
||||||
|
width: "1400px",
|
||||||
|
padding: "20px",
|
||||||
|
},
|
||||||
|
hover: {},
|
||||||
|
active: {},
|
||||||
|
selected: {},
|
||||||
|
},
|
||||||
|
_code: "",
|
||||||
|
_children: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
type: "div",
|
||||||
|
_styles: {
|
||||||
|
active: {},
|
||||||
|
hover: {},
|
||||||
|
normal: {
|
||||||
|
display: "flex",
|
||||||
|
"flex-direction": "column",
|
||||||
|
"align-items": "center",
|
||||||
|
"justify-content": "flex-start",
|
||||||
|
"margin-right": "auto",
|
||||||
|
"margin-left": "auto",
|
||||||
|
"min-height": "100%",
|
||||||
|
"background-image":
|
||||||
|
"linear-gradient(135deg, rgba(252,215,212,1) 20%, rgba(207,218,255,1) 100%);",
|
||||||
|
},
|
||||||
|
selected: {},
|
||||||
|
},
|
||||||
|
_code: "",
|
||||||
|
className: "",
|
||||||
|
onLoad: [],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const UNAUTHENTICATED = {
|
||||||
|
componentLibraries: ["@budibase/standard-components"],
|
||||||
|
title: "{{ name }}",
|
||||||
|
favicon: "./_shared/favicon.png",
|
||||||
|
stylesheets: [],
|
||||||
|
name: PageTypes.UNAUTHENTICATED,
|
||||||
|
props: {
|
||||||
|
_id: "public-master-root",
|
||||||
|
_component: "@budibase/standard-components/container",
|
||||||
|
_children: [
|
||||||
|
{
|
||||||
|
_id: "686c252d-dbf2-4e28-9078-414ba4719759",
|
||||||
|
_component: "@budibase/standard-components/login",
|
||||||
|
_styles: {
|
||||||
|
normal: {
|
||||||
|
padding: "64px",
|
||||||
|
background: "rgba(255, 255, 255, 0.4)",
|
||||||
|
"border-radius": "0.5rem",
|
||||||
|
"margin-top": "0px",
|
||||||
|
margin: "0px",
|
||||||
|
"line-height": "1",
|
||||||
|
"box-shadow":
|
||||||
|
"0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)",
|
||||||
|
"font-size": "16px",
|
||||||
|
"font-family": "Inter",
|
||||||
|
flex: "0 1 auto",
|
||||||
|
transform: "0",
|
||||||
|
},
|
||||||
|
hover: {},
|
||||||
|
active: {},
|
||||||
|
selected: {},
|
||||||
|
},
|
||||||
|
_code: "",
|
||||||
|
loginRedirect: "",
|
||||||
|
usernameLabel: "Username",
|
||||||
|
passwordLabel: "Password",
|
||||||
|
loginButtonLabel: "Login",
|
||||||
|
buttonClass: "",
|
||||||
|
_instanceName: "Login",
|
||||||
|
inputClass: "",
|
||||||
|
_children: [],
|
||||||
|
title: "Log in to {{ name }}",
|
||||||
|
buttonText: "Log In",
|
||||||
|
logo:
|
||||||
|
"https://d33wubrfki0l68.cloudfront.net/aac32159d7207b5085e74a7ef67afbb7027786c5/2b1fd/img/logo/bb-emblem.svg",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
type: "div",
|
||||||
|
_styles: {
|
||||||
|
active: {},
|
||||||
|
hover: {},
|
||||||
|
normal: {
|
||||||
|
display: "flex",
|
||||||
|
"flex-direction": "column",
|
||||||
|
"align-items": "center",
|
||||||
|
"justify-content": "center",
|
||||||
|
"margin-right": "auto",
|
||||||
|
"margin-left": "auto",
|
||||||
|
"min-height": "100%",
|
||||||
|
"background-image":
|
||||||
|
"linear-gradient(135deg, rgba(252,215,212,1) 20%, rgba(207,218,255,1) 100%);",
|
||||||
|
},
|
||||||
|
selected: {},
|
||||||
|
},
|
||||||
|
_code: "",
|
||||||
|
className: "",
|
||||||
|
onLoad: [],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { MAIN, UNAUTHENTICATED, PageTypes }
|
|
@ -0,0 +1,103 @@
|
||||||
|
exports.HOME_SCREEN = {
|
||||||
|
description: "",
|
||||||
|
url: "",
|
||||||
|
props: {
|
||||||
|
_id: "d834fea2-1b3e-4320-ab34-f9009f5ecc59",
|
||||||
|
_component: "@budibase/standard-components/container",
|
||||||
|
_styles: {
|
||||||
|
normal: {
|
||||||
|
flex: "1 1 auto",
|
||||||
|
display: "flex",
|
||||||
|
"flex-direction": "column",
|
||||||
|
"justify-content": "flex-start",
|
||||||
|
"align-items": "stretch",
|
||||||
|
},
|
||||||
|
hover: {},
|
||||||
|
active: {},
|
||||||
|
selected: {},
|
||||||
|
},
|
||||||
|
_code: "",
|
||||||
|
className: "",
|
||||||
|
onLoad: [],
|
||||||
|
type: "div",
|
||||||
|
_children: [
|
||||||
|
{
|
||||||
|
_id: "ef60083f-4a02-4df3-80f3-a0d3d16847e7",
|
||||||
|
_component: "@budibase/standard-components/heading",
|
||||||
|
_styles: {
|
||||||
|
normal: {
|
||||||
|
"text-align": "left",
|
||||||
|
},
|
||||||
|
hover: {},
|
||||||
|
active: {},
|
||||||
|
selected: {},
|
||||||
|
},
|
||||||
|
_code: "",
|
||||||
|
className: "",
|
||||||
|
text: "Welcome to your Budibase App 👋",
|
||||||
|
type: "h2",
|
||||||
|
_appId: "inst_cf8ace4_69efc0d72e6f443db2d4c902c14d9394",
|
||||||
|
_instanceName: "Heading",
|
||||||
|
_children: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_id: "cbbf41b27c2b44d1abba38bb694880c6a",
|
||||||
|
_component: "@budibase/standard-components/container",
|
||||||
|
_styles: {
|
||||||
|
normal: {
|
||||||
|
display: "flex",
|
||||||
|
"flex-direction": "column",
|
||||||
|
"justify-content": "center",
|
||||||
|
"align-items": "stretch",
|
||||||
|
flex: "1 1 auto",
|
||||||
|
"border-width": "4px",
|
||||||
|
"border-style": "Dashed",
|
||||||
|
"margin-bottom": "32px",
|
||||||
|
},
|
||||||
|
hover: {},
|
||||||
|
active: {},
|
||||||
|
selected: {},
|
||||||
|
},
|
||||||
|
_code: "",
|
||||||
|
className: "",
|
||||||
|
onLoad: [],
|
||||||
|
type: "div",
|
||||||
|
_appId: "inst_app_2cc_ca3383f896034e9295345c05f7dfca0c",
|
||||||
|
_instanceName: "Video Container",
|
||||||
|
_children: [
|
||||||
|
{
|
||||||
|
_id: "c07d752cb3e544b418088fa9be84ba2e4",
|
||||||
|
_component: "@budibase/standard-components/embed",
|
||||||
|
_styles: {
|
||||||
|
normal: {
|
||||||
|
width: "100%",
|
||||||
|
flex: "1 1 auto",
|
||||||
|
opacity: "0",
|
||||||
|
"transition-property": "Opacity",
|
||||||
|
"transition-duration": "1s",
|
||||||
|
"transition-timing-function:": "ease-in",
|
||||||
|
},
|
||||||
|
hover: {
|
||||||
|
"transition-property": "Opacity",
|
||||||
|
"transition-duration": "1s",
|
||||||
|
"transition-timing-function:": "ease-out",
|
||||||
|
opacity: "1",
|
||||||
|
},
|
||||||
|
active: {},
|
||||||
|
selected: {},
|
||||||
|
},
|
||||||
|
_code: "",
|
||||||
|
embed:
|
||||||
|
'<iframe width="560" height="315" src="https://www.youtube.com/embed/dQw4w9WgXcQ" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>',
|
||||||
|
_appId: "inst_app_2cc_ca3383f896034e9295345c05f7dfca0c",
|
||||||
|
_instanceName: "Rick Astley Video",
|
||||||
|
_children: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
_instanceName: "Home",
|
||||||
|
},
|
||||||
|
route: "/",
|
||||||
|
name: "d834fea2-1b3e-4320-ab34-f9009f5ecc59",
|
||||||
|
}
|
|
@ -26,4 +26,18 @@ const Pouch = PouchDB.defaults(POUCH_DB_DEFAULTS)
|
||||||
|
|
||||||
allDbs(Pouch)
|
allDbs(Pouch)
|
||||||
|
|
||||||
|
// replicate your local levelDB pouch to a running HTTP compliant couch or pouchdb server.
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
function replicateLocal() {
|
||||||
|
Pouch.allDbs().then(dbs => {
|
||||||
|
for (let db of dbs) {
|
||||||
|
new Pouch(db).sync(
|
||||||
|
new PouchDB(`http://127.0.0.1:5984/${db}`, { live: true })
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
replicateLocal()
|
||||||
|
|
||||||
module.exports = Pouch
|
module.exports = Pouch
|
||||||
|
|
|
@ -13,6 +13,8 @@ const DocumentTypes = {
|
||||||
ACCESS_LEVEL: "ac",
|
ACCESS_LEVEL: "ac",
|
||||||
WEBHOOK: "wh",
|
WEBHOOK: "wh",
|
||||||
INSTANCE: "inst",
|
INSTANCE: "inst",
|
||||||
|
PAGE: "page",
|
||||||
|
SCREEN: "screen",
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.DocumentTypes = DocumentTypes
|
exports.DocumentTypes = DocumentTypes
|
||||||
|
@ -175,6 +177,36 @@ exports.generateWebhookID = () => {
|
||||||
return `${DocumentTypes.WEBHOOK}${SEPARATOR}${newid()}`
|
return `${DocumentTypes.WEBHOOK}${SEPARATOR}${newid()}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a new page ID.
|
||||||
|
* @returns {string} The new page ID which the page doc can be stored under.
|
||||||
|
*/
|
||||||
|
exports.generatePageID = () => {
|
||||||
|
return `${DocumentTypes.PAGE}${SEPARATOR}${newid()}`
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets parameters for retrieving pages, this is a utility function for the getDocParams function.
|
||||||
|
*/
|
||||||
|
exports.getPageParams = (pageId = null, otherProps = {}) => {
|
||||||
|
return getDocParams(DocumentTypes.PAGE, pageId, otherProps)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a new screen ID.
|
||||||
|
* @returns {string} The new screen ID which the screen doc can be stored under.
|
||||||
|
*/
|
||||||
|
exports.generateScreenID = pageId => {
|
||||||
|
return `${DocumentTypes.SCREEN}${SEPARATOR}${pageId}${SEPARATOR}${newid()}`
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets parameters for retrieving screens for a particular page, this is a utility function for the getDocParams function.
|
||||||
|
*/
|
||||||
|
exports.getScreenParams = (pageId = null, otherProps = {}) => {
|
||||||
|
return getDocParams(DocumentTypes.SCREEN, pageId, otherProps)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets parameters for retrieving a webhook, this is a utility function for the getDocParams function.
|
* Gets parameters for retrieving a webhook, this is a utility function for the getDocParams function.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -34,6 +34,7 @@ module.exports = {
|
||||||
USERID_API_KEY: process.env.USERID_API_KEY,
|
USERID_API_KEY: process.env.USERID_API_KEY,
|
||||||
ENABLE_ANALYTICS: process.env.ENABLE_ANALYTICS,
|
ENABLE_ANALYTICS: process.env.ENABLE_ANALYTICS,
|
||||||
DEPLOYMENT_DB_URL: process.env.DEPLOYMENT_DB_URL,
|
DEPLOYMENT_DB_URL: process.env.DEPLOYMENT_DB_URL,
|
||||||
|
LOCAL_TEMPLATES: process.env.LOCAL_TEMPLATES,
|
||||||
_set(key, value) {
|
_set(key, value) {
|
||||||
process.env[key] = value
|
process.env[key] = value
|
||||||
module.exports[key] = value
|
module.exports[key] = value
|
||||||
|
|
|
@ -1,144 +0,0 @@
|
||||||
{
|
|
||||||
"componentLibraries": [
|
|
||||||
"@budibase/standard-components"
|
|
||||||
],
|
|
||||||
"title": "{{ name }}",
|
|
||||||
"favicon": "./_shared/favicon.png",
|
|
||||||
"stylesheets": [],
|
|
||||||
"props": {
|
|
||||||
"_id": "private-master-root",
|
|
||||||
"_component": "@budibase/standard-components/container",
|
|
||||||
"_children": [
|
|
||||||
{
|
|
||||||
"_id": "c74f07266980c4b6eafc33e2a6caa783d",
|
|
||||||
"_component": "@budibase/standard-components/container",
|
|
||||||
"_styles": {
|
|
||||||
"normal": {
|
|
||||||
"display": "flex",
|
|
||||||
"flex-direction": "row",
|
|
||||||
"justify-content": "flex-start",
|
|
||||||
"align-items": "flex-start",
|
|
||||||
"background": "#fff",
|
|
||||||
"width": "100%",
|
|
||||||
"box-shadow": "0 1px 2px 0 rgba(0, 0, 0, 0.05)"
|
|
||||||
},
|
|
||||||
"hover": {},
|
|
||||||
"active": {},
|
|
||||||
"selected": {}
|
|
||||||
},
|
|
||||||
"_code": "",
|
|
||||||
"className": "",
|
|
||||||
"onLoad": [],
|
|
||||||
"type": "div",
|
|
||||||
"_appId": "inst_app_80b_f158d4057d2c4bedb0042d42fda8abaf",
|
|
||||||
"_instanceName": "Header",
|
|
||||||
"_children": [
|
|
||||||
{
|
|
||||||
"_id": "49e0e519-9e5e-4127-885a-ee6a0a49e2c1",
|
|
||||||
"_component": "@budibase/standard-components/Navigation",
|
|
||||||
"_styles": {
|
|
||||||
"normal": {
|
|
||||||
"max-width": "1400px",
|
|
||||||
"margin-left": "auto",
|
|
||||||
"margin-right": "auto",
|
|
||||||
"padding": "20px",
|
|
||||||
"color": "#757575",
|
|
||||||
"font-weight": "400",
|
|
||||||
"font-size": "16px",
|
|
||||||
"flex": "1 1 auto"
|
|
||||||
},
|
|
||||||
"hover": {},
|
|
||||||
"active": {},
|
|
||||||
"selected": {}
|
|
||||||
},
|
|
||||||
"_code": "",
|
|
||||||
"logoUrl": "https://d33wubrfki0l68.cloudfront.net/aac32159d7207b5085e74a7ef67afbb7027786c5/2b1fd/img/logo/bb-emblem.svg",
|
|
||||||
"title": "",
|
|
||||||
"backgroundColor": "",
|
|
||||||
"color": "",
|
|
||||||
"borderWidth": "",
|
|
||||||
"borderColor": "",
|
|
||||||
"borderStyle": "",
|
|
||||||
"_appId": "inst_cf8ace4_69efc0d72e6f443db2d4c902c14d9394",
|
|
||||||
"_instanceName": "Navigation",
|
|
||||||
"_children": [
|
|
||||||
{
|
|
||||||
"_id": "48b35328-4c91-4343-a6a3-1a1fd77b3386",
|
|
||||||
"_component": "@budibase/standard-components/link",
|
|
||||||
"_styles": {
|
|
||||||
"normal": {
|
|
||||||
"font-family": "Inter",
|
|
||||||
"font-weight": "500",
|
|
||||||
"color": "#000000",
|
|
||||||
"text-decoration-line": "none",
|
|
||||||
"font-size": "16px"
|
|
||||||
},
|
|
||||||
"hover": {
|
|
||||||
"color": "#4285f4"
|
|
||||||
},
|
|
||||||
"active": {},
|
|
||||||
"selected": {}
|
|
||||||
},
|
|
||||||
"_code": "",
|
|
||||||
"url": "/",
|
|
||||||
"openInNewTab": false,
|
|
||||||
"text": "Home",
|
|
||||||
"color": "",
|
|
||||||
"hoverColor": "",
|
|
||||||
"underline": false,
|
|
||||||
"fontSize": "",
|
|
||||||
"fontFamily": "initial",
|
|
||||||
"_appId": "inst_cf8ace4_69efc0d72e6f443db2d4c902c14d9394",
|
|
||||||
"_instanceName": "Home Link",
|
|
||||||
"_children": []
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"_id": "7fcf11e4-6f5b-4085-8e0d-9f3d44c98967",
|
|
||||||
"_component": "##builtin/screenslot",
|
|
||||||
"_styles": {
|
|
||||||
"normal": {
|
|
||||||
"flex": "1 1 auto",
|
|
||||||
"display": "flex",
|
|
||||||
"flex-direction": "column",
|
|
||||||
"justify-content": "flex-start",
|
|
||||||
"align-items": "stretch",
|
|
||||||
"max-width": "100%",
|
|
||||||
"margin-left": "20px",
|
|
||||||
"margin-right": "20px",
|
|
||||||
"width": "1400px",
|
|
||||||
"padding": "20px"
|
|
||||||
},
|
|
||||||
"hover": {},
|
|
||||||
"active": {},
|
|
||||||
"selected": {}
|
|
||||||
},
|
|
||||||
"_code": "",
|
|
||||||
"_children": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"type": "div",
|
|
||||||
"_styles": {
|
|
||||||
"active": {},
|
|
||||||
"hover": {},
|
|
||||||
"normal": {
|
|
||||||
"display": "flex",
|
|
||||||
"flex-direction": "column",
|
|
||||||
"align-items": "center",
|
|
||||||
"justify-content": "flex-start",
|
|
||||||
"margin-right": "auto",
|
|
||||||
"margin-left": "auto",
|
|
||||||
"min-height": "100%",
|
|
||||||
"background-image": "linear-gradient(135deg, rgba(252,215,212,1) 20%, rgba(207,218,255,1) 100%);"
|
|
||||||
},
|
|
||||||
"selected": {}
|
|
||||||
},
|
|
||||||
"_code": "",
|
|
||||||
"className": "",
|
|
||||||
"onLoad": []
|
|
||||||
},
|
|
||||||
"uiFunctions": ""
|
|
||||||
}
|
|
|
@ -1,102 +0,0 @@
|
||||||
{
|
|
||||||
"description": "",
|
|
||||||
"url": "",
|
|
||||||
"props": {
|
|
||||||
"_id": "d834fea2-1b3e-4320-ab34-f9009f5ecc59",
|
|
||||||
"_component": "@budibase/standard-components/container",
|
|
||||||
"_styles": {
|
|
||||||
"normal": {
|
|
||||||
"flex": "1 1 auto",
|
|
||||||
"display": "flex",
|
|
||||||
"flex-direction": "column",
|
|
||||||
"justify-content": "flex-start",
|
|
||||||
"align-items": "stretch"
|
|
||||||
},
|
|
||||||
"hover": {},
|
|
||||||
"active": {},
|
|
||||||
"selected": {}
|
|
||||||
},
|
|
||||||
"_code": "",
|
|
||||||
"className": "",
|
|
||||||
"onLoad": [],
|
|
||||||
"type": "div",
|
|
||||||
"_children": [
|
|
||||||
{
|
|
||||||
"_id": "ef60083f-4a02-4df3-80f3-a0d3d16847e7",
|
|
||||||
"_component": "@budibase/standard-components/heading",
|
|
||||||
"_styles": {
|
|
||||||
"normal": {
|
|
||||||
"text-align": "left"
|
|
||||||
},
|
|
||||||
"hover": {},
|
|
||||||
"active": {},
|
|
||||||
"selected": {}
|
|
||||||
},
|
|
||||||
"_code": "",
|
|
||||||
"className": "",
|
|
||||||
"text": "Welcome to your Budibase App 👋",
|
|
||||||
"type": "h2",
|
|
||||||
"_appId": "inst_cf8ace4_69efc0d72e6f443db2d4c902c14d9394",
|
|
||||||
"_instanceName": "Heading",
|
|
||||||
"_children": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"_id": "cbbf41b27c2b44d1abba38bb694880c6a",
|
|
||||||
"_component": "@budibase/standard-components/container",
|
|
||||||
"_styles": {
|
|
||||||
"normal": {
|
|
||||||
"display": "flex",
|
|
||||||
"flex-direction": "column",
|
|
||||||
"justify-content": "center",
|
|
||||||
"align-items": "stretch",
|
|
||||||
"flex": "1 1 auto",
|
|
||||||
"border-width": "4px",
|
|
||||||
"border-style": "Dashed",
|
|
||||||
"margin-bottom": "32px"
|
|
||||||
},
|
|
||||||
"hover": {},
|
|
||||||
"active": {},
|
|
||||||
"selected": {}
|
|
||||||
},
|
|
||||||
"_code": "",
|
|
||||||
"className": "",
|
|
||||||
"onLoad": [],
|
|
||||||
"type": "div",
|
|
||||||
"_appId": "inst_app_2cc_ca3383f896034e9295345c05f7dfca0c",
|
|
||||||
"_instanceName": "Video Container",
|
|
||||||
"_children": [
|
|
||||||
{
|
|
||||||
"_id": "c07d752cb3e544b418088fa9be84ba2e4",
|
|
||||||
"_component": "@budibase/standard-components/embed",
|
|
||||||
"_styles": {
|
|
||||||
"normal": {
|
|
||||||
"width": "100%",
|
|
||||||
"flex": "1 1 auto",
|
|
||||||
"opacity": "0",
|
|
||||||
"transition-property": "Opacity",
|
|
||||||
"transition-duration": "1s",
|
|
||||||
"transition-timing-function:": "ease-in"
|
|
||||||
},
|
|
||||||
"hover": {
|
|
||||||
"transition-property": "Opacity",
|
|
||||||
"transition-duration": "1s",
|
|
||||||
"transition-timing-function:": "ease-out",
|
|
||||||
"opacity": "1"
|
|
||||||
},
|
|
||||||
"active": {},
|
|
||||||
"selected": {}
|
|
||||||
},
|
|
||||||
"_code": "",
|
|
||||||
"embed": "<iframe width=\"560\" height=\"315\" src=\"https://www.youtube.com/embed/dQw4w9WgXcQ\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen></iframe>",
|
|
||||||
"_appId": "inst_app_2cc_ca3383f896034e9295345c05f7dfca0c",
|
|
||||||
"_instanceName": "Rick Astley Video",
|
|
||||||
"_children": []
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"_instanceName": "Home"
|
|
||||||
},
|
|
||||||
"route": "/",
|
|
||||||
"name": "d834fea2-1b3e-4320-ab34-f9009f5ecc59"
|
|
||||||
}
|
|
|
@ -1,68 +0,0 @@
|
||||||
{
|
|
||||||
"componentLibraries": [
|
|
||||||
"@budibase/standard-components"
|
|
||||||
],
|
|
||||||
"title": "{{ name }}",
|
|
||||||
"favicon": "./_shared/favicon.png",
|
|
||||||
"stylesheets": [],
|
|
||||||
"props": {
|
|
||||||
"_id": "public-master-root",
|
|
||||||
"_component": "@budibase/standard-components/container",
|
|
||||||
"_children": [
|
|
||||||
{
|
|
||||||
"_id": "686c252d-dbf2-4e28-9078-414ba4719759",
|
|
||||||
"_component": "@budibase/standard-components/login",
|
|
||||||
"_styles": {
|
|
||||||
"normal": {
|
|
||||||
"padding": "64px",
|
|
||||||
"background": "rgba(255, 255, 255, 0.4)",
|
|
||||||
"border-radius": "0.5rem",
|
|
||||||
"margin-top": "0px",
|
|
||||||
"margin": "0px",
|
|
||||||
"line-height": "1",
|
|
||||||
"box-shadow": "0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)",
|
|
||||||
"font-size": "16px",
|
|
||||||
"font-family": "Inter",
|
|
||||||
"flex": "0 1 auto",
|
|
||||||
"transform": "0"
|
|
||||||
},
|
|
||||||
"hover": {},
|
|
||||||
"active": {},
|
|
||||||
"selected": {}
|
|
||||||
},
|
|
||||||
"_code": "",
|
|
||||||
"loginRedirect": "",
|
|
||||||
"usernameLabel": "Username",
|
|
||||||
"passwordLabel": "Password",
|
|
||||||
"loginButtonLabel": "Login",
|
|
||||||
"buttonClass": "",
|
|
||||||
"_instanceName": "Login",
|
|
||||||
"inputClass": "",
|
|
||||||
"_children": [],
|
|
||||||
"title": "Log in to {{ name }}",
|
|
||||||
"buttonText": "Log In",
|
|
||||||
"logo": "https://d33wubrfki0l68.cloudfront.net/aac32159d7207b5085e74a7ef67afbb7027786c5/2b1fd/img/logo/bb-emblem.svg"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"type": "div",
|
|
||||||
"_styles": {
|
|
||||||
"active": {},
|
|
||||||
"hover": {},
|
|
||||||
"normal": {
|
|
||||||
"display": "flex",
|
|
||||||
"flex-direction": "column",
|
|
||||||
"align-items": "center",
|
|
||||||
"justify-content": "center",
|
|
||||||
"margin-right": "auto",
|
|
||||||
"margin-left": "auto",
|
|
||||||
"min-height": "100%",
|
|
||||||
"background-image": "linear-gradient(135deg, rgba(252,215,212,1) 20%, rgba(207,218,255,1) 100%);"
|
|
||||||
},
|
|
||||||
"selected": {}
|
|
||||||
},
|
|
||||||
"_code": "",
|
|
||||||
"className": "",
|
|
||||||
"onLoad": []
|
|
||||||
},
|
|
||||||
"uiFunctions": ""
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
module.exports = () => ({})
|
|
|
@ -1,35 +1,24 @@
|
||||||
const { appPackageFolder } = require("../createAppPackage")
|
const { constants, copyFile, writeFile, readFile } = require("fs-extra")
|
||||||
const {
|
|
||||||
constants,
|
|
||||||
copyFile,
|
|
||||||
writeFile,
|
|
||||||
readFile,
|
|
||||||
writeJSON,
|
|
||||||
} = require("fs-extra")
|
|
||||||
const { join, resolve } = require("../centralPath")
|
const { join, resolve } = require("../centralPath")
|
||||||
const sqrl = require("squirrelly")
|
const sqrl = require("squirrelly")
|
||||||
const { convertCssToFiles } = require("./convertCssToFiles")
|
const { convertCssToFiles } = require("./convertCssToFiles")
|
||||||
const publicPath = require("./publicPath")
|
const publicPath = require("./publicPath")
|
||||||
const deleteCodeMeta = require("./deleteCodeMeta")
|
const { budibaseAppsDir } = require("../budibaseDir")
|
||||||
|
|
||||||
module.exports = async (config, appId, pageName, pkg) => {
|
module.exports = async (appId, pageName, pkg) => {
|
||||||
const appPath = appPackageFolder(config, appId)
|
const appPath = join(budibaseAppsDir(), appId)
|
||||||
|
|
||||||
pkg.screens = pkg.screens || []
|
pkg.screens = pkg.screens || []
|
||||||
|
|
||||||
await convertCssToFiles(publicPath(appPath, pageName), pkg)
|
await convertCssToFiles(publicPath(appPath, pageName), pkg)
|
||||||
|
|
||||||
await buildIndexHtml(config, appId, pageName, appPath, pkg)
|
await buildIndexHtml(appId, pageName, appPath, pkg)
|
||||||
|
|
||||||
await buildFrontendAppDefinition(config, appId, pageName, pkg, appPath)
|
await buildFrontendAppDefinition(appId, pageName, pkg, appPath)
|
||||||
|
|
||||||
await copyClientLib(appPath, pageName)
|
await copyClientLib(appPath, pageName)
|
||||||
|
|
||||||
await savePageJson(appPath, pageName, pkg)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const rootPath = (config, appId) => (config.useAppRootPath ? `/${appId}` : "")
|
|
||||||
|
|
||||||
const copyClientLib = async (appPath, pageName) => {
|
const copyClientLib = async (appPath, pageName) => {
|
||||||
const sourcepath = require.resolve("@budibase/client")
|
const sourcepath = require.resolve("@budibase/client")
|
||||||
const destPath = join(publicPath(appPath, pageName), "budibase-client.js")
|
const destPath = join(publicPath(appPath, pageName), "budibase-client.js")
|
||||||
|
@ -43,11 +32,10 @@ const copyClientLib = async (appPath, pageName) => {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const buildIndexHtml = async (config, appId, pageName, appPath, pkg) => {
|
const buildIndexHtml = async (appId, pageName, appPath, pkg) => {
|
||||||
const appPublicPath = publicPath(appPath, pageName)
|
const appPublicPath = publicPath(appPath, pageName)
|
||||||
|
|
||||||
const stylesheetUrl = s =>
|
const stylesheetUrl = s => (s.startsWith("http") ? s : `/${appId}/${s}`)
|
||||||
s.startsWith("http") ? s : `/${rootPath(config, appId)}/${s}`
|
|
||||||
|
|
||||||
const templateObj = {
|
const templateObj = {
|
||||||
title: pkg.page.title || "Budibase App",
|
title: pkg.page.title || "Budibase App",
|
||||||
|
@ -77,15 +65,13 @@ const buildIndexHtml = async (config, appId, pageName, appPath, pkg) => {
|
||||||
await writeFile(deployableHtmlPath, deployableHtml, { flag: "w+" })
|
await writeFile(deployableHtmlPath, deployableHtml, { flag: "w+" })
|
||||||
}
|
}
|
||||||
|
|
||||||
const buildFrontendAppDefinition = async (config, appId, pageName, pkg) => {
|
const buildFrontendAppDefinition = async (appId, pageName, pkg) => {
|
||||||
const appPath = appPackageFolder(config, appId)
|
const appPath = join(budibaseAppsDir(), appId)
|
||||||
const appPublicPath = publicPath(appPath, pageName)
|
const appPublicPath = publicPath(appPath, pageName)
|
||||||
|
|
||||||
const filename = join(appPublicPath, "clientFrontendDefinition.js")
|
const filename = join(appPublicPath, "clientFrontendDefinition.js")
|
||||||
|
|
||||||
if (pkg.page._css) {
|
delete pkg.page._css
|
||||||
delete pkg.page._css
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let screen of pkg.screens) {
|
for (let screen of pkg.screens) {
|
||||||
if (screen._css) {
|
if (screen._css) {
|
||||||
|
@ -106,25 +92,3 @@ const buildFrontendAppDefinition = async (config, appId, pageName, pkg) => {
|
||||||
`
|
`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const savePageJson = async (appPath, pageName, pkg) => {
|
|
||||||
const pageFile = join(appPath, "pages", pageName, "page.json")
|
|
||||||
|
|
||||||
if (pkg.page._css) {
|
|
||||||
delete pkg.page._css
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pkg.page.name) {
|
|
||||||
delete pkg.page.name
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pkg.page._screens) {
|
|
||||||
delete pkg.page._screens
|
|
||||||
}
|
|
||||||
|
|
||||||
deleteCodeMeta(pkg.page.props)
|
|
||||||
|
|
||||||
await writeJSON(pageFile, pkg.page, {
|
|
||||||
spaces: 2,
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
module.exports = props => {
|
|
||||||
if (props._codeMeta) {
|
|
||||||
delete props._codeMeta
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let child of props._children || []) {
|
|
||||||
module.exports(child)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,20 +0,0 @@
|
||||||
const { readJSON, readdir } = require("fs-extra")
|
|
||||||
const { join } = require("../centralPath")
|
|
||||||
|
|
||||||
module.exports = async appPath => {
|
|
||||||
const pages = {}
|
|
||||||
|
|
||||||
const pageFolders = await readdir(join(appPath, "pages"))
|
|
||||||
for (let pageFolder of pageFolders) {
|
|
||||||
try {
|
|
||||||
pages[pageFolder] = await readJSON(
|
|
||||||
join(appPath, "pages", pageFolder, "page.json")
|
|
||||||
)
|
|
||||||
pages[pageFolder].name = pageFolder
|
|
||||||
} catch (_) {
|
|
||||||
// ignore error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return pages
|
|
||||||
}
|
|
|
@ -1,98 +0,0 @@
|
||||||
const { appPackageFolder } = require("../createAppPackage")
|
|
||||||
const {
|
|
||||||
readJSON,
|
|
||||||
writeJSON,
|
|
||||||
readdir,
|
|
||||||
ensureDir,
|
|
||||||
rename,
|
|
||||||
unlink,
|
|
||||||
rmdir,
|
|
||||||
} = require("fs-extra")
|
|
||||||
const { join, resolve } = require("../centralPath")
|
|
||||||
const { dirname } = require("path")
|
|
||||||
|
|
||||||
const buildPage = require("./buildPage")
|
|
||||||
const getPages = require("./getPages")
|
|
||||||
const listScreens = require("./listScreens")
|
|
||||||
const deleteCodeMeta = require("./deleteCodeMeta")
|
|
||||||
|
|
||||||
module.exports.buildPage = buildPage
|
|
||||||
module.exports.listScreens = listScreens
|
|
||||||
|
|
||||||
const getAppDefinition = async appPath =>
|
|
||||||
await readJSON(`${appPath}/appDefinition.json`)
|
|
||||||
|
|
||||||
module.exports.getPackageForBuilder = async (config, application) => {
|
|
||||||
const appPath = resolve(config.latestPackagesFolder, application._id)
|
|
||||||
|
|
||||||
const pages = await getPages(appPath)
|
|
||||||
|
|
||||||
return {
|
|
||||||
pages,
|
|
||||||
application,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const screenPath = (appPath, pageName, name) =>
|
|
||||||
join(appPath, "pages", pageName, "screens", name + ".json")
|
|
||||||
|
|
||||||
module.exports.saveScreen = async (config, appname, pagename, screen) => {
|
|
||||||
const appPath = appPackageFolder(config, appname)
|
|
||||||
const compPath = screenPath(appPath, pagename, screen.props._id)
|
|
||||||
|
|
||||||
await ensureDir(dirname(compPath))
|
|
||||||
if (screen._css) {
|
|
||||||
delete screen._css
|
|
||||||
}
|
|
||||||
|
|
||||||
deleteCodeMeta(screen.props)
|
|
||||||
|
|
||||||
await writeJSON(compPath, screen, {
|
|
||||||
encoding: "utf8",
|
|
||||||
flag: "w",
|
|
||||||
spaces: 2,
|
|
||||||
})
|
|
||||||
return screen
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports.renameScreen = async (
|
|
||||||
config,
|
|
||||||
appname,
|
|
||||||
pagename,
|
|
||||||
oldName,
|
|
||||||
newName
|
|
||||||
) => {
|
|
||||||
const appPath = appPackageFolder(config, appname)
|
|
||||||
|
|
||||||
const oldComponentPath = screenPath(appPath, pagename, oldName)
|
|
||||||
|
|
||||||
const newComponentPath = screenPath(appPath, pagename, newName)
|
|
||||||
|
|
||||||
await ensureDir(dirname(newComponentPath))
|
|
||||||
await rename(oldComponentPath, newComponentPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports.deleteScreen = async (config, appId, pagename, name) => {
|
|
||||||
const appPath = appPackageFolder(config, appId)
|
|
||||||
const componentFile = screenPath(appPath, pagename, name)
|
|
||||||
await unlink(componentFile)
|
|
||||||
|
|
||||||
const dir = dirname(componentFile)
|
|
||||||
if ((await readdir(dir)).length === 0) {
|
|
||||||
await rmdir(dir)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports.savePage = async (config, appname, pagename, page) => {
|
|
||||||
const appPath = appPackageFolder(config, appname)
|
|
||||||
const pageDir = join(appPath, "pages", pagename)
|
|
||||||
|
|
||||||
await ensureDir(pageDir)
|
|
||||||
await writeJSON(join(pageDir, "page.json"), page, {
|
|
||||||
encoding: "utf8",
|
|
||||||
flag: "w",
|
|
||||||
space: 2,
|
|
||||||
})
|
|
||||||
const appDefinition = await getAppDefinition(appPath)
|
|
||||||
await buildPage(config, appname, appDefinition, pagename, page)
|
|
||||||
}
|
|
|
@ -1,48 +0,0 @@
|
||||||
const { appPackageFolder } = require("../createAppPackage")
|
|
||||||
const { readJSON, readdir, stat } = require("fs-extra")
|
|
||||||
const { join } = require("../centralPath")
|
|
||||||
const { keyBy } = require("lodash/fp")
|
|
||||||
|
|
||||||
module.exports = async (config, appname, pagename) => {
|
|
||||||
const appPath = appPackageFolder(config, appname)
|
|
||||||
return keyBy("name")(await fetchscreens(appPath, pagename))
|
|
||||||
}
|
|
||||||
|
|
||||||
const fetchscreens = async (appPath, pagename, relativePath = "") => {
|
|
||||||
const currentDir = join(appPath, "pages", pagename, "screens", relativePath)
|
|
||||||
|
|
||||||
const contents = await readdir(currentDir)
|
|
||||||
|
|
||||||
const screens = []
|
|
||||||
|
|
||||||
for (let item of contents) {
|
|
||||||
const itemRelativePath = join(relativePath, item)
|
|
||||||
const itemFullPath = join(currentDir, item)
|
|
||||||
const stats = await stat(itemFullPath)
|
|
||||||
|
|
||||||
if (stats.isFile()) {
|
|
||||||
if (!item.endsWith(".json")) continue
|
|
||||||
|
|
||||||
const component = await readJSON(itemFullPath)
|
|
||||||
|
|
||||||
component.name = itemRelativePath
|
|
||||||
.substring(0, itemRelativePath.length - 5)
|
|
||||||
.replace(/\\/g, "/")
|
|
||||||
|
|
||||||
component.props = component.props || {}
|
|
||||||
|
|
||||||
screens.push(component)
|
|
||||||
} else {
|
|
||||||
const childComponents = await fetchscreens(
|
|
||||||
appPath,
|
|
||||||
join(relativePath, item)
|
|
||||||
)
|
|
||||||
|
|
||||||
for (let c of childComponents) {
|
|
||||||
screens.push(c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return screens
|
|
||||||
}
|
|
|
@ -21,6 +21,7 @@ module.exports = async (ctx, appId, version) => {
|
||||||
|
|
||||||
// set the builder token
|
// set the builder token
|
||||||
setCookie(ctx, "builder", token)
|
setCookie(ctx, "builder", token)
|
||||||
|
setCookie(ctx, "currentapp", appId)
|
||||||
// need to clear all app tokens or else unable to use the app in the builder
|
// need to clear all app tokens or else unable to use the app in the builder
|
||||||
let allDbNames = await CouchDB.allDbs()
|
let allDbNames = await CouchDB.allDbs()
|
||||||
allDbNames.map(dbName => {
|
allDbNames.map(dbName => {
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
const { resolve } = require("./centralPath")
|
|
||||||
const { cwd } = require("process")
|
|
||||||
const stream = require("stream")
|
const stream = require("stream")
|
||||||
const fetch = require("node-fetch")
|
const fetch = require("node-fetch")
|
||||||
const tar = require("tar-fs")
|
const tar = require("tar-fs")
|
||||||
|
@ -9,9 +7,6 @@ const packageJson = require("../../package.json")
|
||||||
|
|
||||||
const streamPipeline = promisify(stream.pipeline)
|
const streamPipeline = promisify(stream.pipeline)
|
||||||
|
|
||||||
exports.appPackageFolder = (config, appname) =>
|
|
||||||
resolve(cwd(), config.latestPackagesFolder, appname)
|
|
||||||
|
|
||||||
exports.downloadExtractComponentLibraries = async appFolder => {
|
exports.downloadExtractComponentLibraries = async appFolder => {
|
||||||
const LIBRARIES = ["standard-components"]
|
const LIBRARIES = ["standard-components"]
|
||||||
|
|
||||||
|
|
|
@ -8,12 +8,35 @@ const zlib = require("zlib")
|
||||||
const { promisify } = require("util")
|
const { promisify } = require("util")
|
||||||
const streamPipeline = promisify(stream.pipeline)
|
const streamPipeline = promisify(stream.pipeline)
|
||||||
const { budibaseAppsDir } = require("./budibaseDir")
|
const { budibaseAppsDir } = require("./budibaseDir")
|
||||||
|
const env = require("../environment")
|
||||||
const CouchDB = require("../db")
|
const CouchDB = require("../db")
|
||||||
|
const { DocumentTypes } = require("../db/utils")
|
||||||
|
|
||||||
const DEFAULT_TEMPLATES_BUCKET =
|
const DEFAULT_TEMPLATES_BUCKET =
|
||||||
"prod-budi-templates.s3-eu-west-1.amazonaws.com"
|
"prod-budi-templates.s3-eu-west-1.amazonaws.com"
|
||||||
|
|
||||||
|
exports.getLocalTemplates = function() {
|
||||||
|
const templatesDir = join(os.homedir(), ".budibase", "templates", "app")
|
||||||
|
const templateObj = { app: {} }
|
||||||
|
fs.ensureDirSync(templatesDir)
|
||||||
|
const templateNames = fs.readdirSync(templatesDir)
|
||||||
|
for (let name of templateNames) {
|
||||||
|
templateObj.app[name] = {
|
||||||
|
name,
|
||||||
|
category: "local",
|
||||||
|
description: "local template",
|
||||||
|
type: "app",
|
||||||
|
key: `app/${name}`,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return templateObj
|
||||||
|
}
|
||||||
|
|
||||||
exports.downloadTemplate = async function(type, name) {
|
exports.downloadTemplate = async function(type, name) {
|
||||||
|
const dirName = join(budibaseAppsDir(), "templates", type, name)
|
||||||
|
if (env.LOCAL_TEMPLATES) {
|
||||||
|
return dirName
|
||||||
|
}
|
||||||
const templateUrl = `https://${DEFAULT_TEMPLATES_BUCKET}/templates/${type}/${name}.tar.gz`
|
const templateUrl = `https://${DEFAULT_TEMPLATES_BUCKET}/templates/${type}/${name}.tar.gz`
|
||||||
const response = await fetch(templateUrl)
|
const response = await fetch(templateUrl)
|
||||||
|
|
||||||
|
@ -30,26 +53,27 @@ exports.downloadTemplate = async function(type, name) {
|
||||||
tar.extract(join(budibaseAppsDir(), "templates", type))
|
tar.extract(join(budibaseAppsDir(), "templates", type))
|
||||||
)
|
)
|
||||||
|
|
||||||
return join(budibaseAppsDir(), "templates", type, name)
|
return dirName
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.exportTemplateFromApp = async function({ templateName, appId }) {
|
exports.exportTemplateFromApp = async function({ templateName, appId }) {
|
||||||
// Copy frontend files
|
// Copy frontend files
|
||||||
const appToExport = join(os.homedir(), ".budibase", appId, "pages")
|
const templatesDir = join(
|
||||||
const templatesDir = join(os.homedir(), ".budibase", "templates")
|
os.homedir(),
|
||||||
fs.ensureDirSync(templatesDir)
|
".budibase",
|
||||||
|
"templates",
|
||||||
const templateOutputPath = join(templatesDir, templateName)
|
"app",
|
||||||
fs.copySync(appToExport, join(templateOutputPath, "pages"))
|
templateName,
|
||||||
|
"db"
|
||||||
fs.ensureDirSync(join(templateOutputPath, "db"))
|
|
||||||
const writeStream = fs.createWriteStream(
|
|
||||||
join(templateOutputPath, "db", "dump.txt")
|
|
||||||
)
|
)
|
||||||
|
fs.ensureDirSync(templatesDir)
|
||||||
|
const writeStream = fs.createWriteStream(join(templatesDir, "dump.txt"))
|
||||||
// perform couch dump
|
// perform couch dump
|
||||||
const instanceDb = new CouchDB(appId)
|
const instanceDb = new CouchDB(appId)
|
||||||
|
await instanceDb.dump(writeStream, {
|
||||||
await instanceDb.dump(writeStream)
|
filter: doc => {
|
||||||
return templateOutputPath
|
return !doc._id.startsWith(DocumentTypes.USER)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return templatesDir
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue