Merge branch 'master' of github.com:Budibase/budibase into endpoint-renaming
This commit is contained in:
commit
cfd4d9d34d
|
@ -112,7 +112,7 @@
|
||||||
"rollup-plugin-terser": "^7.0.2",
|
"rollup-plugin-terser": "^7.0.2",
|
||||||
"rollup-plugin-url": "^2.2.2",
|
"rollup-plugin-url": "^2.2.2",
|
||||||
"start-server-and-test": "^1.11.0",
|
"start-server-and-test": "^1.11.0",
|
||||||
"svelte": "^3.24.1",
|
"svelte": "^3.29.0",
|
||||||
"svelte-jester": "^1.0.6"
|
"svelte-jester": "^1.0.6"
|
||||||
},
|
},
|
||||||
"gitHead": "115189f72a850bfb52b65ec61d932531bf327072"
|
"gitHead": "115189f72a850bfb52b65ec61d932531bf327072"
|
||||||
|
|
|
@ -8,8 +8,9 @@ export default function(component, state) {
|
||||||
|
|
||||||
const findMatches = props => {
|
const findMatches = props => {
|
||||||
walkProps(props, c => {
|
walkProps(props, c => {
|
||||||
if ((c._instanceName || "").startsWith(capitalised)) {
|
const thisInstanceName = get_capitalised_name(c._instanceName)
|
||||||
matchingComponents.push(c._instanceName)
|
if ((thisInstanceName || "").startsWith(capitalised)) {
|
||||||
|
matchingComponents.push(thisInstanceName)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -124,17 +124,18 @@ const saveScreen = store => screen => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const _saveScreen = async (store, s, screen) => {
|
const _saveScreen = async (store, s, screen) => {
|
||||||
const currentPageScreens = s.pages[s.currentPageName]._screens
|
const pageName = s.currentPageName || "main"
|
||||||
|
const currentPageScreens = s.pages[pageName]._screens
|
||||||
|
|
||||||
await api
|
await api
|
||||||
.post(`/_builder/api/${s.appId}/pages/${s.currentPageName}/screen`, screen)
|
.post(`/_builder/api/${s.appId}/pages/${pageName}/screen`, screen)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
if (currentPageScreens.includes(screen)) return
|
if (currentPageScreens.includes(screen)) return
|
||||||
|
|
||||||
const screens = [...currentPageScreens, screen]
|
const screens = [...currentPageScreens, screen]
|
||||||
|
|
||||||
store.update(innerState => {
|
store.update(innerState => {
|
||||||
innerState.pages[s.currentPageName]._screens = screens
|
innerState.pages[pageName]._screens = screens
|
||||||
innerState.screens = screens
|
innerState.screens = screens
|
||||||
innerState.currentPreviewItem = screen
|
innerState.currentPreviewItem = screen
|
||||||
innerState.allScreens = [...innerState.allScreens, screen]
|
innerState.allScreens = [...innerState.allScreens, screen]
|
||||||
|
@ -153,27 +154,17 @@ const _saveScreen = async (store, s, screen) => {
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
const createScreen = store => (screenName, route, layoutComponentName) => {
|
const createScreen = store => async screen => {
|
||||||
|
let savePromise
|
||||||
store.update(state => {
|
store.update(state => {
|
||||||
const rootComponent = state.components[layoutComponentName]
|
state.currentPreviewItem = screen
|
||||||
|
state.currentComponentInfo = screen.props
|
||||||
const newScreen = {
|
|
||||||
description: "",
|
|
||||||
url: "",
|
|
||||||
_css: "",
|
|
||||||
props: createProps(rootComponent).props,
|
|
||||||
}
|
|
||||||
newScreen.route = route
|
|
||||||
newScreen.name = newScreen.props._id
|
|
||||||
newScreen.props._instanceName = screenName || ""
|
|
||||||
state.currentPreviewItem = newScreen
|
|
||||||
state.currentComponentInfo = newScreen.props
|
|
||||||
state.currentFrontEndType = "screen"
|
state.currentFrontEndType = "screen"
|
||||||
|
savePromise = _saveScreen(store, state, screen)
|
||||||
_saveScreen(store, state, newScreen)
|
regenerateCssForCurrentScreen(state)
|
||||||
|
|
||||||
return state
|
return state
|
||||||
})
|
})
|
||||||
|
await savePromise
|
||||||
}
|
}
|
||||||
|
|
||||||
const setCurrentScreen = store => screenName => {
|
const setCurrentScreen = store => screenName => {
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
export default {
|
||||||
|
name: `Create from scratch`,
|
||||||
|
create: () => createScreen(),
|
||||||
|
}
|
||||||
|
|
||||||
|
const createScreen = () => ({
|
||||||
|
props: {
|
||||||
|
_id: "",
|
||||||
|
_component: "@budibase/standard-components/container",
|
||||||
|
_styles: {
|
||||||
|
normal: {},
|
||||||
|
hover: {},
|
||||||
|
active: {},
|
||||||
|
selected: {},
|
||||||
|
},
|
||||||
|
type: "div",
|
||||||
|
_children: [],
|
||||||
|
_instanceName: "",
|
||||||
|
},
|
||||||
|
route: "",
|
||||||
|
name: "screen-id",
|
||||||
|
})
|
|
@ -0,0 +1,22 @@
|
||||||
|
export default {
|
||||||
|
name: `New Row (Empty)`,
|
||||||
|
create: () => createScreen(),
|
||||||
|
}
|
||||||
|
|
||||||
|
const createScreen = () => ({
|
||||||
|
props: {
|
||||||
|
_id: "",
|
||||||
|
_component: "@budibase/standard-components/newrow",
|
||||||
|
_styles: {
|
||||||
|
normal: {},
|
||||||
|
hover: {},
|
||||||
|
active: {},
|
||||||
|
selected: {},
|
||||||
|
},
|
||||||
|
_children: [],
|
||||||
|
_instanceName: "",
|
||||||
|
table: "",
|
||||||
|
},
|
||||||
|
route: "",
|
||||||
|
name: "screen-id",
|
||||||
|
})
|
|
@ -0,0 +1,22 @@
|
||||||
|
export default {
|
||||||
|
name: `Row Detail (Empty)`,
|
||||||
|
create: () => createScreen(),
|
||||||
|
}
|
||||||
|
|
||||||
|
const createScreen = () => ({
|
||||||
|
props: {
|
||||||
|
_id: "",
|
||||||
|
_component: "@budibase/standard-components/rowdetail",
|
||||||
|
_styles: {
|
||||||
|
normal: {},
|
||||||
|
hover: {},
|
||||||
|
active: {},
|
||||||
|
selected: {},
|
||||||
|
},
|
||||||
|
_children: [],
|
||||||
|
_instanceName: "",
|
||||||
|
table: "",
|
||||||
|
},
|
||||||
|
route: "",
|
||||||
|
name: "screen-id",
|
||||||
|
})
|
|
@ -0,0 +1,35 @@
|
||||||
|
import newRowScreen from "./newRowScreen"
|
||||||
|
import rowDetailScreen from "./rowDetailScreen"
|
||||||
|
import rowListScreen from "./rowListScreen"
|
||||||
|
import emptyNewRowScreen from "./emptyNewRowScreen"
|
||||||
|
import createFromScratchScreen from "./createFromScratchScreen"
|
||||||
|
import emptyRowDetailScreen from "./emptyRowDetailScreen"
|
||||||
|
import { generateNewIdsForComponent } from "../../storeUtils"
|
||||||
|
import { uuid } from "builderStore/uuid"
|
||||||
|
|
||||||
|
const allTemplates = tables => [
|
||||||
|
createFromScratchScreen,
|
||||||
|
...newRowScreen(tables),
|
||||||
|
...rowDetailScreen(tables),
|
||||||
|
...rowListScreen(tables),
|
||||||
|
emptyNewRowScreen,
|
||||||
|
emptyRowDetailScreen,
|
||||||
|
]
|
||||||
|
|
||||||
|
// allows us to apply common behaviour to all create() functions
|
||||||
|
const createTemplateOverride = (frontendState, create) => () => {
|
||||||
|
const screen = create()
|
||||||
|
for (let component of screen.props._children) {
|
||||||
|
generateNewIdsForComponent(component, frontendState, false)
|
||||||
|
}
|
||||||
|
screen.props._id = uuid()
|
||||||
|
screen.name = screen.props._id
|
||||||
|
screen.route = screen.route.toLowerCase()
|
||||||
|
return screen
|
||||||
|
}
|
||||||
|
|
||||||
|
export default (frontendState, tables) =>
|
||||||
|
allTemplates(tables).map(template => ({
|
||||||
|
...template,
|
||||||
|
create: createTemplateOverride(frontendState, template.create),
|
||||||
|
}))
|
|
@ -0,0 +1,135 @@
|
||||||
|
export default function(tables) {
|
||||||
|
return tables.map(table => {
|
||||||
|
const fields = Object.keys(table.schema)
|
||||||
|
const heading = fields.length > 0 ? `{{ data.${fields[0]} }}` : "Add Row"
|
||||||
|
return {
|
||||||
|
name: `${table.name} - New`,
|
||||||
|
create: () => createScreen(table, heading),
|
||||||
|
id: NEW_ROW_TEMPLATE,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const NEW_ROW_TEMPLATE = "NEW_ROW_TEMPLATE"
|
||||||
|
|
||||||
|
const createScreen = (table, heading) => ({
|
||||||
|
props: {
|
||||||
|
_id: "",
|
||||||
|
_component: "@budibase/standard-components/newrow",
|
||||||
|
_styles: {
|
||||||
|
normal: {},
|
||||||
|
hover: {},
|
||||||
|
active: {},
|
||||||
|
selected: {},
|
||||||
|
},
|
||||||
|
table: table._id,
|
||||||
|
_children: [
|
||||||
|
{
|
||||||
|
_id: "",
|
||||||
|
_component: "@budibase/standard-components/heading",
|
||||||
|
_styles: {
|
||||||
|
normal: {},
|
||||||
|
hover: {},
|
||||||
|
active: {},
|
||||||
|
selected: {},
|
||||||
|
},
|
||||||
|
_code: "",
|
||||||
|
className: "",
|
||||||
|
text: heading,
|
||||||
|
type: "h1",
|
||||||
|
_instanceName: "Heading 1",
|
||||||
|
_children: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_id: "",
|
||||||
|
_component: "@budibase/standard-components/dataform",
|
||||||
|
_styles: {
|
||||||
|
normal: {},
|
||||||
|
hover: {},
|
||||||
|
active: {},
|
||||||
|
selected: {},
|
||||||
|
},
|
||||||
|
_code: "",
|
||||||
|
_instanceName: `${table.name} Form`,
|
||||||
|
_children: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_id: "",
|
||||||
|
_component: "@budibase/standard-components/container",
|
||||||
|
_styles: {
|
||||||
|
normal: {
|
||||||
|
display: "flex",
|
||||||
|
"flex-direction": "row",
|
||||||
|
"align-items": "center",
|
||||||
|
"justify-content": "flex-end",
|
||||||
|
},
|
||||||
|
hover: {},
|
||||||
|
active: {},
|
||||||
|
selected: {},
|
||||||
|
},
|
||||||
|
_code: "",
|
||||||
|
className: "",
|
||||||
|
onLoad: [],
|
||||||
|
type: "div",
|
||||||
|
_instanceName: "Buttons Container",
|
||||||
|
_children: [
|
||||||
|
{
|
||||||
|
_id: "",
|
||||||
|
_component: "@budibase/standard-components/button",
|
||||||
|
_styles: {
|
||||||
|
normal: {
|
||||||
|
"margin-right": "20px",
|
||||||
|
},
|
||||||
|
hover: {},
|
||||||
|
active: {},
|
||||||
|
selected: {},
|
||||||
|
},
|
||||||
|
_code: "",
|
||||||
|
text: "Back",
|
||||||
|
className: "",
|
||||||
|
disabled: false,
|
||||||
|
onClick: [
|
||||||
|
{
|
||||||
|
parameters: {
|
||||||
|
url: `/${table.name.toLowerCase()}`,
|
||||||
|
},
|
||||||
|
"##eventHandlerType": "Navigate To",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
_instanceName: "Back Button",
|
||||||
|
_children: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_id: "",
|
||||||
|
_component: "@budibase/standard-components/button",
|
||||||
|
_styles: {
|
||||||
|
normal: {},
|
||||||
|
hover: {},
|
||||||
|
active: {},
|
||||||
|
selected: {},
|
||||||
|
},
|
||||||
|
_code: "",
|
||||||
|
text: "Save",
|
||||||
|
className: "",
|
||||||
|
disabled: false,
|
||||||
|
onClick: [
|
||||||
|
{
|
||||||
|
parameters: {
|
||||||
|
contextPath: "data",
|
||||||
|
tableId: table._id,
|
||||||
|
},
|
||||||
|
"##eventHandlerType": "Save Row",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
_instanceName: "Save Button",
|
||||||
|
_children: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
_instanceName: `${table.name} - New`,
|
||||||
|
_code: "",
|
||||||
|
},
|
||||||
|
route: `/${table.name.toLowerCase()}/new`,
|
||||||
|
name: "",
|
||||||
|
})
|
|
@ -0,0 +1,135 @@
|
||||||
|
export default function(tables) {
|
||||||
|
return tables.map(table => {
|
||||||
|
const fields = Object.keys(table.schema)
|
||||||
|
const heading = fields.length > 0 ? `{{ data.${fields[0]} }}` : "Detail"
|
||||||
|
return {
|
||||||
|
name: `${table.name} - Detail`,
|
||||||
|
create: () => createScreen(table, heading),
|
||||||
|
id: ROW_DETAIL_TEMPLATE,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ROW_DETAIL_TEMPLATE = "ROW_DETAIL_TEMPLATE"
|
||||||
|
|
||||||
|
const createScreen = (table, heading) => ({
|
||||||
|
props: {
|
||||||
|
_id: "",
|
||||||
|
_component: "@budibase/standard-components/rowdetail",
|
||||||
|
_styles: {
|
||||||
|
normal: {},
|
||||||
|
hover: {},
|
||||||
|
active: {},
|
||||||
|
selected: {},
|
||||||
|
},
|
||||||
|
table: table._id,
|
||||||
|
_children: [
|
||||||
|
{
|
||||||
|
_id: "",
|
||||||
|
_component: "@budibase/standard-components/heading",
|
||||||
|
_styles: {
|
||||||
|
normal: {},
|
||||||
|
hover: {},
|
||||||
|
active: {},
|
||||||
|
selected: {},
|
||||||
|
},
|
||||||
|
_code: "",
|
||||||
|
className: "",
|
||||||
|
text: heading,
|
||||||
|
type: "h1",
|
||||||
|
_instanceName: "Heading 1",
|
||||||
|
_children: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_id: "",
|
||||||
|
_component: "@budibase/standard-components/dataform",
|
||||||
|
_styles: {
|
||||||
|
normal: {},
|
||||||
|
hover: {},
|
||||||
|
active: {},
|
||||||
|
selected: {},
|
||||||
|
},
|
||||||
|
_code: "",
|
||||||
|
_instanceName: `${table.name} Form`,
|
||||||
|
_children: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_id: "",
|
||||||
|
_component: "@budibase/standard-components/container",
|
||||||
|
_styles: {
|
||||||
|
normal: {
|
||||||
|
display: "flex",
|
||||||
|
"flex-direction": "row",
|
||||||
|
"align-items": "center",
|
||||||
|
"justify-content": "flex-end",
|
||||||
|
},
|
||||||
|
hover: {},
|
||||||
|
active: {},
|
||||||
|
selected: {},
|
||||||
|
},
|
||||||
|
_code: "",
|
||||||
|
className: "",
|
||||||
|
onLoad: [],
|
||||||
|
type: "div",
|
||||||
|
_instanceName: "Buttons Container",
|
||||||
|
_children: [
|
||||||
|
{
|
||||||
|
_id: "",
|
||||||
|
_component: "@budibase/standard-components/button",
|
||||||
|
_styles: {
|
||||||
|
normal: {
|
||||||
|
"margin-right": "20px",
|
||||||
|
},
|
||||||
|
hover: {},
|
||||||
|
active: {},
|
||||||
|
selected: {},
|
||||||
|
},
|
||||||
|
_code: "",
|
||||||
|
text: "Back",
|
||||||
|
className: "",
|
||||||
|
disabled: false,
|
||||||
|
onClick: [
|
||||||
|
{
|
||||||
|
parameters: {
|
||||||
|
url: `/${table.name.toLowerCase()}`,
|
||||||
|
},
|
||||||
|
"##eventHandlerType": "Navigate To",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
_instanceName: "Back Button",
|
||||||
|
_children: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_id: "",
|
||||||
|
_component: "@budibase/standard-components/button",
|
||||||
|
_styles: {
|
||||||
|
normal: {},
|
||||||
|
hover: {},
|
||||||
|
active: {},
|
||||||
|
selected: {},
|
||||||
|
},
|
||||||
|
_code: "",
|
||||||
|
text: "Save",
|
||||||
|
className: "",
|
||||||
|
disabled: false,
|
||||||
|
onClick: [
|
||||||
|
{
|
||||||
|
parameters: {
|
||||||
|
contextPath: "data",
|
||||||
|
tableId: table._id,
|
||||||
|
},
|
||||||
|
"##eventHandlerType": "Save Row",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
_instanceName: "Save Button",
|
||||||
|
_children: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
_instanceName: `${table.name} - Detail`,
|
||||||
|
_code: "",
|
||||||
|
},
|
||||||
|
route: `/${table.name.toLowerCase()}/:id`,
|
||||||
|
name: "",
|
||||||
|
})
|
|
@ -0,0 +1,118 @@
|
||||||
|
export default function(tables) {
|
||||||
|
return tables.map(table => {
|
||||||
|
return {
|
||||||
|
name: `${table.name} - List`,
|
||||||
|
create: () => createScreen(table),
|
||||||
|
id: ROW_LIST_TEMPLATE,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ROW_LIST_TEMPLATE = "ROW_LIST_TEMPLATE"
|
||||||
|
|
||||||
|
const createScreen = table => ({
|
||||||
|
props: {
|
||||||
|
_id: "",
|
||||||
|
_component: "@budibase/standard-components/container",
|
||||||
|
_styles: {
|
||||||
|
normal: {},
|
||||||
|
hover: {},
|
||||||
|
active: {},
|
||||||
|
selected: {},
|
||||||
|
},
|
||||||
|
type: "div",
|
||||||
|
_children: [
|
||||||
|
{
|
||||||
|
_id: "",
|
||||||
|
_component: "@budibase/standard-components/container",
|
||||||
|
_styles: {
|
||||||
|
normal: {
|
||||||
|
display: "flex",
|
||||||
|
"flex-direction": "row",
|
||||||
|
"justify-content": "space-between",
|
||||||
|
"align-items": "center",
|
||||||
|
},
|
||||||
|
hover: {},
|
||||||
|
active: {},
|
||||||
|
selected: {},
|
||||||
|
},
|
||||||
|
_code: "",
|
||||||
|
className: "",
|
||||||
|
onLoad: [],
|
||||||
|
type: "div",
|
||||||
|
_instanceName: "Header",
|
||||||
|
_children: [
|
||||||
|
{
|
||||||
|
_id: "",
|
||||||
|
_component: "@budibase/standard-components/heading",
|
||||||
|
_styles: {
|
||||||
|
normal: {},
|
||||||
|
hover: {},
|
||||||
|
active: {},
|
||||||
|
selected: {},
|
||||||
|
},
|
||||||
|
_code: "",
|
||||||
|
className: "",
|
||||||
|
text: `${table.name} List`,
|
||||||
|
type: "h1",
|
||||||
|
_instanceName: "Heading 1",
|
||||||
|
_children: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_id: "",
|
||||||
|
_component: "@budibase/standard-components/button",
|
||||||
|
_styles: {
|
||||||
|
normal: {},
|
||||||
|
hover: {},
|
||||||
|
active: {},
|
||||||
|
selected: {},
|
||||||
|
},
|
||||||
|
_code: "",
|
||||||
|
text: "Create New",
|
||||||
|
className: "",
|
||||||
|
disabled: false,
|
||||||
|
onClick: [
|
||||||
|
{
|
||||||
|
parameters: {
|
||||||
|
url: `/${table.name}/new`,
|
||||||
|
},
|
||||||
|
"##eventHandlerType": "Navigate To",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
_instanceName: "Create New Button",
|
||||||
|
_children: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_id: "",
|
||||||
|
_component: "@budibase/standard-components/datatable",
|
||||||
|
_styles: {
|
||||||
|
normal: {},
|
||||||
|
hover: {},
|
||||||
|
active: {},
|
||||||
|
selected: {},
|
||||||
|
},
|
||||||
|
_code: "",
|
||||||
|
datasource: {
|
||||||
|
label: "Deals",
|
||||||
|
name: `all_${table._id}`,
|
||||||
|
tableId: table._id,
|
||||||
|
isTable: true,
|
||||||
|
},
|
||||||
|
stripeColor: "",
|
||||||
|
borderColor: "",
|
||||||
|
backgroundColor: "",
|
||||||
|
color: "",
|
||||||
|
_instanceName: `${table.name} Table`,
|
||||||
|
_children: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
_instanceName: `${table.name} - List`,
|
||||||
|
_code: "",
|
||||||
|
className: "",
|
||||||
|
onLoad: [],
|
||||||
|
},
|
||||||
|
route: `/${table.name.toLowerCase()}`,
|
||||||
|
name: "",
|
||||||
|
})
|
|
@ -85,10 +85,10 @@ export const regenerateCssForCurrentScreen = state => {
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
|
||||||
export const generateNewIdsForComponent = (c, state) =>
|
export const generateNewIdsForComponent = (c, state, changeName = true) =>
|
||||||
walkProps(c, p => {
|
walkProps(c, p => {
|
||||||
p._id = uuid()
|
p._id = uuid()
|
||||||
p._instanceName = getNewComponentName(p._component, state)
|
if (changeName) p._instanceName = getNewComponentName(p._component, state)
|
||||||
})
|
})
|
||||||
|
|
||||||
export const getComponentDefinition = (state, name) =>
|
export const getComponentDefinition = (state, name) =>
|
||||||
|
|
|
@ -1,11 +1,21 @@
|
||||||
<script>
|
<script>
|
||||||
import { goto } from "@sveltech/routify"
|
import { goto } from "@sveltech/routify"
|
||||||
import { backendUiStore } from "builderStore"
|
import { backendUiStore, store } from "builderStore"
|
||||||
import { notifier } from "builderStore/store/notifications"
|
import { notifier } from "builderStore/store/notifications"
|
||||||
import { Button, Input, Label, ModalContent, Modal } from "@budibase/bbui"
|
import { Button, Input, Label, ModalContent, Modal } from "@budibase/bbui"
|
||||||
import Spinner from "components/common/Spinner.svelte"
|
import Spinner from "components/common/Spinner.svelte"
|
||||||
import TableDataImport from "../TableDataImport.svelte"
|
import TableDataImport from "../TableDataImport.svelte"
|
||||||
import analytics from "analytics"
|
import analytics from "analytics"
|
||||||
|
import screenTemplates from "builderStore/store/screenTemplates"
|
||||||
|
import { NEW_ROW_TEMPLATE } from "builderStore/store/screenTemplates/newRowScreen"
|
||||||
|
import { ROW_DETAIL_TEMPLATE } from "builderStore/store/screenTemplates/rowDetailScreen"
|
||||||
|
import { ROW_LIST_TEMPLATE } from "builderStore/store/screenTemplates/rowListScreen"
|
||||||
|
|
||||||
|
const defaultScreens = [
|
||||||
|
NEW_ROW_TEMPLATE,
|
||||||
|
ROW_DETAIL_TEMPLATE,
|
||||||
|
ROW_LIST_TEMPLATE,
|
||||||
|
]
|
||||||
|
|
||||||
let modal
|
let modal
|
||||||
let name
|
let name
|
||||||
|
@ -25,6 +35,24 @@
|
||||||
notifier.success(`Table ${name} created successfully.`)
|
notifier.success(`Table ${name} created successfully.`)
|
||||||
$goto(`./table/${table._id}`)
|
$goto(`./table/${table._id}`)
|
||||||
analytics.captureEvent("Table Created", { name })
|
analytics.captureEvent("Table Created", { name })
|
||||||
|
|
||||||
|
const screens = screenTemplates($store, [table])
|
||||||
|
.filter(template => defaultScreens.includes(template.id))
|
||||||
|
.map(template => template.create())
|
||||||
|
|
||||||
|
for (let screen of screens) {
|
||||||
|
console.log(JSON.stringify(screen))
|
||||||
|
try {
|
||||||
|
await store.createScreen(screen)
|
||||||
|
} catch (_) {
|
||||||
|
// TODO: this is temporary
|
||||||
|
// a cypress test is failing, because I added the
|
||||||
|
// NewRow component. So - this throws an exception
|
||||||
|
// because the currently released standard-components (on NPM)
|
||||||
|
// does not have NewRow
|
||||||
|
// we should remove this after this has been released
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -68,18 +68,8 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
const copyComponent = () => {
|
const copyComponent = () => {
|
||||||
store.update(s => {
|
storeComponentForCopy(false)
|
||||||
const parent = getParent(s.currentPreviewItem.props, component)
|
pasteComponent("below")
|
||||||
const copiedComponent = cloneDeep(component)
|
|
||||||
walkProps(copiedComponent, p => {
|
|
||||||
p._id = uuid()
|
|
||||||
})
|
|
||||||
parent._children = [...parent._children, copiedComponent]
|
|
||||||
saveCurrentPreviewItem(s)
|
|
||||||
s.currentComponentInfo = copiedComponent
|
|
||||||
regenerateCssForCurrentScreen(s)
|
|
||||||
return s
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const deleteComponent = () => {
|
const deleteComponent = () => {
|
||||||
|
|
|
@ -41,14 +41,6 @@
|
||||||
|
|
||||||
const onStyleChanged = store.setComponentStyle
|
const onStyleChanged = store.setComponentStyle
|
||||||
|
|
||||||
function onPropChanged(key, value) {
|
|
||||||
if ($store.currentView !== "component") {
|
|
||||||
store.setPageOrScreenProp(key, value)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
store.setComponentProp(key, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
$: isComponentOrScreen =
|
$: isComponentOrScreen =
|
||||||
$store.currentView === "component" ||
|
$store.currentView === "component" ||
|
||||||
$store.currentFrontEndType === "screen"
|
$store.currentFrontEndType === "screen"
|
||||||
|
@ -103,7 +95,8 @@
|
||||||
{componentDefinition}
|
{componentDefinition}
|
||||||
{panelDefinition}
|
{panelDefinition}
|
||||||
displayNameField={displayName}
|
displayNameField={displayName}
|
||||||
onChange={onPropChanged}
|
onChange={store.setComponentProp}
|
||||||
|
onScreenPropChange={store.setPageOrScreenProp}
|
||||||
screenOrPageInstance={$store.currentView !== 'component' && $store.currentPreviewItem} />
|
screenOrPageInstance={$store.currentView !== 'component' && $store.currentPreviewItem} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
|
|
@ -73,7 +73,7 @@
|
||||||
|
|
||||||
{#if fields}
|
{#if fields}
|
||||||
{#each fields as field}
|
{#each fields as field}
|
||||||
<Label size="m" color="dark">Field</Label>
|
<Label size="m" color="dark">Column</Label>
|
||||||
<Select secondary bind:value={field.name} on:blur={rebuildParameters}>
|
<Select secondary bind:value={field.name} on:blur={rebuildParameters}>
|
||||||
<option value="" />
|
<option value="" />
|
||||||
{#each schemaFields as schemaField}
|
{#each schemaFields as schemaField}
|
||||||
|
|
|
@ -0,0 +1,136 @@
|
||||||
|
<script>
|
||||||
|
import { Select, Label } from "@budibase/bbui"
|
||||||
|
import { store, backendUiStore } from "builderStore"
|
||||||
|
import fetchBindableProperties from "builderStore/fetchBindableProperties"
|
||||||
|
import SaveFields from "./SaveFields.svelte"
|
||||||
|
import {
|
||||||
|
readableToRuntimeBinding,
|
||||||
|
runtimeToReadableBinding,
|
||||||
|
} from "builderStore/replaceBindings"
|
||||||
|
|
||||||
|
// parameters.contextPath used in the client handler to determine which row to save
|
||||||
|
// this could be "data" or "data.parent", "data.parent.parent" etc
|
||||||
|
export let parameters
|
||||||
|
|
||||||
|
let idFields
|
||||||
|
let schemaFields
|
||||||
|
|
||||||
|
$: bindableProperties = fetchBindableProperties({
|
||||||
|
componentInstanceId: $store.currentComponentInfo._id,
|
||||||
|
components: $store.components,
|
||||||
|
screen: $store.currentPreviewItem,
|
||||||
|
tables: $backendUiStore.tables,
|
||||||
|
})
|
||||||
|
|
||||||
|
$: {
|
||||||
|
if (parameters && parameters.contextPath) {
|
||||||
|
schemaFields = schemaFromContextPath(parameters.contextPath)
|
||||||
|
} else {
|
||||||
|
schemaFields = []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const idBindingToContextPath = id => id.substring(0, id.length - 4)
|
||||||
|
const contextPathToId = path => `${path}._id`
|
||||||
|
|
||||||
|
$: {
|
||||||
|
idFields = bindableProperties.filter(
|
||||||
|
bindable =>
|
||||||
|
bindable.type === "context" && bindable.runtimeBinding.endsWith("._id")
|
||||||
|
)
|
||||||
|
// ensure contextPath is always defaulted - there is usually only one option
|
||||||
|
if (idFields.length > 0 && !parameters.contextPath) {
|
||||||
|
parameters.contextPath = idBindingToContextPath(
|
||||||
|
idFields[0].runtimeBinding
|
||||||
|
)
|
||||||
|
parameters = parameters
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// just wraps binding in {{ ... }}
|
||||||
|
const toBindingExpression = bindingPath => `{{ ${bindingPath} }}`
|
||||||
|
|
||||||
|
// finds the selected idBinding, then reads the table/view
|
||||||
|
// from the component instance that it belongs to.
|
||||||
|
// then returns the field names for that schema
|
||||||
|
const schemaFromContextPath = contextPath => {
|
||||||
|
if (!contextPath) return []
|
||||||
|
|
||||||
|
const idBinding = bindableProperties.find(
|
||||||
|
prop => prop.runtimeBinding === contextPathToId(contextPath)
|
||||||
|
)
|
||||||
|
if (!idBinding) return []
|
||||||
|
|
||||||
|
const { instance } = idBinding
|
||||||
|
|
||||||
|
const component = $store.components[instance._component]
|
||||||
|
|
||||||
|
// component.context is the name of the prop that holds the tableId
|
||||||
|
const tableInfo = instance[component.context]
|
||||||
|
const tableId =
|
||||||
|
typeof tableInfo === "string" ? tableInfo : tableInfo.tableId
|
||||||
|
|
||||||
|
if (!tableInfo) return []
|
||||||
|
|
||||||
|
const table = $backendUiStore.tables.find(m => m._id === tableId)
|
||||||
|
parameters.tableId = tableId
|
||||||
|
return Object.keys(table.schema).map(k => ({
|
||||||
|
name: k,
|
||||||
|
type: table.schema[k].type,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
const onFieldsChanged = e => {
|
||||||
|
parameters.fields = e.detail
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="root">
|
||||||
|
{#if idFields.length === 0}
|
||||||
|
<div class="cannot-use">
|
||||||
|
Update row can only be used within a component that provides data, such
|
||||||
|
as a List
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<Label size="m" color="dark">Datasource</Label>
|
||||||
|
<Select secondary bind:value={parameters.contextPath}>
|
||||||
|
<option value="" />
|
||||||
|
{#each idFields as idField}
|
||||||
|
<option value={idBindingToContextPath(idField.runtimeBinding)}>
|
||||||
|
{idField.instance._instanceName}
|
||||||
|
</option>
|
||||||
|
{/each}
|
||||||
|
</Select>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if parameters.contextPath}
|
||||||
|
<SaveFields
|
||||||
|
parameterFields={parameters.fields}
|
||||||
|
{schemaFields}
|
||||||
|
on:fieldschanged={onFieldsChanged} />
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.root {
|
||||||
|
display: grid;
|
||||||
|
column-gap: var(--spacing-s);
|
||||||
|
row-gap: var(--spacing-s);
|
||||||
|
grid-template-columns: auto 1fr auto 1fr auto;
|
||||||
|
align-items: baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.root :global(> div:nth-child(2)) {
|
||||||
|
grid-column-start: 2;
|
||||||
|
grid-column-end: 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cannot-use {
|
||||||
|
color: var(--red);
|
||||||
|
font-size: var(--font-size-s);
|
||||||
|
text-align: center;
|
||||||
|
width: 70%;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,6 +1,5 @@
|
||||||
import NavigateTo from "./NavigateTo.svelte"
|
import NavigateTo from "./NavigateTo.svelte"
|
||||||
import UpdateRow from "./UpdateRow.svelte"
|
import SaveRow from "./SaveRow.svelte"
|
||||||
import CreateRow from "./CreateRow.svelte"
|
|
||||||
|
|
||||||
// defines what actions are available, when adding a new one
|
// defines what actions are available, when adding a new one
|
||||||
// the component is the setup panel for the action
|
// the component is the setup panel for the action
|
||||||
|
@ -9,15 +8,11 @@ import CreateRow from "./CreateRow.svelte"
|
||||||
|
|
||||||
export default [
|
export default [
|
||||||
{
|
{
|
||||||
name: "Create Row",
|
name: "Save Row",
|
||||||
component: CreateRow,
|
component: SaveRow,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Navigate To",
|
name: "Navigate To",
|
||||||
component: NavigateTo,
|
component: NavigateTo,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "Update Row",
|
|
||||||
component: UpdateRow,
|
|
||||||
},
|
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,27 +1,49 @@
|
||||||
<script>
|
<script>
|
||||||
import { store } from "builderStore"
|
import { store, backendUiStore } from "builderStore"
|
||||||
import { Input, Select, ModalContent } from "@budibase/bbui"
|
import { Input, Button, Spacer, Select, ModalContent } from "@budibase/bbui"
|
||||||
import { find, filter, some } from "lodash/fp"
|
import getTemplates from "builderStore/store/screenTemplates"
|
||||||
|
import { some } from "lodash/fp"
|
||||||
|
|
||||||
|
const CONTAINER = "@budibase/standard-components/container"
|
||||||
|
|
||||||
let dialog
|
|
||||||
let layoutComponents
|
|
||||||
let layoutComponent
|
|
||||||
let screens
|
|
||||||
let name = ""
|
let name = ""
|
||||||
let routeError
|
let routeError
|
||||||
|
let baseComponent = CONTAINER
|
||||||
|
let templateIndex
|
||||||
|
let draftScreen
|
||||||
|
|
||||||
$: layoutComponents = Object.values($store.components).filter(
|
$: templates = getTemplates($store, $backendUiStore.tables)
|
||||||
componentDefinition => componentDefinition.container
|
|
||||||
)
|
|
||||||
|
|
||||||
$: layoutComponent = layoutComponent
|
|
||||||
? layoutComponents.find(
|
|
||||||
component => component._component === layoutComponent._component
|
|
||||||
)
|
|
||||||
: layoutComponents[0]
|
|
||||||
|
|
||||||
$: route = !route && $store.screens.length === 0 ? "*" : route
|
$: route = !route && $store.screens.length === 0 ? "*" : route
|
||||||
|
|
||||||
|
$: baseComponents = Object.values($store.components)
|
||||||
|
.filter(componentDefinition => componentDefinition.baseComponent)
|
||||||
|
.map(c => c._component)
|
||||||
|
|
||||||
|
$: {
|
||||||
|
if (templates && templateIndex === undefined) {
|
||||||
|
templateIndex = 0
|
||||||
|
templateChanged(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const templateChanged = newTemplateIndex => {
|
||||||
|
if (newTemplateIndex === undefined) return
|
||||||
|
|
||||||
|
draftScreen = templates[newTemplateIndex].create()
|
||||||
|
if (draftScreen.props._instanceName) {
|
||||||
|
name = draftScreen.props._instanceName
|
||||||
|
}
|
||||||
|
|
||||||
|
if (draftScreen.props._component) {
|
||||||
|
baseComponent = draftScreen.props._component
|
||||||
|
}
|
||||||
|
|
||||||
|
if (draftScreen.route) {
|
||||||
|
route = draftScreen.route
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const save = () => {
|
const save = () => {
|
||||||
if (!route) {
|
if (!route) {
|
||||||
routeError = "Url is required"
|
routeError = "Url is required"
|
||||||
|
@ -32,12 +54,23 @@
|
||||||
routeError = ""
|
routeError = ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (routeError) {
|
|
||||||
return false
|
if (routeError) return false
|
||||||
}
|
|
||||||
store.createScreen(name, route, layoutComponent._component)
|
draftScreen.props._instanceName = name
|
||||||
|
draftScreen.props._component = baseComponent
|
||||||
|
draftScreen.route = route
|
||||||
|
|
||||||
|
store.createScreen(draftScreen)
|
||||||
|
|
||||||
|
finished()
|
||||||
|
}
|
||||||
|
|
||||||
|
const finished = () => {
|
||||||
|
templateIndex = 0
|
||||||
name = ""
|
name = ""
|
||||||
route = ""
|
route = ""
|
||||||
|
baseComponent = CONTAINER
|
||||||
}
|
}
|
||||||
|
|
||||||
const routeNameExists = route => {
|
const routeNameExists = route => {
|
||||||
|
@ -54,15 +87,25 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ModalContent title="New Screen" confirmText="Create Screen" onConfirm={save}>
|
<ModalContent title="New Screen" confirmText="Create Screen" onConfirm={save}>
|
||||||
|
|
||||||
|
<Select
|
||||||
|
label="Choose a Template"
|
||||||
|
bind:value={templateIndex}
|
||||||
|
secondary
|
||||||
|
on:change={ev => templateChanged(ev.target.value)}>
|
||||||
|
{#if templates}
|
||||||
|
{#each templates as template, index}
|
||||||
|
<option value={index}>{template.name}</option>
|
||||||
|
{/each}
|
||||||
|
{/if}
|
||||||
|
</Select>
|
||||||
|
|
||||||
<Input label="Name" bind:value={name} />
|
<Input label="Name" bind:value={name} />
|
||||||
|
|
||||||
<Input
|
<Input
|
||||||
label="Url"
|
label="Url"
|
||||||
error={routeError}
|
error={routeError}
|
||||||
bind:value={route}
|
bind:value={route}
|
||||||
on:change={routeChanged} />
|
on:change={routeChanged} />
|
||||||
<Select label="Layout Component" bind:value={layoutComponent} secondary>
|
|
||||||
{#each layoutComponents as { _component, name }}
|
|
||||||
<option value={_component}>{name}</option>
|
|
||||||
{/each}
|
|
||||||
</Select>
|
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
export let componentDefinition = {}
|
export let componentDefinition = {}
|
||||||
export let componentInstance = {}
|
export let componentInstance = {}
|
||||||
export let onChange = () => {}
|
export let onChange = () => {}
|
||||||
|
export let onScreenPropChange = () => {}
|
||||||
export let displayNameField = false
|
export let displayNameField = false
|
||||||
export let screenOrPageInstance
|
export let screenOrPageInstance
|
||||||
|
|
||||||
|
@ -91,7 +92,7 @@
|
||||||
label={def.label}
|
label={def.label}
|
||||||
key={def.key}
|
key={def.key}
|
||||||
value={screenOrPageInstance[def.key]}
|
value={screenOrPageInstance[def.key]}
|
||||||
{onChange}
|
onChange={onScreenPropChange}
|
||||||
props={{ ...excludeProps(def, ['control', 'label']) }} />
|
props={{ ...excludeProps(def, ['control', 'label']) }} />
|
||||||
{/each}
|
{/each}
|
||||||
<hr />
|
<hr />
|
||||||
|
|
|
@ -583,23 +583,7 @@ export default {
|
||||||
icon: "ri-file-edit-line",
|
icon: "ri-file-edit-line",
|
||||||
properties: {
|
properties: {
|
||||||
design: { ...all },
|
design: { ...all },
|
||||||
settings: [
|
settings: [],
|
||||||
{
|
|
||||||
label: "Table",
|
|
||||||
key: "table",
|
|
||||||
control: TableSelect,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Title",
|
|
||||||
key: "title",
|
|
||||||
control: Input,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Button Text",
|
|
||||||
key: "buttonText",
|
|
||||||
control: Input,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -608,23 +592,7 @@ export default {
|
||||||
icon: "ri-file-edit-line",
|
icon: "ri-file-edit-line",
|
||||||
properties: {
|
properties: {
|
||||||
design: { ...all },
|
design: { ...all },
|
||||||
settings: [
|
settings: [],
|
||||||
{
|
|
||||||
label: "Table",
|
|
||||||
key: "table",
|
|
||||||
control: TableSelect,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Title",
|
|
||||||
key: "title",
|
|
||||||
control: Input,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Button Text",
|
|
||||||
key: "buttonText",
|
|
||||||
control: Input,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -1136,6 +1104,18 @@ export default {
|
||||||
},
|
},
|
||||||
children: [],
|
children: [],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "New Row",
|
||||||
|
_component: "@budibase/standard-components/newrow",
|
||||||
|
description:
|
||||||
|
"Sets up a new row for creation, which can be used with {{ context }}, in children",
|
||||||
|
icon: "ri-profile-line",
|
||||||
|
properties: {
|
||||||
|
design: { ...all },
|
||||||
|
settings: [{ label: "Table", key: "table", control: TableSelect }],
|
||||||
|
},
|
||||||
|
children: [],
|
||||||
|
},
|
||||||
// {
|
// {
|
||||||
// name: "Map",
|
// name: "Map",
|
||||||
// _component: "@budibase/standard-components/datamap",
|
// _component: "@budibase/standard-components/datamap",
|
||||||
|
|
|
@ -5850,10 +5850,10 @@ svelte-portal@^1.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/svelte-portal/-/svelte-portal-1.0.0.tgz#36a47c5578b1a4d9b4dc60fa32a904640ec4cdd3"
|
resolved "https://registry.yarnpkg.com/svelte-portal/-/svelte-portal-1.0.0.tgz#36a47c5578b1a4d9b4dc60fa32a904640ec4cdd3"
|
||||||
integrity sha512-nHf+DS/jZ6jjnZSleBMSaZua9JlG5rZv9lOGKgJuaZStfevtjIlUJrkLc3vbV8QdBvPPVmvcjTlazAzfKu0v3Q==
|
integrity sha512-nHf+DS/jZ6jjnZSleBMSaZua9JlG5rZv9lOGKgJuaZStfevtjIlUJrkLc3vbV8QdBvPPVmvcjTlazAzfKu0v3Q==
|
||||||
|
|
||||||
svelte@^3.24.1:
|
svelte@^3.29.0:
|
||||||
version "3.25.1"
|
version "3.29.0"
|
||||||
resolved "https://registry.yarnpkg.com/svelte/-/svelte-3.25.1.tgz#218def1243fea5a97af6eb60f5e232315bb57ac4"
|
resolved "https://registry.yarnpkg.com/svelte/-/svelte-3.29.0.tgz#80acac4254341ad8f3301e5ef03f4127ea967d96"
|
||||||
integrity sha512-IbrVKTmuR0BvDw4ii8/gBNy8REu7nWTRy9uhUz+Yuae5lIjWgSGwKlWtJGC2Vg95s+UnXPqDu0Kk/sUwe0t2GQ==
|
integrity sha512-f+A65eyOQ5ujETLy+igNXtlr6AEjAQLYd1yJE1VwNiXMQO5Z/Vmiy3rL+zblV/9jd7rtTTWqO1IcuXsP2Qv0OA==
|
||||||
|
|
||||||
symbol-observable@^1.1.0:
|
symbol-observable@^1.1.0:
|
||||||
version "1.2.0"
|
version "1.2.0"
|
||||||
|
|
|
@ -52,14 +52,14 @@ const apiOpts = {
|
||||||
delete: del,
|
delete: del,
|
||||||
}
|
}
|
||||||
|
|
||||||
const createRow = async params =>
|
const saveRow = async (params, state) =>
|
||||||
await post({
|
await post({
|
||||||
url: `/api/${params.tableId}/rows`,
|
url: `/api/${params.tableId}/rows`,
|
||||||
body: makeRowRequestBody(params),
|
body: makeRowRequestBody(params, state),
|
||||||
})
|
})
|
||||||
|
|
||||||
const updateRow = async params => {
|
const updateRow = async (params, state) => {
|
||||||
const row = makeRowRequestBody(params)
|
const row = makeRowRequestBody(params, state)
|
||||||
row._id = params._id
|
row._id = params._id
|
||||||
await patch({
|
await patch({
|
||||||
url: `/api/${params.tableId}/rows/${params._id}`,
|
url: `/api/${params.tableId}/rows/${params._id}`,
|
||||||
|
@ -67,8 +67,14 @@ const updateRow = async params => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const makeRowRequestBody = parameters => {
|
const makeRowRequestBody = (parameters, state) => {
|
||||||
const body = {}
|
// start with the row thats currently in context
|
||||||
|
const body = { ...(state.data || {}) }
|
||||||
|
|
||||||
|
// dont send the table
|
||||||
|
if (body._table) delete body._table
|
||||||
|
|
||||||
|
// then override with supplied parameters
|
||||||
for (let fieldName in parameters.fields) {
|
for (let fieldName in parameters.fields) {
|
||||||
const field = parameters.fields[fieldName]
|
const field = parameters.fields[fieldName]
|
||||||
|
|
||||||
|
@ -95,6 +101,6 @@ const makeRowRequestBody = parameters => {
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
authenticate: authenticate(apiOpts),
|
authenticate: authenticate(apiOpts),
|
||||||
createRow,
|
saveRow,
|
||||||
updateRow,
|
updateRow,
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,8 +6,8 @@ export const EVENT_TYPE_MEMBER_NAME = "##eventHandlerType"
|
||||||
export const eventHandlers = routeTo => {
|
export const eventHandlers = routeTo => {
|
||||||
const handlers = {
|
const handlers = {
|
||||||
"Navigate To": param => routeTo(param && param.url),
|
"Navigate To": param => routeTo(param && param.url),
|
||||||
"Create Row": api.createRow,
|
"Update Record": api.updateRow,
|
||||||
"Update Row": api.updateRow,
|
"Save Record": api.saveRow,
|
||||||
"Trigger Workflow": api.triggerWorkflow,
|
"Trigger Workflow": api.triggerWorkflow,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ export const eventHandlers = routeTo => {
|
||||||
const handler = handlers[action[EVENT_TYPE_MEMBER_NAME]]
|
const handler = handlers[action[EVENT_TYPE_MEMBER_NAME]]
|
||||||
const parameters = createParameters(action.parameters, state)
|
const parameters = createParameters(action.parameters, state)
|
||||||
if (handler) {
|
if (handler) {
|
||||||
await handler(parameters)
|
await handler(parameters, state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -95,7 +95,7 @@ const getState = contextStoreKey =>
|
||||||
contextStoreKey ? contextStores[contextStoreKey].state : rootState
|
contextStoreKey ? contextStores[contextStoreKey].state : rootState
|
||||||
|
|
||||||
const getStore = contextStoreKey =>
|
const getStore = contextStoreKey =>
|
||||||
contextStoreKey ? contextStores[contextStoreKey] : rootStore
|
contextStoreKey ? contextStores[contextStoreKey].store : rootStore
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
subscribe,
|
subscribe,
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
const LinkController = require("./LinkController")
|
const LinkController = require("./LinkController")
|
||||||
const { IncludeDocs, getLinkDocuments, createLinkView } = require("./linkUtils")
|
const { IncludeDocs, getLinkDocuments, createLinkView } = require("./linkUtils")
|
||||||
|
const _ = require("lodash")
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This functionality makes sure that when rows with links are created, updated or deleted they are processed
|
* This functionality makes sure that when rows with links are created, updated or deleted they are processed
|
||||||
|
@ -90,8 +91,7 @@ exports.attachLinkInfo = async (instanceId, rows) => {
|
||||||
}
|
}
|
||||||
let tableIds = [...new Set(rows.map(el => el.tableId))]
|
let tableIds = [...new Set(rows.map(el => el.tableId))]
|
||||||
// start by getting all the link values for performance reasons
|
// start by getting all the link values for performance reasons
|
||||||
let responses = [].concat.apply(
|
let responses = _.flatten(
|
||||||
[],
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
tableIds.map(tableId =>
|
tableIds.map(tableId =>
|
||||||
getLinkDocuments({
|
getLinkDocuments({
|
||||||
|
|
|
@ -218,20 +218,12 @@
|
||||||
"dataform": {
|
"dataform": {
|
||||||
"description": "an HTML table that fetches data from a table or view and displays it.",
|
"description": "an HTML table that fetches data from a table or view and displays it.",
|
||||||
"data": true,
|
"data": true,
|
||||||
"props": {
|
"props": {}
|
||||||
"table": "tables",
|
|
||||||
"title": "string",
|
|
||||||
"buttonText": "string"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"dataformwide": {
|
"dataformwide": {
|
||||||
"description": "an HTML table that fetches data from a table or view and displays it.",
|
"description": "an HTML table that fetches data from a table or view and displays it.",
|
||||||
"data": true,
|
"data": true,
|
||||||
"props": {
|
"props": {}
|
||||||
"table": "tables",
|
|
||||||
"title": "string",
|
|
||||||
"buttonText": "string"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"datalist": {
|
"datalist": {
|
||||||
"description": "A configurable data list that attaches to your backend tables.",
|
"description": "A configurable data list that attaches to your backend tables.",
|
||||||
|
@ -270,10 +262,26 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"rowdetail": {
|
"rowdetail": {
|
||||||
|
<<<<<<< HEAD
|
||||||
"description": "Loads a row, using an ID in the url",
|
"description": "Loads a row, using an ID in the url",
|
||||||
"context": "table",
|
"context": "table",
|
||||||
|
=======
|
||||||
|
"description": "Loads a record, using an ID in the url",
|
||||||
|
"context": "model",
|
||||||
|
>>>>>>> 3af1d8dc7f13091cc4673d53046919bad9ea28f7
|
||||||
"children": true,
|
"children": true,
|
||||||
"data": true,
|
"data": true,
|
||||||
|
"baseComponent": true,
|
||||||
|
"props": {
|
||||||
|
"model": "models"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"newrow": {
|
||||||
|
"description": "Prepares a new record for creation",
|
||||||
|
"context": "model",
|
||||||
|
"children": true,
|
||||||
|
"data": true,
|
||||||
|
"baseComponent": true,
|
||||||
"props": {
|
"props": {
|
||||||
"table": "tables"
|
"table": "tables"
|
||||||
}
|
}
|
||||||
|
@ -715,7 +723,7 @@
|
||||||
"default": "div"
|
"default": "div"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"container": true,
|
"baseComponent": true,
|
||||||
"tags": [
|
"tags": [
|
||||||
"div",
|
"div",
|
||||||
"container",
|
"container",
|
||||||
|
|
|
@ -16,17 +16,17 @@
|
||||||
"@budibase/client": "^0.2.0",
|
"@budibase/client": "^0.2.0",
|
||||||
"@rollup/plugin-commonjs": "^11.1.0",
|
"@rollup/plugin-commonjs": "^11.1.0",
|
||||||
"lodash": "^4.17.15",
|
"lodash": "^4.17.15",
|
||||||
"rollup": "^1.11.0",
|
"rollup": "^2.11.2",
|
||||||
"rollup-plugin-commonjs": "^10.0.2",
|
"rollup-plugin-commonjs": "^10.0.2",
|
||||||
"rollup-plugin-json": "^4.0.0",
|
"rollup-plugin-json": "^4.0.0",
|
||||||
"rollup-plugin-livereload": "^1.0.1",
|
"rollup-plugin-livereload": "^1.0.1",
|
||||||
"rollup-plugin-node-resolve": "^5.0.0",
|
"rollup-plugin-node-resolve": "^5.0.0",
|
||||||
"rollup-plugin-postcss": "^3.1.5",
|
"rollup-plugin-postcss": "^3.1.5",
|
||||||
"rollup-plugin-svelte": "^5.0.0",
|
"rollup-plugin-svelte": "^5.0.3",
|
||||||
"rollup-plugin-terser": "^5.1.1",
|
"rollup-plugin-terser": "^7.0.2",
|
||||||
"shortid": "^2.2.15",
|
"shortid": "^2.2.15",
|
||||||
"sirv-cli": "^0.4.4",
|
"sirv-cli": "^0.4.4",
|
||||||
"svelte": "^3.12.1"
|
"svelte": "^3.29.0"
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"svelte"
|
"svelte"
|
||||||
|
|
|
@ -1,168 +1,61 @@
|
||||||
<script>
|
<script>
|
||||||
import { onMount } from "svelte"
|
import { Label, DatePicker, Input, Select, Toggle } from "@budibase/bbui"
|
||||||
import { fade } from "svelte/transition"
|
|
||||||
import {
|
|
||||||
Label,
|
|
||||||
DatePicker,
|
|
||||||
Input,
|
|
||||||
Select,
|
|
||||||
Button,
|
|
||||||
Toggle,
|
|
||||||
} from "@budibase/bbui"
|
|
||||||
import Dropzone from "./attachments/Dropzone.svelte"
|
import Dropzone from "./attachments/Dropzone.svelte"
|
||||||
import LinkedRowSelector from "./LinkedRowSelector.svelte"
|
import LinkedRowSelector from "./LinkedRowSelector.svelte"
|
||||||
import debounce from "lodash.debounce"
|
|
||||||
import ErrorsBox from "./ErrorsBox.svelte"
|
import ErrorsBox from "./ErrorsBox.svelte"
|
||||||
import { capitalise } from "./helpers"
|
import { capitalise } from "./helpers"
|
||||||
|
|
||||||
export let _bb
|
export let _bb
|
||||||
export let table
|
export let table
|
||||||
export let title
|
|
||||||
export let buttonText
|
|
||||||
export let wide = false
|
export let wide = false
|
||||||
|
|
||||||
const TYPE_MAP = {
|
|
||||||
string: "text",
|
|
||||||
boolean: "checkbox",
|
|
||||||
number: "number",
|
|
||||||
}
|
|
||||||
|
|
||||||
const DEFAULTS_FOR_TYPE = {
|
|
||||||
string: "",
|
|
||||||
boolean: false,
|
|
||||||
number: null,
|
|
||||||
link: [],
|
|
||||||
}
|
|
||||||
|
|
||||||
let row
|
|
||||||
let store = _bb.store
|
let store = _bb.store
|
||||||
let schema = {}
|
let schema = {}
|
||||||
let tableDef = {}
|
|
||||||
let saved = false
|
|
||||||
let rowId
|
let rowId
|
||||||
let isNew = true
|
|
||||||
let errors = {}
|
let errors = {}
|
||||||
|
|
||||||
|
$: schema = $store.data && $store.data._table.schema
|
||||||
$: fields = schema ? Object.keys(schema) : []
|
$: fields = schema ? Object.keys(schema) : []
|
||||||
$: if (table && table.length !== 0) {
|
|
||||||
fetchTable()
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchTable() {
|
|
||||||
const FETCH_TABLE_URL = `/api/tables/${table}`
|
|
||||||
const response = await _bb.api.get(FETCH_TABLE_URL)
|
|
||||||
tableDef = await response.json()
|
|
||||||
schema = tableDef.schema
|
|
||||||
row = {
|
|
||||||
tableId: table,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const save = debounce(async () => {
|
|
||||||
for (let field of fields) {
|
|
||||||
// Assign defaults to empty fields to prevent validation issues
|
|
||||||
if (!(field in row)) {
|
|
||||||
row[field] = DEFAULTS_FOR_TYPE[schema[field].type]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const SAVE_ROW_URL = `/api/${table}/rows`
|
|
||||||
const response = await _bb.api.post(SAVE_ROW_URL, row)
|
|
||||||
|
|
||||||
const json = await response.json()
|
|
||||||
|
|
||||||
if (response.status === 200) {
|
|
||||||
store.update(state => {
|
|
||||||
state[table] = state[table] ? [...state[table], json] : [json]
|
|
||||||
return state
|
|
||||||
})
|
|
||||||
|
|
||||||
errors = {}
|
|
||||||
|
|
||||||
// wipe form, if new row, otherwise update
|
|
||||||
// table to get new _rev
|
|
||||||
row = isNew ? { tableId: table } : json
|
|
||||||
|
|
||||||
// set saved, and unset after 1 second
|
|
||||||
// i.e. make the success notifier appear, then disappear again after time
|
|
||||||
saved = true
|
|
||||||
setTimeout(() => {
|
|
||||||
saved = false
|
|
||||||
}, 3000)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response.status === 400) {
|
|
||||||
errors = Object.keys(json.errors)
|
|
||||||
.map(k => ({ dataPath: k, message: json.errors[k] }))
|
|
||||||
.flat()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
onMount(async () => {
|
|
||||||
const routeParams = _bb.routeParams()
|
|
||||||
rowId =
|
|
||||||
Object.keys(routeParams).length > 0 && (routeParams.id || routeParams[0])
|
|
||||||
isNew = !rowId || rowId === "new"
|
|
||||||
|
|
||||||
if (isNew) {
|
|
||||||
row = { tableId: table }
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const GET_ROW_URL = `/api/${table}/rows/${rowId}`
|
|
||||||
const response = await _bb.api.get(GET_ROW_URL)
|
|
||||||
row = await response.json()
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<form class="form" on:submit|preventDefault>
|
<div class="form-content">
|
||||||
{#if title}
|
<ErrorsBox errors={$store.saveRowErrors || {}} />
|
||||||
<h1>{title}</h1>
|
{#each fields as field}
|
||||||
{/if}
|
<div class="form-field" class:wide>
|
||||||
<div class="form-content">
|
{#if !(schema[field].type === 'boolean' && !wide)}
|
||||||
<ErrorsBox {errors} />
|
<Label extraSmall={!wide} grey={!wide}>
|
||||||
{#each fields as field}
|
{capitalise(schema[field].name)}
|
||||||
<div class="form-field" class:wide>
|
</Label>
|
||||||
{#if !(schema[field].type === 'boolean' && !wide)}
|
{/if}
|
||||||
<Label extraSmall={!wide} grey={!wide}>
|
{#if schema[field].type === 'options'}
|
||||||
{capitalise(schema[field].name)}
|
<Select secondary bind:value={$store.data[field]}>
|
||||||
</Label>
|
<option value="">Choose an option</option>
|
||||||
{/if}
|
{#each schema[field].constraints.inclusion as opt}
|
||||||
{#if schema[field].type === 'options'}
|
<option>{opt}</option>
|
||||||
<Select secondary bind:value={row[field]}>
|
{/each}
|
||||||
<option value="">Choose an option</option>
|
</Select>
|
||||||
{#each schema[field].constraints.inclusion as opt}
|
{:else if schema[field].type === 'datetime'}
|
||||||
<option>{opt}</option>
|
<DatePicker bind:value={$store.data[field]} />
|
||||||
{/each}
|
{:else if schema[field].type === 'boolean'}
|
||||||
</Select>
|
<Toggle
|
||||||
{:else if schema[field].type === 'datetime'}
|
text={wide ? null : capitalise(schema[field].name)}
|
||||||
<DatePicker bind:value={row[field]} />
|
bind:checked={$store.data[field]} />
|
||||||
{:else if schema[field].type === 'boolean'}
|
{:else if schema[field].type === 'number'}
|
||||||
<Toggle
|
<Input type="number" bind:value={$store.data[field]} />
|
||||||
text={wide ? null : capitalise(schema[field].name)}
|
{:else if schema[field].type === 'string'}
|
||||||
bind:checked={row[field]} />
|
<Input bind:value={$store.data[field]} />
|
||||||
{:else if schema[field].type === 'number'}
|
{:else if schema[field].type === 'attachment'}
|
||||||
<Input type="number" bind:value={row[field]} />
|
<Dropzone bind:files={$store.data[field]} />
|
||||||
{:else if schema[field].type === 'string'}
|
{:else if schema[field].type === 'link'}
|
||||||
<Input bind:value={row[field]} />
|
<LinkedRowSelector
|
||||||
{:else if schema[field].type === 'attachment'}
|
secondary
|
||||||
<Dropzone bind:files={row[field]} />
|
showLabel={false}
|
||||||
{:else if schema[field].type === 'link'}
|
bind:linkedRows={$store.data[field]}
|
||||||
<LinkedRowSelector
|
schema={schema[field]} />
|
||||||
secondary
|
{/if}
|
||||||
showLabel={false}
|
|
||||||
bind:linkedRows={row[field]}
|
|
||||||
schema={schema[field]} />
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
<div class="buttons">
|
|
||||||
<Button primary on:click={save} green={saved}>
|
|
||||||
{#if saved}Success{:else}{buttonText || 'Submit Form'}{/if}
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{/each}
|
||||||
</form>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.form {
|
.form {
|
||||||
|
@ -189,9 +82,4 @@
|
||||||
.form-field.wide :global(label) {
|
.form-field.wide :global(label) {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.buttons {
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
<script>
|
||||||
|
import { onMount } from "svelte"
|
||||||
|
|
||||||
|
export let _bb
|
||||||
|
export let table
|
||||||
|
|
||||||
|
let row = {}
|
||||||
|
|
||||||
|
$: {
|
||||||
|
row.tableId = table
|
||||||
|
}
|
||||||
|
|
||||||
|
let target
|
||||||
|
|
||||||
|
async function fetchTable(id) {
|
||||||
|
const FETCH_TABLE_URL = `/api/tables/${id}`
|
||||||
|
const response = await _bb.api.get(FETCH_TABLE_URL)
|
||||||
|
return await response.json()
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
if (table) {
|
||||||
|
const tableObj = await fetchTable(table)
|
||||||
|
row.tableId = table
|
||||||
|
row._table = tableObj
|
||||||
|
_bb.attachChildren(target, {
|
||||||
|
context: row,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
_bb.attachChildren(target, {
|
||||||
|
context: {},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<section bind:this={target} />
|
|
@ -20,6 +20,7 @@
|
||||||
if (response.status === 200) {
|
if (response.status === 200) {
|
||||||
const allRows = await response.json()
|
const allRows = await response.json()
|
||||||
if (allRows.length > 0) return allRows[0]
|
if (allRows.length > 0) return allRows[0]
|
||||||
|
return { tableId: table }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,31 +30,35 @@
|
||||||
let row
|
let row
|
||||||
// if srcdoc, then we assume this is the builder preview
|
// if srcdoc, then we assume this is the builder preview
|
||||||
if (pathParts.length === 0 || pathParts[0] === "srcdoc") {
|
if (pathParts.length === 0 || pathParts[0] === "srcdoc") {
|
||||||
row = await fetchFirstRow()
|
if (table) row = await fetchFirstRow()
|
||||||
} else {
|
} else if (_bb.routeParams().id) {
|
||||||
const id = pathParts[pathParts.length - 1]
|
const GET_ROW_URL = `/api/${table}/rows/${_bb.routeParams().id}`
|
||||||
const GET_ROW_URL = `/api/${table}/rows/${id}`
|
|
||||||
const response = await _bb.api.get(GET_ROW_URL)
|
const response = await _bb.api.get(GET_ROW_URL)
|
||||||
if (response.status === 200) {
|
if (response.status === 200) {
|
||||||
row = await response.json()
|
row = await response.json()
|
||||||
|
} else {
|
||||||
|
throw new Error("Failed to fetch row.", response)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
throw new Exception("Row ID was not supplied to RowDetail")
|
||||||
}
|
}
|
||||||
|
|
||||||
if (row) {
|
if (row) {
|
||||||
// Fetch table schema so we can check for linked rows
|
// Fetch table schema so we can check for linked rows
|
||||||
const table = await fetchTable(row.tableId)
|
const tableObj = await fetchTable(row.tableId)
|
||||||
for (let key of Object.keys(table.schema)) {
|
for (let key of Object.keys(tableObj.schema)) {
|
||||||
if (table.schema[key].type === "link") {
|
if (tableObj.schema[key].type === "link") {
|
||||||
row[key] = Array.isArray(row[key]) ? row[key].length : 0
|
row[key] = Array.isArray(row[key]) ? row[key].length : 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
row._table = tableObj
|
||||||
|
|
||||||
_bb.attachChildren(target, {
|
_bb.attachChildren(target, {
|
||||||
hydrate: false,
|
|
||||||
context: row,
|
context: row,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
throw new Error("Failed to fetch row.", response)
|
_bb.attachChildren(target)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,7 @@ export { default as stackedlist } from "./StackedList.svelte"
|
||||||
export { default as card } from "./Card.svelte"
|
export { default as card } from "./Card.svelte"
|
||||||
export { default as cardhorizontal } from "./CardHorizontal.svelte"
|
export { default as cardhorizontal } from "./CardHorizontal.svelte"
|
||||||
export { default as rowdetail } from "./RowDetail.svelte"
|
export { default as rowdetail } from "./RowDetail.svelte"
|
||||||
|
export { default as newrow } from "./NewRow.svelte"
|
||||||
export { default as datepicker } from "./DatePicker.svelte"
|
export { default as datepicker } from "./DatePicker.svelte"
|
||||||
export * from "./Chart"
|
export * from "./Chart"
|
||||||
export { default as icon } from "./Icon.svelte"
|
export { default as icon } from "./Icon.svelte"
|
||||||
|
|
Loading…
Reference in New Issue