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
bd65dd5992
|
@ -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 { get_capitalised_name } from "../helpers"
|
||||
import { get } from "svelte/store"
|
||||
import { allScreens } from "builderStore"
|
||||
|
||||
export default function(component, state) {
|
||||
const capitalised = get_capitalised_name(
|
||||
|
@ -25,7 +27,7 @@ export default function(component, state) {
|
|||
findMatches(state.currentPreviewItem.props)
|
||||
} else {
|
||||
// viewing master page - need to find against all screens
|
||||
for (let screen of state.screens) {
|
||||
for (let screen of get(allScreens)) {
|
||||
findMatches(screen.props)
|
||||
}
|
||||
}
|
||||
|
@ -33,7 +35,7 @@ export default function(component, state) {
|
|||
let index = 1
|
||||
let name
|
||||
while (!name) {
|
||||
const tryName = `${capitalised} ${index}`
|
||||
const tryName = `${capitalised || "Copy"} ${index}`
|
||||
if (!matchingComponents.includes(tryName)) name = tryName
|
||||
index++
|
||||
}
|
||||
|
|
|
@ -1,14 +1,36 @@
|
|||
import { getStore } from "./store"
|
||||
import { getFrontendStore } from "./store/frontend"
|
||||
import { getBackendUiStore } from "./store/backend"
|
||||
import { getAutomationStore } from "./store/automation/"
|
||||
import { getThemeStore } from "./store/theme"
|
||||
import { derived } from "svelte/store"
|
||||
import analytics from "analytics"
|
||||
|
||||
export const store = getStore()
|
||||
export const store = getFrontendStore()
|
||||
export const backendUiStore = getBackendUiStore()
|
||||
export const automationStore = getAutomationStore()
|
||||
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 () => {
|
||||
try {
|
||||
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 {
|
||||
makePropsSafe,
|
||||
getBuiltin,
|
||||
} from "components/userInterface/pagesParsing/createProps"
|
||||
import api from "./api"
|
||||
import { generate_screen_css } from "./generate_css"
|
||||
import { getBuiltin } from "components/userInterface/pagesParsing/createProps"
|
||||
import { uuid } from "./uuid"
|
||||
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) => {
|
||||
let parent
|
||||
walkProps(rootProps, (p, breakWalk) => {
|
||||
|
@ -30,41 +16,6 @@ export const getParent = (rootProps, child) => {
|
|||
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) => {
|
||||
cancelToken = cancelToken || { cancelled: false }
|
||||
action(props, () => {
|
||||
|
@ -79,21 +30,14 @@ export const walkProps = (props, action, cancelToken = null) => {
|
|||
}
|
||||
}
|
||||
|
||||
export const regenerateCssForScreen = screen => {
|
||||
screen._css = generate_screen_css([screen.props])
|
||||
}
|
||||
|
||||
export const regenerateCssForCurrentScreen = state => {
|
||||
if (state.currentPreviewItem) {
|
||||
regenerateCssForScreen(state.currentPreviewItem)
|
||||
}
|
||||
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 generateNewIdsForComponent = (
|
||||
component,
|
||||
state,
|
||||
changeName = true
|
||||
) =>
|
||||
walkProps(component, prop => {
|
||||
prop._id = uuid()
|
||||
if (changeName) prop._instanceName = getNewComponentName(prop, state)
|
||||
})
|
||||
|
||||
export const getComponentDefinition = (state, name) =>
|
||||
|
|
|
@ -55,7 +55,7 @@
|
|||
// Record the table that created this screen so we can link it later
|
||||
screen.autoTableId = table._id
|
||||
try {
|
||||
await store.createScreen(screen)
|
||||
await store.actions.screens.create(screen)
|
||||
} catch (_) {
|
||||
// TODO: this is temporary
|
||||
// a cypress test is failing, because I added the
|
||||
|
@ -70,7 +70,7 @@
|
|||
const listPage = screens.find(screen =>
|
||||
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
|
||||
$goto(`./table/${table._id}`)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
import { backendUiStore, store } from "builderStore"
|
||||
import { backendUiStore, store, allScreens } from "builderStore"
|
||||
import { notifier } from "builderStore/store/notifications"
|
||||
import { DropdownMenu, Button, Input } from "@budibase/bbui"
|
||||
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
||||
|
@ -28,7 +28,7 @@
|
|||
}
|
||||
|
||||
function showModal() {
|
||||
const screens = $store.screens
|
||||
const screens = $allScreens
|
||||
templateScreens = screens.filter(screen => screen.autoTableId === table._id)
|
||||
willBeDeleted = ["All table data"].concat(
|
||||
templateScreens.map(screen => `Screen ${screen.props._instanceName}`)
|
||||
|
@ -39,7 +39,7 @@
|
|||
|
||||
async function deleteTable() {
|
||||
await backendUiStore.actions.tables.delete(table)
|
||||
store.deleteScreens(templateScreens)
|
||||
store.store.actions.screens.delete(templateScreens)
|
||||
await backendUiStore.actions.tables.fetch()
|
||||
notifier.success("Table deleted")
|
||||
hideEditor()
|
||||
|
|
|
@ -128,7 +128,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
async function signUp() {
|
||||
async function createNewApp() {
|
||||
submitting = true
|
||||
try {
|
||||
// Add API key if there is none.
|
||||
|
@ -154,7 +154,7 @@
|
|||
if (applicationPkg.ok) {
|
||||
backendUiStore.actions.reset()
|
||||
pkg.justCreated = true
|
||||
await store.setPackage(pkg)
|
||||
await store.actions.initialise(pkg)
|
||||
automationStore.actions.fetch()
|
||||
} else {
|
||||
throw new Error(pkg)
|
||||
|
@ -193,10 +193,6 @@
|
|||
$: checkValidity($createAppStore.values, $createAppStore.currentStep)
|
||||
|
||||
let onChange = () => {}
|
||||
|
||||
async function _onOkay() {
|
||||
await createNewApp()
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="container">
|
||||
|
@ -239,7 +235,7 @@
|
|||
<Button
|
||||
medium
|
||||
blue
|
||||
on:click={signUp}
|
||||
on:click={createNewApp}
|
||||
disabled={!fullFormIsValid || submitting}>
|
||||
{submitting ? 'Loading...' : 'Submit'}
|
||||
</Button>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
import { getComponentDefinition } from "builderStore/storeUtils"
|
||||
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
||||
import { last } from "lodash/fp"
|
||||
import { getParent, saveCurrentPreviewItem } from "builderStore/storeUtils"
|
||||
import { getParent } from "builderStore/storeUtils"
|
||||
import { DropdownMenu } from "@budibase/bbui"
|
||||
import { DropdownContainer, DropdownItem } from "components/common/Dropdowns"
|
||||
|
||||
|
@ -25,50 +25,50 @@
|
|||
}
|
||||
|
||||
const selectComponent = component => {
|
||||
store.selectComponent(component)
|
||||
const path = store.getPathToComponent(component)
|
||||
store.actions.components.select(component)
|
||||
const path = store.actions.components.findRoute(component)
|
||||
$goto(`./:page/:screen/${path}`)
|
||||
}
|
||||
|
||||
const moveUpComponent = () => {
|
||||
store.update(s => {
|
||||
const parent = getParent(s.currentPreviewItem.props, component)
|
||||
store.update(state => {
|
||||
const parent = getParent(state.currentPreviewItem.props, component)
|
||||
|
||||
if (parent) {
|
||||
const currentIndex = parent._children.indexOf(component)
|
||||
if (currentIndex === 0) return s
|
||||
if (currentIndex === 0) return state
|
||||
|
||||
const newChildren = parent._children.filter(c => c !== component)
|
||||
newChildren.splice(currentIndex - 1, 0, component)
|
||||
parent._children = newChildren
|
||||
}
|
||||
s.currentComponentInfo = component
|
||||
saveCurrentPreviewItem(s)
|
||||
state.currentComponentInfo = component
|
||||
store.actions.preview.saveSelected()
|
||||
|
||||
return s
|
||||
return state
|
||||
})
|
||||
}
|
||||
|
||||
const moveDownComponent = () => {
|
||||
store.update(s => {
|
||||
const parent = getParent(s.currentPreviewItem.props, component)
|
||||
store.update(state => {
|
||||
const parent = getParent(state.currentPreviewItem.props, component)
|
||||
|
||||
if (parent) {
|
||||
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)
|
||||
newChildren.splice(currentIndex + 1, 0, component)
|
||||
parent._children = newChildren
|
||||
}
|
||||
s.currentComponentInfo = component
|
||||
saveCurrentPreviewItem(s)
|
||||
state.currentComponentInfo = component
|
||||
store.actions.preview.saveSelected()
|
||||
|
||||
return s
|
||||
return state
|
||||
})
|
||||
}
|
||||
|
||||
const copyComponent = () => {
|
||||
const duplicateComponent = () => {
|
||||
storeComponentForCopy(false)
|
||||
pasteComponent("below")
|
||||
}
|
||||
|
@ -82,19 +82,19 @@
|
|||
selectComponent(parent)
|
||||
}
|
||||
|
||||
saveCurrentPreviewItem(state)
|
||||
store.actions.preview.saveSelected()
|
||||
return state
|
||||
})
|
||||
}
|
||||
|
||||
const storeComponentForCopy = (cut = false) => {
|
||||
// lives in store - also used by drag drop
|
||||
store.storeComponentForCopy(component, cut)
|
||||
store.actions.components.copy(component, cut)
|
||||
}
|
||||
|
||||
const pasteComponent = mode => {
|
||||
// lives in store - also used by drag drop
|
||||
store.pasteComponent(component, mode)
|
||||
store.actions.components.paste(component, mode)
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -117,7 +117,7 @@
|
|||
<DropdownItem
|
||||
icon="ri-repeat-one-line"
|
||||
title="Duplicate"
|
||||
on:click={copyComponent} />
|
||||
on:click={duplicateComponent} />
|
||||
<DropdownItem
|
||||
icon="ri-scissors-cut-line"
|
||||
title="Cut"
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
componentPropDefinition.properties &&
|
||||
componentPropDefinition.properties[selectedCategory.value]
|
||||
|
||||
const onStyleChanged = store.setComponentStyle
|
||||
const onStyleChanged = store.actions.components.updateStyle
|
||||
|
||||
$: isComponentOrScreen =
|
||||
$store.currentView === "component" ||
|
||||
|
@ -58,6 +58,18 @@
|
|||
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) {
|
||||
return keys.map((key, i) => [key, obj[key], obj.props._id + i])
|
||||
}
|
||||
|
@ -81,8 +93,8 @@
|
|||
{componentDefinition}
|
||||
{panelDefinition}
|
||||
displayNameField={displayName}
|
||||
onChange={store.setComponentProp}
|
||||
onScreenPropChange={store.setPageOrScreenProp}
|
||||
onChange={store.actions.components.updateProp}
|
||||
onScreenPropChange={setPageOrScreenProp}
|
||||
screenOrPageInstance={$store.currentView !== 'component' && $store.currentPreviewItem} />
|
||||
{/if}
|
||||
</div>
|
||||
|
|
|
@ -25,8 +25,8 @@
|
|||
}
|
||||
|
||||
const onComponentChosen = component => {
|
||||
store.addChildComponent(component._component, component.presetProps)
|
||||
const path = store.getPathToComponent($store.currentComponentInfo)
|
||||
store.actions.components.create(component._component, component.presetProps)
|
||||
const path = store.actions.components.findRoute($store.currentComponentInfo)
|
||||
$goto(`./:page/:screen/${path}`)
|
||||
close()
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@
|
|||
])
|
||||
|
||||
const changeScreen = screen => {
|
||||
store.setCurrentScreen(screen.props._instanceName)
|
||||
store.actions.screens.select(screen.props._instanceName)
|
||||
$goto(`./:page/${screen.props._instanceName}`)
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -40,10 +40,10 @@
|
|||
|
||||
const selectComponent = component => {
|
||||
// Set current component
|
||||
store.selectComponent(component)
|
||||
store.actions.components.select(component)
|
||||
|
||||
// Get ID path
|
||||
const path = store.getPathToComponent(component)
|
||||
const path = store.actions.components.findRoute(component)
|
||||
|
||||
// Go to correct URL
|
||||
$goto(`./:page/:screen/${path}`)
|
||||
|
@ -96,8 +96,8 @@
|
|||
|
||||
const drop = () => {
|
||||
if ($dragDropStore.targetComponent !== $dragDropStore.componentToDrop) {
|
||||
store.storeComponentForCopy($dragDropStore.componentToDrop, true)
|
||||
store.pasteComponent(
|
||||
store.actions.components.copy($dragDropStore.componentToDrop, true)
|
||||
store.actions.components.paste(
|
||||
$dragDropStore.targetComponent,
|
||||
$dragDropStore.dropPosition
|
||||
)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
import { store } from "builderStore/"
|
||||
import { store, allScreens } from "builderStore"
|
||||
import ComponentPropertiesPanel from "./ComponentPropertiesPanel.svelte"
|
||||
import ComponentSelectionList from "./ComponentSelectionList.svelte"
|
||||
|
||||
|
@ -18,7 +18,7 @@
|
|||
</script>
|
||||
|
||||
<div class="root">
|
||||
{#if $store.currentFrontEndType === 'page' || $store.screens.length}
|
||||
{#if $store.currentFrontEndType === 'page' || $allScreens.length}
|
||||
<div class="switcher">
|
||||
<button
|
||||
class:selected={selected === COMPONENT_SELECTION_TAB}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<script>
|
||||
import { DataList } from "@budibase/bbui"
|
||||
import { createEventDispatcher } from "svelte"
|
||||
import { store } from "builderStore"
|
||||
import { allScreens } from "builderStore"
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
|
@ -13,7 +13,7 @@
|
|||
|
||||
const getUrls = () => {
|
||||
return [
|
||||
...$store.screens
|
||||
...$allScreens
|
||||
.filter(
|
||||
screen =>
|
||||
screen.props._component.endsWith("/rowdetail") ||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
import { Input, DataList, Select } from "@budibase/bbui"
|
||||
import { store, automationStore } from "builderStore"
|
||||
import { automationStore, allScreens } from "builderStore"
|
||||
|
||||
export let parameter
|
||||
|
||||
|
@ -24,7 +24,7 @@
|
|||
{:else if parameter.name === 'url'}
|
||||
<DataList on:change bind:value={parameter.value}>
|
||||
<option value="" />
|
||||
{#each $store.screens as screen}
|
||||
{#each $allScreens as screen}
|
||||
<option value={screen.route}>{screen.props._instanceName}</option>
|
||||
{/each}
|
||||
</DataList>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
import { DataList, Label } from "@budibase/bbui"
|
||||
import { store } from "builderStore"
|
||||
import { allScreens } from "builderStore"
|
||||
|
||||
export let parameters
|
||||
</script>
|
||||
|
@ -9,7 +9,7 @@
|
|||
<Label size="m" color="dark">Screen</Label>
|
||||
<DataList secondary bind:value={parameters.url}>
|
||||
<option value="" />
|
||||
{#each $store.screens as screen}
|
||||
{#each $allScreens as screen}
|
||||
<option value={screen.route}>{screen.props._instanceName}</option>
|
||||
{/each}
|
||||
</DataList>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
import { store } from "builderStore"
|
||||
import { store, currentScreens } from "builderStore"
|
||||
import ComponentsHierarchy from "components/userInterface/ComponentsHierarchy.svelte"
|
||||
import PageLayout from "components/userInterface/PageLayout.svelte"
|
||||
import PagesList from "components/userInterface/PagesList.svelte"
|
||||
|
@ -16,7 +16,7 @@
|
|||
<PagesList />
|
||||
<div class="nav-items-container">
|
||||
<PageLayout layout={$store.pages[$store.currentPageName]} />
|
||||
<ComponentsHierarchy screens={$store.screens} />
|
||||
<ComponentsHierarchy screens={$currentScreens} />
|
||||
</div>
|
||||
<Modal bind:this={modal}>
|
||||
<NewScreenModal />
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
import { goto } from "@sveltech/routify"
|
||||
import { store, backendUiStore } from "builderStore"
|
||||
import { store, backendUiStore, allScreens } from "builderStore"
|
||||
import {
|
||||
Input,
|
||||
Button,
|
||||
|
@ -24,7 +24,7 @@
|
|||
|
||||
$: templates = getTemplates($store, $backendUiStore.tables)
|
||||
|
||||
$: route = !route && $store.screens.length === 0 ? "*" : route
|
||||
$: route = !route && $allScreens.length === 0 ? "*" : route
|
||||
|
||||
$: baseComponents = Object.values($store.components)
|
||||
.filter(componentDefinition => componentDefinition.baseComponent)
|
||||
|
@ -71,9 +71,9 @@
|
|||
draftScreen.props._component = baseComponent
|
||||
draftScreen.route = route
|
||||
|
||||
await store.createScreen(draftScreen)
|
||||
await store.actions.screens.create(draftScreen)
|
||||
if (createLink) {
|
||||
await store.createLink(route, name)
|
||||
await store.actions.components.links.save(route, name)
|
||||
}
|
||||
|
||||
if (templateIndex !== undefined) {
|
||||
|
@ -87,7 +87,7 @@
|
|||
}
|
||||
|
||||
const routeNameExists = route => {
|
||||
return $store.screens.some(
|
||||
return $allScreens.some(
|
||||
screen => screen.route.toLowerCase() === route.toLowerCase()
|
||||
)
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
}
|
||||
|
||||
const setCurrentScreenToLayout = () => {
|
||||
store.setScreenType("page")
|
||||
store.actions.selectPageOrScreen("page")
|
||||
$goto("./:page/page-layout")
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
import { params, goto } from "@sveltech/routify"
|
||||
import { store } from "builderStore"
|
||||
|
||||
const getPage = (s, name) => {
|
||||
const props = s.pages[name]
|
||||
const getPage = (state, name) => {
|
||||
const props = state.pages[name]
|
||||
return { name, props }
|
||||
}
|
||||
|
||||
|
@ -19,10 +19,10 @@
|
|||
]
|
||||
|
||||
if (!$store.currentPageName)
|
||||
store.setCurrentPage($params.page ? $params.page : "main")
|
||||
store.actions.pages.select($params.page ? $params.page : "main")
|
||||
|
||||
const changePage = id => {
|
||||
store.setCurrentPage(id)
|
||||
store.actions.pages.select(id)
|
||||
$goto(`./${id}/page-layout`)
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<script>
|
||||
import { goto } from "@sveltech/routify"
|
||||
import { store } from "builderStore"
|
||||
import { notifier } from "builderStore/store/notifications"
|
||||
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
||||
import { DropdownMenu } from "@budibase/bbui"
|
||||
import { DropdownContainer, DropdownItem } from "components/common/Dropdowns"
|
||||
|
@ -12,11 +13,12 @@
|
|||
let anchor
|
||||
|
||||
const deleteScreen = () => {
|
||||
store.deleteScreens(screen, $store.currentPageName)
|
||||
store.actions.screens.delete(screen, $store.currentPageName)
|
||||
// update the page if required
|
||||
store.update(state => {
|
||||
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`)
|
||||
}
|
||||
return state
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<script>
|
||||
import { DataList } from "@budibase/bbui"
|
||||
import { createEventDispatcher } from "svelte"
|
||||
import { store, backendUiStore } from "builderStore"
|
||||
import { store, allScreens, backendUiStore } from "builderStore"
|
||||
import fetchBindableProperties from "builderStore/fetchBindableProperties"
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
@ -17,7 +17,7 @@
|
|||
// and substitute the :id param for the actual {{ ._id }} binding
|
||||
const getUrls = () => {
|
||||
const urls = [
|
||||
...$store.screens
|
||||
...$allScreens
|
||||
.filter(screen => !screen.props._component.endsWith("/rowdetail"))
|
||||
.map(screen => ({
|
||||
name: screen.props._instanceName,
|
||||
|
@ -33,7 +33,7 @@
|
|||
tables: $backendUiStore.tables,
|
||||
})
|
||||
|
||||
const detailScreens = $store.screens.filter(screen =>
|
||||
const detailScreens = $allScreens.filter(screen =>
|
||||
screen.props._component.endsWith("/rowdetail")
|
||||
)
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
import Input from "./PropertyPanelControls/Input.svelte"
|
||||
import { goto } from "@sveltech/routify"
|
||||
import { excludeProps } from "./propertyCategories.js"
|
||||
import { store } from "builderStore"
|
||||
import { store, allScreens } from "builderStore"
|
||||
import { walkProps } from "builderStore/storeUtils"
|
||||
|
||||
export let panelDefinition = []
|
||||
|
@ -67,7 +67,7 @@
|
|||
lookForDuplicate($store.currentPreviewItem.props)
|
||||
} else {
|
||||
// viewing master page - need to dedupe against all screens
|
||||
for (let screen of $store.screens) {
|
||||
for (let screen of $allScreens) {
|
||||
lookForDuplicate(screen.props)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -59,6 +59,11 @@ export const createProps = (componentDefinition, derivedFromProps) => {
|
|||
}
|
||||
|
||||
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
|
||||
for (let propName in safeProps) {
|
||||
props[propName] = safeProps[propName]
|
||||
|
|
|
@ -29,8 +29,8 @@ export const searchAllComponents = (components, phrase) => {
|
|||
}
|
||||
|
||||
export const getExactComponent = (components, name, isScreen = false) => {
|
||||
return components.find(c =>
|
||||
isScreen ? c.props._instanceName === name : c._instanceName === name
|
||||
return components.find(comp =>
|
||||
isScreen ? comp.props._instanceName === name : comp._instanceName === name
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,19 +1,15 @@
|
|||
export const DEFAULT_PAGES_OBJECT = {
|
||||
main: {
|
||||
_props: {},
|
||||
_screens: {},
|
||||
index: {
|
||||
_component: "./components/indexHtml",
|
||||
props: {
|
||||
_component: "@budibase/standard-components/container",
|
||||
},
|
||||
appBody: "bbapp.main.json",
|
||||
_screens: {},
|
||||
},
|
||||
unauthenticated: {
|
||||
_props: {},
|
||||
_screens: {},
|
||||
index: {
|
||||
_component: "./components/indexHtml",
|
||||
props: {
|
||||
_component: "@budibase/standard-components/container",
|
||||
},
|
||||
appBody: "bbapp.unauthenticated.json",
|
||||
_screens: {},
|
||||
},
|
||||
componentLibraries: [],
|
||||
stylesheets: [],
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
|
||||
if (res.ok) {
|
||||
backendUiStore.actions.reset()
|
||||
await store.setPackage(pkg)
|
||||
await store.actions.initialise(pkg)
|
||||
await automationStore.actions.fetch()
|
||||
return pkg
|
||||
} else {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<script>
|
||||
import { params } from "@sveltech/routify"
|
||||
store.setCurrentPage($params.page)
|
||||
store.actions.pages.select($params.page)
|
||||
</script>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<script>
|
||||
import { onMount } from "svelte"
|
||||
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.
|
||||
const componentIds = $leftover.split("/").filter(id => id !== "")
|
||||
|
@ -10,17 +10,17 @@
|
|||
if ($params.screen !== "page-layout") {
|
||||
const currentScreenName = decodeURI($params.screen)
|
||||
const validScreen =
|
||||
$store.screens.findIndex(
|
||||
$allScreens.findIndex(
|
||||
screen => screen.props._instanceName === currentScreenName
|
||||
) !== -1
|
||||
|
||||
if (!validScreen) {
|
||||
// Go to main layout if URL set to invalid screen
|
||||
store.setCurrentPage("main")
|
||||
store.actions.pages.select("main")
|
||||
$goto("../../main")
|
||||
} else {
|
||||
// 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.
|
||||
if ($leftover) {
|
||||
|
@ -35,7 +35,7 @@
|
|||
}
|
||||
} else {
|
||||
// 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.
|
||||
if ($leftover) {
|
||||
|
@ -64,7 +64,7 @@
|
|||
})
|
||||
|
||||
// Select Component!
|
||||
if (componentToSelect) store.selectComponent(componentToSelect)
|
||||
if (componentToSelect) store.actions.components.select(componentToSelect)
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import { params } from "@sveltech/routify"
|
||||
import { store } from "builderStore"
|
||||
|
||||
store.setCurrentPage($params.page)
|
||||
store.actions.pages.select($params.page)
|
||||
</script>
|
||||
|
||||
<slot />
|
||||
|
|
|
@ -28,12 +28,12 @@
|
|||
<Link
|
||||
icon={CommunityIcon}
|
||||
title="Community"
|
||||
href="https://forum.budibase.com/" />
|
||||
href="https://github.com/Budibase/budibase/discussions" />
|
||||
|
||||
<Link
|
||||
icon={BugIcon}
|
||||
title="Raise an issue"
|
||||
href="https://github.com/Budibase/budibase" />
|
||||
href="https://github.com/Budibase/budibase/issues/new/choose" />
|
||||
</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 => {
|
||||
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({
|
||||
templateName: args.name,
|
||||
appId: args.appId,
|
||||
|
|
|
@ -1,21 +1,29 @@
|
|||
const CouchDB = require("../../db")
|
||||
const { getPackageForBuilder, buildPage } = require("../../utilities/builder")
|
||||
const compileStaticAssetsForPage = require("../../utilities/builder/compileStaticAssetsForPage")
|
||||
const env = require("../../environment")
|
||||
const { copy, existsSync, readFile, writeFile } = require("fs-extra")
|
||||
const { existsSync } = require("fs-extra")
|
||||
const { budibaseAppsDir } = require("../../utilities/budibaseDir")
|
||||
const sqrl = require("squirrelly")
|
||||
const setBuilderToken = require("../../utilities/builder/setBuilderToken")
|
||||
const fs = require("fs-extra")
|
||||
const { join, resolve } = require("../../utilities/centralPath")
|
||||
const { promisify } = require("util")
|
||||
const chmodr = require("chmodr")
|
||||
const packageJson = require("../../../package.json")
|
||||
const { createLinkView } = require("../../db/linkedRows")
|
||||
const { downloadTemplate } = require("../../utilities/templates")
|
||||
const { generateAppID, DocumentTypes, SEPARATOR } = require("../../db/utils")
|
||||
const {
|
||||
generateAppID,
|
||||
DocumentTypes,
|
||||
SEPARATOR,
|
||||
getPageParams,
|
||||
generatePageID,
|
||||
generateScreenID,
|
||||
} = require("../../db/utils")
|
||||
const {
|
||||
downloadExtractComponentLibraries,
|
||||
} = 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
|
||||
|
||||
async function createInstance(template) {
|
||||
|
@ -60,13 +68,31 @@ exports.fetch = async function(ctx) {
|
|||
exports.fetchAppPackage = async function(ctx) {
|
||||
const db = new CouchDB(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)
|
||||
}
|
||||
|
||||
exports.create = async function(ctx) {
|
||||
const instance = await createInstance(ctx.request.body.template)
|
||||
const appId = instance._id
|
||||
const version = packageJson.version
|
||||
const newApplication = {
|
||||
_id: appId,
|
||||
type: "app",
|
||||
|
@ -84,6 +110,7 @@ exports.create = async function(ctx) {
|
|||
await downloadExtractComponentLibraries(newAppFolder)
|
||||
}
|
||||
|
||||
await setBuilderToken(ctx, appId, version)
|
||||
ctx.status = 200
|
||||
ctx.body = newApplication
|
||||
ctx.message = `Application ${ctx.request.body.name} created successfully`
|
||||
|
@ -120,99 +147,38 @@ exports.delete = async function(ctx) {
|
|||
}
|
||||
|
||||
const createEmptyAppPackage = async (ctx, app) => {
|
||||
const templateFolder = resolve(
|
||||
__dirname,
|
||||
"..",
|
||||
"..",
|
||||
"utilities",
|
||||
"appDirectoryTemplate"
|
||||
)
|
||||
|
||||
const appsFolder = budibaseAppsDir()
|
||||
const newAppFolder = resolve(appsFolder, app._id)
|
||||
|
||||
const db = new CouchDB(app._id)
|
||||
|
||||
if (existsSync(newAppFolder)) {
|
||||
ctx.throw(400, "App folder already exists for this application")
|
||||
}
|
||||
|
||||
await fs.ensureDir(join(newAppFolder, "pages", "main", "screens"), 0o777)
|
||||
await fs.ensureDir(
|
||||
join(newAppFolder, "pages", "unauthenticated", "screens"),
|
||||
0o777
|
||||
)
|
||||
fs.mkdirpSync(newAppFolder)
|
||||
|
||||
await copy(templateFolder, newAppFolder)
|
||||
const mainPage = cloneDeep(MAIN)
|
||||
mainPage._id = generatePageID()
|
||||
mainPage.title = app.name
|
||||
|
||||
// this line allows full permission on copied files
|
||||
// we have an unknown problem without this, whereby the
|
||||
// files get weird permissions and cant be written to :(
|
||||
const chmodrPromise = promisify(chmodr)
|
||||
await chmodrPromise(newAppFolder, 0o777)
|
||||
const unauthPage = cloneDeep(UNAUTHENTICATED)
|
||||
unauthPage._id = generatePageID()
|
||||
unauthPage.title = app.name
|
||||
unauthPage.props._children[0].title = `Log in to ${app.name}`
|
||||
|
||||
await updateJsonFile(join(appsFolder, app._id, "package.json"), {
|
||||
name: npmFriendlyAppName(app.name),
|
||||
const homeScreen = cloneDeep(HOME_SCREEN)
|
||||
homeScreen._id = generateScreenID(mainPage._id)
|
||||
await db.bulkDocs([mainPage, unauthPage, homeScreen])
|
||||
|
||||
await compileStaticAssetsForPage(app._id, "main", {
|
||||
page: mainPage,
|
||||
screens: [homeScreen],
|
||||
})
|
||||
|
||||
// if this app is being created from a template,
|
||||
// copy the frontend page definition files from
|
||||
// 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"),
|
||||
await compileStaticAssetsForPage(app._id, "unauthenticated", {
|
||||
page: unauthPage,
|
||||
screens: [],
|
||||
})
|
||||
|
||||
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"
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
const DEPLOYMENT_SUCCESS_URL =
|
||||
env.DEPLOYMENT_CREDENTIALS_URL + "deploy/success"
|
||||
|
@ -67,7 +74,8 @@ exports.updateDeploymentQuota = async function(quota) {
|
|||
|
||||
/**
|
||||
* 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 {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 @@
|
|||
/**
|
||||
* This controller is not currently fully implemented. Screens are
|
||||
* currently managed as part of the pages API, please look in api/routes/page.js
|
||||
* for routes and controllers.
|
||||
*/
|
||||
const CouchDB = require("../../db")
|
||||
const { getScreenParams, generateScreenID } = require("../../db/utils")
|
||||
|
||||
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 => {
|
||||
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 => {
|
||||
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 {
|
||||
downloadTemplate,
|
||||
exportTemplateFromApp,
|
||||
getLocalTemplates,
|
||||
} = require("../../utilities/templates")
|
||||
const env = require("../../environment")
|
||||
|
||||
// development flag, can be used to test against templates exported locally
|
||||
const DEFAULT_TEMPLATES_BUCKET =
|
||||
"prod-budi-templates.s3-eu-west-1.amazonaws.com"
|
||||
|
||||
exports.fetch = async function(ctx) {
|
||||
const { type = "app" } = ctx.query
|
||||
|
||||
const response = await fetch(
|
||||
`https://${DEFAULT_TEMPLATES_BUCKET}/manifest.json`
|
||||
)
|
||||
const json = await response.json()
|
||||
ctx.body = Object.values(json.templates[type])
|
||||
if (env.LOCAL_TEMPLATES) {
|
||||
ctx.body = Object.values(getLocalTemplates()[type])
|
||||
} else {
|
||||
const response = await fetch(
|
||||
`https://${DEFAULT_TEMPLATES_BUCKET}/manifest.json`
|
||||
)
|
||||
const json = await response.json()
|
||||
ctx.body = Object.values(json.templates[type])
|
||||
}
|
||||
}
|
||||
|
||||
exports.downloadTemplate = async function(ctx) {
|
||||
const { type, name } = ctx.params
|
||||
|
||||
await downloadTemplate(type, name)
|
||||
if (!env.LOCAL_TEMPLATES) {
|
||||
await downloadTemplate(type, name)
|
||||
}
|
||||
|
||||
ctx.body = {
|
||||
message: `template ${type}:${name} downloaded successfully.`,
|
||||
|
|
|
@ -37,15 +37,22 @@ exports.create = async function(ctx) {
|
|||
accessLevelId,
|
||||
}
|
||||
|
||||
const response = await db.post(user)
|
||||
|
||||
ctx.status = 200
|
||||
ctx.message = "User created successfully."
|
||||
ctx.userId = response._id
|
||||
ctx.body = {
|
||||
_rev: response.rev,
|
||||
username,
|
||||
name,
|
||||
try {
|
||||
const response = await db.post(user)
|
||||
ctx.status = 200
|
||||
ctx.message = "User created successfully."
|
||||
ctx.userId = response._id
|
||||
ctx.body = {
|
||||
_rev: response.rev,
|
||||
username,
|
||||
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 {
|
||||
authRoutes,
|
||||
pageRoutes,
|
||||
screenRoutes,
|
||||
userRoutes,
|
||||
deployRoutes,
|
||||
applicationRoutes,
|
||||
|
@ -97,6 +98,9 @@ router.use(templatesRoutes.allowedMethods())
|
|||
router.use(pageRoutes.routes())
|
||||
router.use(pageRoutes.allowedMethods())
|
||||
|
||||
router.use(screenRoutes.routes())
|
||||
router.use(screenRoutes.allowedMethods())
|
||||
|
||||
router.use(applicationRoutes.routes())
|
||||
router.use(applicationRoutes.allowedMethods())
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
const authRoutes = require("./auth")
|
||||
const pageRoutes = require("./pages")
|
||||
const screenRoutes = require("./screen")
|
||||
const userRoutes = require("./user")
|
||||
const applicationRoutes = require("./application")
|
||||
const tableRoutes = require("./table")
|
||||
|
@ -19,6 +20,7 @@ module.exports = {
|
|||
deployRoutes,
|
||||
authRoutes,
|
||||
pageRoutes,
|
||||
screenRoutes,
|
||||
userRoutes,
|
||||
applicationRoutes,
|
||||
rowRoutes,
|
||||
|
|
|
@ -1,117 +1,10 @@
|
|||
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 { BUILDER } = require("../../utilities/accessLevels")
|
||||
const controller = require("../controllers/page")
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
)
|
||||
router.post("/api/pages/:pageId", authorized(BUILDER), controller.save)
|
||||
|
||||
module.exports = router
|
||||
|
|
|
@ -2,12 +2,42 @@ const Router = require("@koa/router")
|
|||
const controller = require("../controllers/screen")
|
||||
const authorized = require("../../middleware/authorized")
|
||||
const { BUILDER } = require("../../utilities/accessLevels")
|
||||
const joiValidator = require("../../middleware/joi-validator")
|
||||
const Joi = require("joi")
|
||||
|
||||
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
|
||||
.get("/api/screens", authorized(BUILDER), controller.fetch)
|
||||
.post("/api/screens", authorized(BUILDER), controller.save)
|
||||
.delete("/api/:screenId/:revId", authorized(BUILDER), controller.destroy)
|
||||
.get("/api/screens/:pageId", authorized(BUILDER), controller.find)
|
||||
.post(
|
||||
"/api/screens/:pageId",
|
||||
authorized(BUILDER),
|
||||
generateSaveValidation(),
|
||||
controller.save
|
||||
)
|
||||
.delete(
|
||||
"/api/screens/:screenId/:revId",
|
||||
authorized(BUILDER),
|
||||
controller.destroy
|
||||
)
|
||||
|
||||
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)
|
||||
|
||||
// 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
|
||||
|
|
|
@ -13,6 +13,8 @@ const DocumentTypes = {
|
|||
ACCESS_LEVEL: "ac",
|
||||
WEBHOOK: "wh",
|
||||
INSTANCE: "inst",
|
||||
PAGE: "page",
|
||||
SCREEN: "screen",
|
||||
}
|
||||
|
||||
exports.DocumentTypes = DocumentTypes
|
||||
|
@ -175,6 +177,36 @@ exports.generateWebhookID = () => {
|
|||
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.
|
||||
*/
|
||||
|
|
|
@ -34,6 +34,7 @@ module.exports = {
|
|||
USERID_API_KEY: process.env.USERID_API_KEY,
|
||||
ENABLE_ANALYTICS: process.env.ENABLE_ANALYTICS,
|
||||
DEPLOYMENT_DB_URL: process.env.DEPLOYMENT_DB_URL,
|
||||
LOCAL_TEMPLATES: process.env.LOCAL_TEMPLATES,
|
||||
_set(key, value) {
|
||||
process.env[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,
|
||||
writeJSON,
|
||||
} = require("fs-extra")
|
||||
const { constants, copyFile, writeFile, readFile } = require("fs-extra")
|
||||
const { join, resolve } = require("../centralPath")
|
||||
const sqrl = require("squirrelly")
|
||||
const { convertCssToFiles } = require("./convertCssToFiles")
|
||||
const publicPath = require("./publicPath")
|
||||
const deleteCodeMeta = require("./deleteCodeMeta")
|
||||
const { budibaseAppsDir } = require("../budibaseDir")
|
||||
|
||||
module.exports = async (config, appId, pageName, pkg) => {
|
||||
const appPath = appPackageFolder(config, appId)
|
||||
module.exports = async (appId, pageName, pkg) => {
|
||||
const appPath = join(budibaseAppsDir(), appId)
|
||||
|
||||
pkg.screens = pkg.screens || []
|
||||
|
||||
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 savePageJson(appPath, pageName, pkg)
|
||||
}
|
||||
|
||||
const rootPath = (config, appId) => (config.useAppRootPath ? `/${appId}` : "")
|
||||
|
||||
const copyClientLib = async (appPath, pageName) => {
|
||||
const sourcepath = require.resolve("@budibase/client")
|
||||
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 stylesheetUrl = s =>
|
||||
s.startsWith("http") ? s : `/${rootPath(config, appId)}/${s}`
|
||||
const stylesheetUrl = s => (s.startsWith("http") ? s : `/${appId}/${s}`)
|
||||
|
||||
const templateObj = {
|
||||
title: pkg.page.title || "Budibase App",
|
||||
|
@ -77,15 +65,13 @@ const buildIndexHtml = async (config, appId, pageName, appPath, pkg) => {
|
|||
await writeFile(deployableHtmlPath, deployableHtml, { flag: "w+" })
|
||||
}
|
||||
|
||||
const buildFrontendAppDefinition = async (config, appId, pageName, pkg) => {
|
||||
const appPath = appPackageFolder(config, appId)
|
||||
const buildFrontendAppDefinition = async (appId, pageName, pkg) => {
|
||||
const appPath = join(budibaseAppsDir(), appId)
|
||||
const appPublicPath = publicPath(appPath, pageName)
|
||||
|
||||
const filename = join(appPublicPath, "clientFrontendDefinition.js")
|
||||
|
||||
if (pkg.page._css) {
|
||||
delete pkg.page._css
|
||||
}
|
||||
delete pkg.page._css
|
||||
|
||||
for (let screen of pkg.screens) {
|
||||
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
|
||||
setCookie(ctx, "builder", token)
|
||||
setCookie(ctx, "currentapp", appId)
|
||||
// need to clear all app tokens or else unable to use the app in the builder
|
||||
let allDbNames = await CouchDB.allDbs()
|
||||
allDbNames.map(dbName => {
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
const { resolve } = require("./centralPath")
|
||||
const { cwd } = require("process")
|
||||
const stream = require("stream")
|
||||
const fetch = require("node-fetch")
|
||||
const tar = require("tar-fs")
|
||||
|
@ -9,9 +7,6 @@ const packageJson = require("../../package.json")
|
|||
|
||||
const streamPipeline = promisify(stream.pipeline)
|
||||
|
||||
exports.appPackageFolder = (config, appname) =>
|
||||
resolve(cwd(), config.latestPackagesFolder, appname)
|
||||
|
||||
exports.downloadExtractComponentLibraries = async appFolder => {
|
||||
const LIBRARIES = ["standard-components"]
|
||||
|
||||
|
|
|
@ -8,12 +8,35 @@ const zlib = require("zlib")
|
|||
const { promisify } = require("util")
|
||||
const streamPipeline = promisify(stream.pipeline)
|
||||
const { budibaseAppsDir } = require("./budibaseDir")
|
||||
const env = require("../environment")
|
||||
const CouchDB = require("../db")
|
||||
const { DocumentTypes } = require("../db/utils")
|
||||
|
||||
const DEFAULT_TEMPLATES_BUCKET =
|
||||
"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) {
|
||||
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 response = await fetch(templateUrl)
|
||||
|
||||
|
@ -30,26 +53,27 @@ exports.downloadTemplate = async function(type, name) {
|
|||
tar.extract(join(budibaseAppsDir(), "templates", type))
|
||||
)
|
||||
|
||||
return join(budibaseAppsDir(), "templates", type, name)
|
||||
return dirName
|
||||
}
|
||||
|
||||
exports.exportTemplateFromApp = async function({ templateName, appId }) {
|
||||
// Copy frontend files
|
||||
const appToExport = join(os.homedir(), ".budibase", appId, "pages")
|
||||
const templatesDir = join(os.homedir(), ".budibase", "templates")
|
||||
fs.ensureDirSync(templatesDir)
|
||||
|
||||
const templateOutputPath = join(templatesDir, templateName)
|
||||
fs.copySync(appToExport, join(templateOutputPath, "pages"))
|
||||
|
||||
fs.ensureDirSync(join(templateOutputPath, "db"))
|
||||
const writeStream = fs.createWriteStream(
|
||||
join(templateOutputPath, "db", "dump.txt")
|
||||
const templatesDir = join(
|
||||
os.homedir(),
|
||||
".budibase",
|
||||
"templates",
|
||||
"app",
|
||||
templateName,
|
||||
"db"
|
||||
)
|
||||
|
||||
fs.ensureDirSync(templatesDir)
|
||||
const writeStream = fs.createWriteStream(join(templatesDir, "dump.txt"))
|
||||
// perform couch dump
|
||||
const instanceDb = new CouchDB(appId)
|
||||
|
||||
await instanceDb.dump(writeStream)
|
||||
return templateOutputPath
|
||||
await instanceDb.dump(writeStream, {
|
||||
filter: doc => {
|
||||
return !doc._id.startsWith(DocumentTypes.USER)
|
||||
},
|
||||
})
|
||||
return templatesDir
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue