[BUDI-8460] Add New Step to Table Screen Template (#14203)
* [BUDI-8460] Add New Step to Table Screen Template Flow * fix tests * PR Feedback --------- Co-authored-by: Andrew Kingston <andrew@kingston.dev>
|
@ -6,8 +6,8 @@
|
||||||
|
|
||||||
export let onConfirm
|
export let onConfirm
|
||||||
export let onCancel
|
export let onCancel
|
||||||
export let screenUrl
|
export let route
|
||||||
export let screenRole
|
export let role
|
||||||
export let confirmText = "Continue"
|
export let confirmText = "Continue"
|
||||||
|
|
||||||
const appPrefix = "/app"
|
const appPrefix = "/app"
|
||||||
|
@ -15,17 +15,17 @@
|
||||||
let error
|
let error
|
||||||
let modal
|
let modal
|
||||||
|
|
||||||
$: appUrl = screenUrl
|
$: appUrl = route
|
||||||
? `${window.location.origin}${appPrefix}${screenUrl}`
|
? `${window.location.origin}${appPrefix}${route}`
|
||||||
: `${window.location.origin}${appPrefix}`
|
: `${window.location.origin}${appPrefix}`
|
||||||
|
|
||||||
const routeChanged = event => {
|
const routeChanged = event => {
|
||||||
if (!event.detail.startsWith("/")) {
|
if (!event.detail.startsWith("/")) {
|
||||||
screenUrl = "/" + event.detail
|
route = "/" + event.detail
|
||||||
}
|
}
|
||||||
touched = true
|
touched = true
|
||||||
screenUrl = sanitizeUrl(screenUrl)
|
route = sanitizeUrl(route)
|
||||||
if (routeExists(screenUrl)) {
|
if (routeExists(route)) {
|
||||||
error = "This URL is already taken for this access role"
|
error = "This URL is already taken for this access role"
|
||||||
} else {
|
} else {
|
||||||
error = null
|
error = null
|
||||||
|
@ -33,19 +33,19 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
const routeExists = url => {
|
const routeExists = url => {
|
||||||
if (!screenRole) {
|
if (!role) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return get(screenStore).screens.some(
|
return get(screenStore).screens.some(
|
||||||
screen =>
|
screen =>
|
||||||
screen.routing.route.toLowerCase() === url.toLowerCase() &&
|
screen.routing.route.toLowerCase() === url.toLowerCase() &&
|
||||||
screen.routing.roleId === screenRole
|
screen.routing.roleId === role
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const confirmScreenDetails = async () => {
|
const confirmScreenDetails = async () => {
|
||||||
await onConfirm({
|
await onConfirm({
|
||||||
screenUrl,
|
route,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -58,13 +58,13 @@
|
||||||
onConfirm={confirmScreenDetails}
|
onConfirm={confirmScreenDetails}
|
||||||
{onCancel}
|
{onCancel}
|
||||||
cancelText={"Back"}
|
cancelText={"Back"}
|
||||||
disabled={!screenUrl || error || !touched}
|
disabled={!route || error || !touched}
|
||||||
>
|
>
|
||||||
<form on:submit|preventDefault={() => modal.confirm()}>
|
<form on:submit|preventDefault={() => modal.confirm()}>
|
||||||
<Input
|
<Input
|
||||||
label="Enter a URL for the new screen"
|
label="Enter a URL for the new screen"
|
||||||
{error}
|
{error}
|
||||||
bind:value={screenUrl}
|
bind:value={route}
|
||||||
on:change={routeChanged}
|
on:change={routeChanged}
|
||||||
/>
|
/>
|
||||||
<div class="app-server" title={appUrl}>
|
<div class="app-server" title={appUrl}>
|
||||||
|
|
|
@ -34,6 +34,7 @@
|
||||||
import ClientBindingPanel from "components/common/bindings/ClientBindingPanel.svelte"
|
import ClientBindingPanel from "components/common/bindings/ClientBindingPanel.svelte"
|
||||||
import DataSourceCategory from "components/design/settings/controls/DataSourceSelect/DataSourceCategory.svelte"
|
import DataSourceCategory from "components/design/settings/controls/DataSourceSelect/DataSourceCategory.svelte"
|
||||||
import { API } from "api"
|
import { API } from "api"
|
||||||
|
import { datasourceSelect as format } from "helpers/data/format"
|
||||||
|
|
||||||
export let value = {}
|
export let value = {}
|
||||||
export let otherSources
|
export let otherSources
|
||||||
|
@ -51,24 +52,15 @@
|
||||||
let modal
|
let modal
|
||||||
|
|
||||||
$: text = value?.label ?? "Choose an option"
|
$: text = value?.label ?? "Choose an option"
|
||||||
$: tables = $tablesStore.list.map(m => ({
|
$: tables = $tablesStore.list.map(table =>
|
||||||
label: m.name,
|
format.table(table, $datasources.list)
|
||||||
tableId: m._id,
|
)
|
||||||
type: "table",
|
|
||||||
datasource: $datasources.list.find(
|
|
||||||
ds => ds._id === m.sourceId || m.datasourceId
|
|
||||||
),
|
|
||||||
}))
|
|
||||||
$: viewsV1 = $viewsStore.list.map(view => ({
|
$: viewsV1 = $viewsStore.list.map(view => ({
|
||||||
...view,
|
...view,
|
||||||
label: view.name,
|
label: view.name,
|
||||||
type: "view",
|
type: "view",
|
||||||
}))
|
}))
|
||||||
$: viewsV2 = $viewsV2Store.list.map(view => ({
|
$: viewsV2 = $viewsV2Store.list.map(format.viewV2)
|
||||||
...view,
|
|
||||||
label: view.name,
|
|
||||||
type: "viewV2",
|
|
||||||
}))
|
|
||||||
$: views = [...(viewsV1 || []), ...(viewsV2 || [])]
|
$: views = [...(viewsV1 || []), ...(viewsV2 || [])]
|
||||||
$: queries = $queriesStore.list
|
$: queries = $queriesStore.list
|
||||||
.filter(q => showAllQueries || q.queryVerb === "read" || q.readable)
|
.filter(q => showAllQueries || q.queryVerb === "read" || q.readable)
|
||||||
|
|
|
@ -2,24 +2,14 @@
|
||||||
import { Select } from "@budibase/bbui"
|
import { Select } from "@budibase/bbui"
|
||||||
import { createEventDispatcher, onMount } from "svelte"
|
import { createEventDispatcher, onMount } from "svelte"
|
||||||
import { tables as tablesStore, viewsV2 } from "stores/builder"
|
import { tables as tablesStore, viewsV2 } from "stores/builder"
|
||||||
|
import { tableSelect as format } from "helpers/data/format"
|
||||||
|
|
||||||
export let value
|
export let value
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
$: tables = $tablesStore.list.map(table => ({
|
$: tables = $tablesStore.list.map(format.table)
|
||||||
type: "table",
|
$: views = $viewsV2.list.map(format.viewV2)
|
||||||
label: table.name,
|
|
||||||
tableId: table._id,
|
|
||||||
resourceId: table._id,
|
|
||||||
}))
|
|
||||||
$: views = $viewsV2.list.map(view => ({
|
|
||||||
type: "viewV2",
|
|
||||||
id: view.id,
|
|
||||||
label: view.name,
|
|
||||||
tableId: view.tableId,
|
|
||||||
resourceId: view.id,
|
|
||||||
}))
|
|
||||||
$: options = [...(tables || []), ...(views || [])]
|
$: options = [...(tables || []), ...(views || [])]
|
||||||
|
|
||||||
const onChange = e => {
|
const onChange = e => {
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
export const datasourceSelect = {
|
||||||
|
table: (table, datasources) => ({
|
||||||
|
label: table.name,
|
||||||
|
tableId: table._id,
|
||||||
|
type: "table",
|
||||||
|
datasource: datasources.find(
|
||||||
|
datasource => datasource._id === table.sourceId || table.datasourceId
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
viewV2: view => ({
|
||||||
|
...view,
|
||||||
|
label: view.name,
|
||||||
|
type: "viewV2",
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
export const tableSelect = {
|
||||||
|
table: table => ({
|
||||||
|
type: "table",
|
||||||
|
label: table.name,
|
||||||
|
tableId: table._id,
|
||||||
|
resourceId: table._id,
|
||||||
|
}),
|
||||||
|
viewV2: view => ({
|
||||||
|
type: "viewV2",
|
||||||
|
id: view.id,
|
||||||
|
label: view.name,
|
||||||
|
tableId: view.tableId,
|
||||||
|
resourceId: view.id,
|
||||||
|
}),
|
||||||
|
}
|
|
@ -20,7 +20,7 @@
|
||||||
let confirmDeleteDialog
|
let confirmDeleteDialog
|
||||||
let screenDetailsModal
|
let screenDetailsModal
|
||||||
|
|
||||||
const createDuplicateScreen = async ({ screenName, screenUrl }) => {
|
const createDuplicateScreen = async ({ route }) => {
|
||||||
// Create a dupe and ensure it is unique
|
// Create a dupe and ensure it is unique
|
||||||
let duplicateScreen = Helpers.cloneDeep(screen)
|
let duplicateScreen = Helpers.cloneDeep(screen)
|
||||||
delete duplicateScreen._id
|
delete duplicateScreen._id
|
||||||
|
@ -28,9 +28,8 @@
|
||||||
duplicateScreen.props = makeComponentUnique(duplicateScreen.props)
|
duplicateScreen.props = makeComponentUnique(duplicateScreen.props)
|
||||||
|
|
||||||
// Attach the new name and URL
|
// Attach the new name and URL
|
||||||
duplicateScreen.routing.route = sanitizeUrl(screenUrl)
|
duplicateScreen.routing.route = sanitizeUrl(route)
|
||||||
duplicateScreen.routing.homeScreen = false
|
duplicateScreen.routing.homeScreen = false
|
||||||
duplicateScreen.props._instanceName = screenName
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Create the screen
|
// Create the screen
|
||||||
|
@ -136,8 +135,8 @@
|
||||||
<Modal bind:this={screenDetailsModal}>
|
<Modal bind:this={screenDetailsModal}>
|
||||||
<ScreenDetailsModal
|
<ScreenDetailsModal
|
||||||
onConfirm={createDuplicateScreen}
|
onConfirm={createDuplicateScreen}
|
||||||
screenUrl={screen?.routing.route}
|
route={screen?.routing.route}
|
||||||
screenRole={screen?.routing.roleId}
|
role={screen?.routing.roleId}
|
||||||
confirmText="Duplicate"
|
confirmText="Duplicate"
|
||||||
/>
|
/>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
<script>
|
<script>
|
||||||
import ScreenDetailsModal from "components/design/ScreenDetailsModal.svelte"
|
import ScreenDetailsModal from "components/design/ScreenDetailsModal.svelte"
|
||||||
import DatasourceModal from "./DatasourceModal.svelte"
|
import DatasourceModal from "./DatasourceModal.svelte"
|
||||||
import sanitizeUrl from "helpers/sanitizeUrl"
|
import TypeModal from "./TypeModal.svelte"
|
||||||
import FormTypeModal from "./FormTypeModal.svelte"
|
import tableTypes from "./tableTypes"
|
||||||
|
import formTypes from "./formTypes"
|
||||||
import { Modal, notifications } from "@budibase/bbui"
|
import { Modal, notifications } from "@budibase/bbui"
|
||||||
import {
|
import {
|
||||||
screenStore,
|
screenStore,
|
||||||
|
@ -11,14 +12,9 @@
|
||||||
builderStore,
|
builderStore,
|
||||||
} from "stores/builder"
|
} from "stores/builder"
|
||||||
import { auth } from "stores/portal"
|
import { auth } from "stores/portal"
|
||||||
import { get } from "svelte/store"
|
|
||||||
import { capitalise } from "helpers"
|
|
||||||
import { goto } from "@roxi/routify"
|
import { goto } from "@roxi/routify"
|
||||||
import { TOUR_KEYS } from "components/portal/onboarding/tours.js"
|
import { TOUR_KEYS } from "components/portal/onboarding/tours.js"
|
||||||
import blankScreen from "templates/blankScreen"
|
import * as screenTemplating from "templates/screenTemplating"
|
||||||
import formScreen from "templates/formScreen"
|
|
||||||
import gridScreen from "templates/gridScreen"
|
|
||||||
import gridDetailsScreen from "templates/gridDetailsScreen"
|
|
||||||
import { Roles } from "constants/backend"
|
import { Roles } from "constants/backend"
|
||||||
|
|
||||||
let mode
|
let mode
|
||||||
|
@ -26,16 +22,19 @@
|
||||||
let screenDetailsModal
|
let screenDetailsModal
|
||||||
let datasourceModal
|
let datasourceModal
|
||||||
let formTypeModal
|
let formTypeModal
|
||||||
|
let tableTypeModal
|
||||||
|
|
||||||
let selectedTablesAndViews = []
|
let selectedTablesAndViews = []
|
||||||
let permissions = {}
|
let permissions = {}
|
||||||
|
|
||||||
|
$: screens = $screenStore.screens
|
||||||
|
|
||||||
export const show = newMode => {
|
export const show = newMode => {
|
||||||
mode = newMode
|
mode = newMode
|
||||||
selectedTablesAndViews = []
|
selectedTablesAndViews = []
|
||||||
permissions = {}
|
permissions = {}
|
||||||
|
|
||||||
if (mode === "grid" || mode === "gridDetails" || mode === "form") {
|
if (mode === "table" || mode === "form") {
|
||||||
datasourceModal.show()
|
datasourceModal.show()
|
||||||
} else if (mode === "blank") {
|
} else if (mode === "blank") {
|
||||||
screenDetailsModal.show()
|
screenDetailsModal.show()
|
||||||
|
@ -44,136 +43,83 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const createScreen = async screen => {
|
const createScreen = async screenTemplate => {
|
||||||
try {
|
try {
|
||||||
// Check we aren't clashing with an existing URL
|
return await screenStore.save(screenTemplate)
|
||||||
if (hasExistingUrl(screen.routing.route, screen.routing.roleId)) {
|
|
||||||
let suffix = 2
|
|
||||||
let candidateUrl = makeCandidateUrl(screen, suffix)
|
|
||||||
while (hasExistingUrl(candidateUrl, screen.routing.roleId)) {
|
|
||||||
candidateUrl = makeCandidateUrl(screen, ++suffix)
|
|
||||||
}
|
|
||||||
screen.routing.route = candidateUrl
|
|
||||||
}
|
|
||||||
|
|
||||||
screen.routing.route = sanitizeUrl(screen.routing.route)
|
|
||||||
|
|
||||||
return await screenStore.save(screen)
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
notifications.error("Error creating screens")
|
notifications.error("Error creating screens")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const addNavigationLink = async screen =>
|
const createScreens = async screenTemplates => {
|
||||||
|
const newScreens = []
|
||||||
|
|
||||||
|
for (let screenTemplate of screenTemplates) {
|
||||||
|
await addNavigationLink(
|
||||||
|
screenTemplate.data,
|
||||||
|
screenTemplate.navigationLinkLabel
|
||||||
|
)
|
||||||
|
newScreens.push(await createScreen(screenTemplate.data))
|
||||||
|
}
|
||||||
|
|
||||||
|
return newScreens
|
||||||
|
}
|
||||||
|
|
||||||
|
const addNavigationLink = async (screen, linkLabel) => {
|
||||||
|
if (linkLabel == null) return
|
||||||
|
|
||||||
await navigationStore.saveLink(
|
await navigationStore.saveLink(
|
||||||
screen.routing.route,
|
screen.routing.route,
|
||||||
capitalise(screen.routing.route.split("/")[1]),
|
linkLabel,
|
||||||
screen.routing.roleId
|
screen.routing.roleId
|
||||||
)
|
)
|
||||||
|
|
||||||
// Checks if any screens exist in the store with the given route and
|
|
||||||
// currently selected role
|
|
||||||
const hasExistingUrl = (url, screenAccessRole) => {
|
|
||||||
const screens = get(screenStore).screens.filter(
|
|
||||||
s => s.routing.roleId === screenAccessRole
|
|
||||||
)
|
|
||||||
return !!screens.find(s => s.routing?.route === url)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Constructs a candidate URL for a new screen, appending a given suffix to the
|
|
||||||
// screen's URL
|
|
||||||
// e.g. "/sales/:id" => "/sales-1/:id"
|
|
||||||
const makeCandidateUrl = (screen, suffix) => {
|
|
||||||
let url = screen.routing?.route || ""
|
|
||||||
if (url.startsWith("/")) {
|
|
||||||
url = url.slice(1)
|
|
||||||
}
|
|
||||||
if (!url.includes("/")) {
|
|
||||||
return `/${url}-${suffix}`
|
|
||||||
} else {
|
|
||||||
const split = url.split("/")
|
|
||||||
return `/${split[0]}-${suffix}/${split.slice(1).join("/")}`
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const onSelectDatasources = async () => {
|
const onSelectDatasources = async () => {
|
||||||
if (mode === "form") {
|
if (mode === "form") {
|
||||||
formTypeModal.show()
|
formTypeModal.show()
|
||||||
} else if (mode === "grid") {
|
} else if (mode === "table") {
|
||||||
await createGridScreen()
|
tableTypeModal.show()
|
||||||
} else if (mode === "gridDetails") {
|
|
||||||
await createGridDetailsScreen()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const createBlankScreen = async ({ screenUrl }) => {
|
const createBlankScreen = async ({ route }) => {
|
||||||
const screenTemplate = blankScreen(screenUrl)
|
const screenTemplates = screenTemplating.blank({ route, screens })
|
||||||
const screen = await createScreen(screenTemplate)
|
|
||||||
await addNavigationLink(screenTemplate)
|
|
||||||
|
|
||||||
loadNewScreen(screen)
|
const newScreens = await createScreens(screenTemplates)
|
||||||
|
loadNewScreen(newScreens[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
const createGridScreen = async () => {
|
const createTableScreen = async type => {
|
||||||
let firstScreen = null
|
const screenTemplates = selectedTablesAndViews.flatMap(tableOrView =>
|
||||||
|
screenTemplating.table({
|
||||||
for (let tableOrView of selectedTablesAndViews) {
|
screens,
|
||||||
const screenTemplate = gridScreen(
|
|
||||||
tableOrView,
|
tableOrView,
|
||||||
permissions[tableOrView.id]
|
type,
|
||||||
|
permissions: permissions[tableOrView.id],
|
||||||
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
const screen = await createScreen(screenTemplate)
|
const newScreens = await createScreens(screenTemplates)
|
||||||
await addNavigationLink(screen)
|
loadNewScreen(newScreens[0])
|
||||||
|
|
||||||
firstScreen ??= screen
|
|
||||||
}
|
}
|
||||||
|
|
||||||
loadNewScreen(firstScreen)
|
const createFormScreen = async type => {
|
||||||
}
|
const screenTemplates = selectedTablesAndViews.flatMap(tableOrView =>
|
||||||
|
screenTemplating.form({
|
||||||
const createGridDetailsScreen = async () => {
|
screens,
|
||||||
let firstScreen = null
|
|
||||||
|
|
||||||
for (let tableOrView of selectedTablesAndViews) {
|
|
||||||
const screenTemplate = gridDetailsScreen(
|
|
||||||
tableOrView,
|
tableOrView,
|
||||||
permissions[tableOrView.id]
|
type,
|
||||||
|
permissions: permissions[tableOrView.id],
|
||||||
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
const screen = await createScreen(screenTemplate)
|
const newScreens = await createScreens(screenTemplates)
|
||||||
await addNavigationLink(screen)
|
|
||||||
|
|
||||||
firstScreen ??= screen
|
if (type === "update" || type === "create") {
|
||||||
}
|
|
||||||
|
|
||||||
loadNewScreen(firstScreen)
|
|
||||||
}
|
|
||||||
|
|
||||||
const createFormScreen = async formType => {
|
|
||||||
let firstScreen = null
|
|
||||||
|
|
||||||
for (let tableOrView of selectedTablesAndViews) {
|
|
||||||
const screenTemplate = formScreen(
|
|
||||||
tableOrView,
|
|
||||||
formType,
|
|
||||||
permissions[tableOrView.id]
|
|
||||||
)
|
|
||||||
|
|
||||||
const screen = await createScreen(screenTemplate)
|
|
||||||
// Only add a navigation link for `Create`, as both `Update` and `View`
|
|
||||||
// require an `id` in their URL in order to function.
|
|
||||||
if (formType === "Create") {
|
|
||||||
await addNavigationLink(screen)
|
|
||||||
}
|
|
||||||
|
|
||||||
firstScreen ??= screen
|
|
||||||
}
|
|
||||||
|
|
||||||
if (formType === "Update" || formType === "Create") {
|
|
||||||
const associatedTour =
|
const associatedTour =
|
||||||
formType === "Update"
|
type === "update"
|
||||||
? TOUR_KEYS.BUILDER_FORM_VIEW_UPDATE
|
? TOUR_KEYS.BUILDER_FORM_VIEW_UPDATE
|
||||||
: TOUR_KEYS.BUILDER_FORM_CREATE
|
: TOUR_KEYS.BUILDER_FORM_CREATE
|
||||||
|
|
||||||
|
@ -183,7 +129,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
loadNewScreen(firstScreen)
|
loadNewScreen(newScreens[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
const loadNewScreen = screen => {
|
const loadNewScreen = screen => {
|
||||||
|
@ -199,7 +145,11 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
const fetchPermission = resourceId => {
|
const fetchPermission = resourceId => {
|
||||||
permissions[resourceId] = { loading: true, read: null, write: null }
|
permissions[resourceId] = {
|
||||||
|
loading: true,
|
||||||
|
read: Roles.BASIC,
|
||||||
|
write: Roles.BASIC,
|
||||||
|
}
|
||||||
|
|
||||||
permissionsStore
|
permissionsStore
|
||||||
.forResource(resourceId)
|
.forResource(resourceId)
|
||||||
|
@ -218,8 +168,8 @@
|
||||||
if (permissions[resourceId]?.loading) {
|
if (permissions[resourceId]?.loading) {
|
||||||
permissions[resourceId] = {
|
permissions[resourceId] = {
|
||||||
loading: false,
|
loading: false,
|
||||||
read: Roles.PUBLIC,
|
read: Roles.BASIC,
|
||||||
write: Roles.PUBLIC,
|
write: Roles.BASIC,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -250,18 +200,31 @@
|
||||||
<Modal bind:this={datasourceModal} autoFocus={false}>
|
<Modal bind:this={datasourceModal} autoFocus={false}>
|
||||||
<DatasourceModal
|
<DatasourceModal
|
||||||
{selectedTablesAndViews}
|
{selectedTablesAndViews}
|
||||||
{permissions}
|
|
||||||
onConfirm={onSelectDatasources}
|
onConfirm={onSelectDatasources}
|
||||||
on:toggle={handleTableOrViewToggle}
|
on:toggle={handleTableOrViewToggle}
|
||||||
/>
|
/>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
|
<Modal bind:this={tableTypeModal}>
|
||||||
|
<TypeModal
|
||||||
|
title="Choose how you want to manage rows"
|
||||||
|
types={tableTypes}
|
||||||
|
onConfirm={createTableScreen}
|
||||||
|
onCancel={() => {
|
||||||
|
tableTypeModal.hide()
|
||||||
|
datasourceModal.show()
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
<Modal bind:this={screenDetailsModal}>
|
<Modal bind:this={screenDetailsModal}>
|
||||||
<ScreenDetailsModal onConfirm={createBlankScreen} />
|
<ScreenDetailsModal onConfirm={createBlankScreen} />
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
<Modal bind:this={formTypeModal}>
|
<Modal bind:this={formTypeModal}>
|
||||||
<FormTypeModal
|
<TypeModal
|
||||||
|
title="Select form type"
|
||||||
|
types={formTypes}
|
||||||
onConfirm={createFormScreen}
|
onConfirm={createFormScreen}
|
||||||
onCancel={() => {
|
onCancel={() => {
|
||||||
formTypeModal.hide()
|
formTypeModal.hide()
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
<script>
|
<script>
|
||||||
import { ModalContent, Layout, notifications, Body } from "@budibase/bbui"
|
import { Body, ModalContent, Layout, notifications } from "@budibase/bbui"
|
||||||
import { datasources as datasourcesStore } from "stores/builder"
|
import { datasources as datasourcesStore } from "stores/builder"
|
||||||
import ICONS from "components/backend/DatasourceNavigator/icons"
|
import ICONS from "components/backend/DatasourceNavigator/icons"
|
||||||
import { IntegrationNames } from "constants"
|
import { IntegrationNames } from "constants"
|
||||||
import { createEventDispatcher, onMount } from "svelte"
|
import { createEventDispatcher, onMount } from "svelte"
|
||||||
import TableOrViewOption from "./TableOrViewOption.svelte"
|
import TableOrViewOption from "./TableOrViewOption.svelte"
|
||||||
|
import * as format from "helpers/data/format"
|
||||||
|
|
||||||
export let onConfirm
|
export let onConfirm
|
||||||
export let selectedTablesAndViews
|
export let selectedTablesAndViews
|
||||||
export let permissions
|
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
|
@ -21,38 +21,37 @@
|
||||||
icon: "Remove",
|
icon: "Remove",
|
||||||
name: view.name,
|
name: view.name,
|
||||||
id: view.id,
|
id: view.id,
|
||||||
clientData: {
|
tableSelectFormat: format.tableSelect.viewV2(view),
|
||||||
...view,
|
datasourceSelectFormat: format.datasourceSelect.viewV2(view),
|
||||||
type: "viewV2",
|
|
||||||
label: view.name,
|
|
||||||
},
|
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
const getTablesAndViews = datasource => {
|
const getTablesAndViews = datasource => {
|
||||||
let tablesAndViews = []
|
let tablesAndViews = []
|
||||||
const rawTables = Array.isArray(datasource.entities)
|
const tables = Array.isArray(datasource.entities)
|
||||||
? datasource.entities
|
? datasource.entities
|
||||||
: Object.values(datasource.entities ?? {})
|
: Object.values(datasource.entities ?? {})
|
||||||
|
|
||||||
for (const rawTable of rawTables) {
|
for (const table of tables) {
|
||||||
if (rawTable._id === "ta_users") {
|
if (table._id === "ta_users") {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
const table = {
|
const formattedTable = {
|
||||||
icon: "Table",
|
icon: "Table",
|
||||||
name: rawTable.name,
|
name: table.name,
|
||||||
id: rawTable._id,
|
id: table._id,
|
||||||
clientData: {
|
tableSelectFormat: format.tableSelect.table(table),
|
||||||
...rawTable,
|
datasourceSelectFormat: format.datasourceSelect.table(
|
||||||
label: rawTable.name,
|
table,
|
||||||
tableId: rawTable._id,
|
$datasourcesStore.list
|
||||||
type: "table",
|
),
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tablesAndViews = tablesAndViews.concat([table, ...getViews(rawTable)])
|
tablesAndViews = tablesAndViews.concat([
|
||||||
|
formattedTable,
|
||||||
|
...getViews(table),
|
||||||
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
return tablesAndViews
|
return tablesAndViews
|
||||||
|
@ -96,28 +95,27 @@
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<span>
|
<ModalContent
|
||||||
<ModalContent
|
|
||||||
title="Autogenerated screens"
|
title="Autogenerated screens"
|
||||||
confirmText="Confirm"
|
confirmText="Next"
|
||||||
cancelText="Back"
|
cancelText="Cancel"
|
||||||
{onConfirm}
|
{onConfirm}
|
||||||
disabled={!selectedTablesAndViews.length}
|
disabled={!selectedTablesAndViews.length}
|
||||||
size="L"
|
size="L"
|
||||||
>
|
>
|
||||||
<Body size="S">
|
<Body size="S">
|
||||||
Select which datasources you would like to use to create your screens
|
Select which datasources you would like to use to create your screens
|
||||||
</Body>
|
</Body>
|
||||||
<Layout noPadding gap="S">
|
<Layout noPadding gap="S">
|
||||||
{#each datasources as datasource}
|
{#each datasources as datasource}
|
||||||
<div class="data-source-wrap">
|
<div class="datasource">
|
||||||
<div class="data-source-header">
|
<div class="header">
|
||||||
<svelte:component
|
<svelte:component
|
||||||
this={datasource.iconComponent}
|
this={datasource.iconComponent}
|
||||||
height="24"
|
height="18"
|
||||||
width="24"
|
width="18"
|
||||||
/>
|
/>
|
||||||
<div class="data-source-name">{datasource.name}</div>
|
<h2>{datasource.name}</h2>
|
||||||
</div>
|
</div>
|
||||||
<!-- List all tables -->
|
<!-- List all tables -->
|
||||||
{#each datasource.tablesAndViews as tableOrView}
|
{#each datasource.tablesAndViews as tableOrView}
|
||||||
|
@ -125,7 +123,6 @@
|
||||||
selected => selected.id === tableOrView.id
|
selected => selected.id === tableOrView.id
|
||||||
)}
|
)}
|
||||||
<TableOrViewOption
|
<TableOrViewOption
|
||||||
roles={permissions[tableOrView.id]}
|
|
||||||
on:click={() => toggleSelection(tableOrView)}
|
on:click={() => toggleSelection(tableOrView)}
|
||||||
{selected}
|
{selected}
|
||||||
{tableOrView}
|
{tableOrView}
|
||||||
|
@ -134,22 +131,40 @@
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</Layout>
|
</Layout>
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
</span>
|
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.data-source-wrap {
|
.datasource {
|
||||||
padding-bottom: var(--spectrum-alias-item-padding-s);
|
padding-bottom: 15px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
grid-gap: var(--spacing-s);
|
grid-gap: var(--spacing-s);
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
.data-source-header {
|
|
||||||
|
.datasource:last-child {
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: var(--spacing-m);
|
padding-bottom: var(--spacing-m);
|
||||||
padding-bottom: var(--spacing-xs);
|
}
|
||||||
|
|
||||||
|
.header :global(svg) {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header h2 {
|
||||||
|
padding-top: 1px;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 400;
|
||||||
|
margin: 0;
|
||||||
|
margin-left: 10px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,115 +0,0 @@
|
||||||
<script>
|
|
||||||
import { ModalContent, Layout, Body, Icon } from "@budibase/bbui"
|
|
||||||
|
|
||||||
let type = null
|
|
||||||
|
|
||||||
export let onCancel = () => {}
|
|
||||||
export let onConfirm = () => {}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<span>
|
|
||||||
<ModalContent
|
|
||||||
title="Select form type"
|
|
||||||
confirmText="Done"
|
|
||||||
cancelText="Back"
|
|
||||||
onConfirm={() => onConfirm(type)}
|
|
||||||
{onCancel}
|
|
||||||
disabled={!type}
|
|
||||||
size="L"
|
|
||||||
>
|
|
||||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
|
||||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
|
||||||
<Layout noPadding gap="S">
|
|
||||||
<div
|
|
||||||
class="form-type"
|
|
||||||
class:selected={type === "Create"}
|
|
||||||
on:click={() => (type = "Create")}
|
|
||||||
>
|
|
||||||
<div class="form-type-wrap">
|
|
||||||
<div class="form-type-content">
|
|
||||||
<Body noPadding>Create a new row</Body>
|
|
||||||
<Body size="S">
|
|
||||||
For capturing and storing new data from your users
|
|
||||||
</Body>
|
|
||||||
</div>
|
|
||||||
{#if type === "Create"}
|
|
||||||
<span class="form-type-check">
|
|
||||||
<Icon size="S" name="CheckmarkCircle" />
|
|
||||||
</span>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="form-type"
|
|
||||||
class:selected={type === "Update"}
|
|
||||||
on:click={() => (type = "Update")}
|
|
||||||
>
|
|
||||||
<div class="form-type-wrap">
|
|
||||||
<div class="form-type-content">
|
|
||||||
<Body noPadding>Update an existing row</Body>
|
|
||||||
<Body size="S">For viewing and updating existing data</Body>
|
|
||||||
</div>
|
|
||||||
{#if type === "Update"}
|
|
||||||
<span class="form-type-check">
|
|
||||||
<Icon size="S" name="CheckmarkCircle" />
|
|
||||||
</span>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="form-type"
|
|
||||||
class:selected={type === "View"}
|
|
||||||
on:click={() => (type = "View")}
|
|
||||||
>
|
|
||||||
<div class="form-type-wrap">
|
|
||||||
<div class="form-type-content">
|
|
||||||
<Body noPadding>View an existing row</Body>
|
|
||||||
<Body size="S">For a read only view of your data</Body>
|
|
||||||
</div>
|
|
||||||
{#if type === "View"}
|
|
||||||
<span class="form-type-check">
|
|
||||||
<Icon size="S" name="CheckmarkCircle" />
|
|
||||||
</span>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Layout>
|
|
||||||
</ModalContent>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.form-type {
|
|
||||||
cursor: pointer;
|
|
||||||
gap: var(--spacing-s);
|
|
||||||
padding: var(--spacing-m) var(--spacing-xl);
|
|
||||||
background: var(--spectrum-alias-background-color-secondary);
|
|
||||||
transition: 0.3s all;
|
|
||||||
border: 1px solid var(--spectrum-global-color-gray-300);
|
|
||||||
border-radius: 4px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
.selected,
|
|
||||||
.form-type:hover {
|
|
||||||
background: var(--spectrum-alias-background-color-tertiary);
|
|
||||||
}
|
|
||||||
.form-type-wrap {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
.form-type :global(p:nth-child(2)) {
|
|
||||||
color: var(--grey-6);
|
|
||||||
}
|
|
||||||
.form-type-check {
|
|
||||||
margin-left: auto;
|
|
||||||
}
|
|
||||||
.form-type-check :global(.spectrum-Icon) {
|
|
||||||
color: var(--spectrum-global-color-green-600);
|
|
||||||
}
|
|
||||||
.form-type-content {
|
|
||||||
gap: var(--spacing-s);
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,49 +1,14 @@
|
||||||
<script>
|
<script>
|
||||||
import { Icon, AbsTooltip } from "@budibase/bbui"
|
import { Icon } from "@budibase/bbui"
|
||||||
import RoleIcon from "components/common/RoleIcon.svelte"
|
|
||||||
|
|
||||||
export let tableOrView
|
export let tableOrView
|
||||||
export let roles
|
|
||||||
export let selected = false
|
export let selected = false
|
||||||
|
|
||||||
$: hideRoles = roles == undefined || roles?.loading
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
<div role="button" tabindex="0" class="datasource" class:selected on:click>
|
<div role="button" tabindex="0" class="datasource" class:selected on:click>
|
||||||
<div class="content">
|
|
||||||
<Icon name={tableOrView.icon} />
|
<Icon name={tableOrView.icon} />
|
||||||
<span>{tableOrView.name}</span>
|
<span>{tableOrView.name}</span>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class:hideRoles class="roles">
|
|
||||||
<AbsTooltip
|
|
||||||
type="info"
|
|
||||||
text={`Screens that only read data will be generated with access "${roles?.read?.toLowerCase()}"`}
|
|
||||||
>
|
|
||||||
<div class="role">
|
|
||||||
<span>read</span>
|
|
||||||
<RoleIcon
|
|
||||||
size="XS"
|
|
||||||
id={roles?.read}
|
|
||||||
disabled={roles?.loading !== false}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</AbsTooltip>
|
|
||||||
<AbsTooltip
|
|
||||||
type="info"
|
|
||||||
text={`Screens that write data will be generated with access "${roles?.write?.toLowerCase()}"`}
|
|
||||||
>
|
|
||||||
<div class="role">
|
|
||||||
<span>write</span>
|
|
||||||
<RoleIcon
|
|
||||||
size="XS"
|
|
||||||
id={roles?.write}
|
|
||||||
disabled={roles?.loading !== false}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</AbsTooltip>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
@ -52,18 +17,8 @@
|
||||||
border: 1px solid var(--spectrum-global-color-gray-300);
|
border: 1px solid var(--spectrum-global-color-gray-300);
|
||||||
transition: 160ms all;
|
transition: 160ms all;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
user-select: none;
|
user-select: none;
|
||||||
background-color: var(--background);
|
|
||||||
}
|
|
||||||
|
|
||||||
.datasource :global(svg) {
|
|
||||||
transition: 160ms all;
|
|
||||||
color: var(--spectrum-global-color-gray-600);
|
|
||||||
}
|
|
||||||
|
|
||||||
.content {
|
|
||||||
padding: var(--spectrum-alias-item-padding-s);
|
padding: var(--spectrum-alias-item-padding-s);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
@ -71,7 +26,12 @@
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content span {
|
.datasource :global(svg) {
|
||||||
|
transition: 160ms all;
|
||||||
|
color: var(--spectrum-global-color-gray-600);
|
||||||
|
}
|
||||||
|
|
||||||
|
.datasource span {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
|
@ -84,29 +44,4 @@
|
||||||
.selected {
|
.selected {
|
||||||
border: 1px solid var(--blue) !important;
|
border: 1px solid var(--blue) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.roles {
|
|
||||||
margin-left: auto;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: end;
|
|
||||||
padding-right: var(--spectrum-alias-item-padding-s);
|
|
||||||
opacity: 0.5;
|
|
||||||
transition: opacity 160ms;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hideRoles {
|
|
||||||
opacity: 0;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.role {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.role span {
|
|
||||||
font-size: 11px;
|
|
||||||
margin-right: 5px;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -0,0 +1,82 @@
|
||||||
|
<script>
|
||||||
|
import { ModalContent, Layout, Body } from "@budibase/bbui"
|
||||||
|
|
||||||
|
let selectedType = null
|
||||||
|
|
||||||
|
export let title
|
||||||
|
export let types
|
||||||
|
export let onCancel = () => {}
|
||||||
|
export let onConfirm = () => {}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<ModalContent
|
||||||
|
{title}
|
||||||
|
confirmText="Done"
|
||||||
|
cancelText="Back"
|
||||||
|
onConfirm={() => onConfirm(selectedType)}
|
||||||
|
{onCancel}
|
||||||
|
disabled={!selectedType}
|
||||||
|
size="L"
|
||||||
|
>
|
||||||
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
|
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||||
|
<Layout noPadding gap="S">
|
||||||
|
{#each types as type}
|
||||||
|
<div
|
||||||
|
class="type"
|
||||||
|
class:selected={selectedType === type.id}
|
||||||
|
on:click={() => (selectedType = type.id)}
|
||||||
|
>
|
||||||
|
<div class="image">
|
||||||
|
<img alt={type.img.alt} src={type.img.src} />
|
||||||
|
</div>
|
||||||
|
<div class="typeContent">
|
||||||
|
<Body noPadding>{type.title}</Body>
|
||||||
|
<Body size="S">{type.description}</Body>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</Layout>
|
||||||
|
</ModalContent>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.type {
|
||||||
|
cursor: pointer;
|
||||||
|
gap: var(--spacing-s);
|
||||||
|
background: var(--spectrum-alias-background-color-secondary);
|
||||||
|
transition: 0.3s all;
|
||||||
|
border: 1px solid var(--spectrum-global-color-gray-300);
|
||||||
|
border-radius: 4px;
|
||||||
|
display: flex;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.type:hover {
|
||||||
|
border: 1px solid var(--grey-5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.type.selected {
|
||||||
|
border: 1px solid var(--blue);
|
||||||
|
}
|
||||||
|
.type :global(p:nth-child(2)) {
|
||||||
|
color: var(--grey-6);
|
||||||
|
}
|
||||||
|
.typeContent {
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: var(--spacing-m) var(--spacing-xl);
|
||||||
|
flex-grow: 1;
|
||||||
|
gap: var(--spacing-s);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image {
|
||||||
|
min-width: 133px;
|
||||||
|
height: 73px;
|
||||||
|
background-color: var(--grey-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.image img {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,35 @@
|
||||||
|
import formView from "./images/formView.svg"
|
||||||
|
import formUpdate from "./images/formUpdate.svg"
|
||||||
|
import formCreate from "./images/formCreate.svg"
|
||||||
|
|
||||||
|
const tableTypes = [
|
||||||
|
{
|
||||||
|
id: "create",
|
||||||
|
img: {
|
||||||
|
alt: "A form containing new data",
|
||||||
|
src: formCreate,
|
||||||
|
},
|
||||||
|
title: "Create a new row",
|
||||||
|
description: "For capturing and storing new data from your users",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "update",
|
||||||
|
img: {
|
||||||
|
alt: "A form containing edited data",
|
||||||
|
src: formUpdate,
|
||||||
|
},
|
||||||
|
title: "Update an existing row",
|
||||||
|
description: "For viewing and updating existing data",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "view",
|
||||||
|
img: {
|
||||||
|
alt: "A form containing read-only data",
|
||||||
|
src: formView,
|
||||||
|
},
|
||||||
|
title: "View an existing row",
|
||||||
|
description: "For a read only view of your data",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
export default tableTypes
|
Before Width: | Height: | Size: 32 KiB |
|
@ -0,0 +1,15 @@
|
||||||
|
<svg width="118" height="65" viewBox="0 0 118 65" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect width="118" height="65" fill="#34BB84"/>
|
||||||
|
<rect width="118" height="65" fill="#34BB84"/>
|
||||||
|
<mask id="path-3-inside-1_51_2" fill="white">
|
||||||
|
<path d="M22 12H94V65H22V12Z"/>
|
||||||
|
</mask>
|
||||||
|
<path d="M22 12H94V65H22V12Z" fill="url(#paint0_linear_51_2)"/>
|
||||||
|
<path d="M22 12V11H21V12H22ZM94 12H95V11H94V12ZM22 13H94V11H22V13ZM93 12V65H95V12H93ZM23 65V12H21V65H23Z" fill="white" fill-opacity="0.2" mask="url(#path-3-inside-1_51_2)"/>
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="paint0_linear_51_2" x1="22" y1="12" x2="92.4561" y2="66.9785" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="white" stop-opacity="0.8"/>
|
||||||
|
<stop offset="1" stop-color="white" stop-opacity="0"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 771 B |
Before Width: | Height: | Size: 22 KiB |
|
@ -0,0 +1,20 @@
|
||||||
|
<svg width="118" height="65" viewBox="0 0 118 65" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect width="118" height="65" fill="#FD4F01"/>
|
||||||
|
<rect width="118" height="65" fill="#FD4F01"/>
|
||||||
|
<mask id="path-3-inside-1_49_949" fill="white">
|
||||||
|
<path d="M22 12H94V65H22V12Z"/>
|
||||||
|
</mask>
|
||||||
|
<path d="M22 12H94V65H22V12Z" fill="url(#paint0_linear_49_949)"/>
|
||||||
|
<path d="M22 12V11H21V12H22ZM94 12H95V11H94V12ZM22 13H94V11H22V13ZM93 12V65H95V12H93ZM23 65V12H21V65H23Z" fill="white" fill-opacity="0.2" mask="url(#path-3-inside-1_49_949)"/>
|
||||||
|
<rect x="46" y="29" width="15" height="4" fill="white" fill-opacity="0.97"/>
|
||||||
|
<rect x="46" y="45" width="15" height="4" fill="white" fill-opacity="0.97"/>
|
||||||
|
<rect x="46" y="35" width="25" height="6" fill="white" fill-opacity="0.97"/>
|
||||||
|
<rect x="46" y="51" width="25" height="6" fill="white" fill-opacity="0.97"/>
|
||||||
|
<path d="M46 21H71V25H46V21Z" fill="white" fill-opacity="0.97"/>
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="paint0_linear_49_949" x1="22" y1="12" x2="92.4561" y2="66.9785" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="white" stop-opacity="0.6"/>
|
||||||
|
<stop offset="1" stop-color="white" stop-opacity="0"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
|
@ -0,0 +1,20 @@
|
||||||
|
<svg width="118" height="65" viewBox="0 0 118 65" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect width="118" height="65" fill="#FD4F01"/>
|
||||||
|
<rect width="118" height="65" fill="#FD4F01"/>
|
||||||
|
<mask id="path-3-inside-1_49_940" fill="white">
|
||||||
|
<path d="M22 12H94V65H22V12Z"/>
|
||||||
|
</mask>
|
||||||
|
<path d="M22 12H94V65H22V12Z" fill="url(#paint0_linear_49_940)"/>
|
||||||
|
<path d="M22 12V11H21V12H22ZM94 12H95V11H94V12ZM22 13H94V11H22V13ZM93 12V65H95V12H93ZM23 65V12H21V65H23Z" fill="white" fill-opacity="0.2" mask="url(#path-3-inside-1_49_940)"/>
|
||||||
|
<rect x="46" y="29" width="15" height="4" fill="white" fill-opacity="0.97"/>
|
||||||
|
<rect x="46" y="45" width="15" height="4" fill="white" fill-opacity="0.5"/>
|
||||||
|
<rect x="46" y="35" width="25" height="6" fill="white" fill-opacity="0.97"/>
|
||||||
|
<path d="M46 21H71V25H46V21Z" fill="white" fill-opacity="0.5"/>
|
||||||
|
<rect x="46" y="51" width="25" height="6" fill="white" fill-opacity="0.5"/>
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="paint0_linear_49_940" x1="22" y1="12" x2="92.4561" y2="66.9785" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="white" stop-opacity="0.6"/>
|
||||||
|
<stop offset="1" stop-color="white" stop-opacity="0"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
|
@ -0,0 +1,20 @@
|
||||||
|
<svg width="118" height="65" viewBox="0 0 118 65" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect width="118" height="65" fill="#FD4F01"/>
|
||||||
|
<rect width="118" height="65" fill="#FD4F01"/>
|
||||||
|
<mask id="path-3-inside-1_49_931" fill="white">
|
||||||
|
<path d="M22 12H94V65H22V12Z"/>
|
||||||
|
</mask>
|
||||||
|
<path d="M22 12H94V65H22V12Z" fill="url(#paint0_linear_49_931)"/>
|
||||||
|
<path d="M22 12V11H21V12H22ZM94 12H95V11H94V12ZM22 13H94V11H22V13ZM93 12V65H95V12H93ZM23 65V12H21V65H23Z" fill="white" fill-opacity="0.2" mask="url(#path-3-inside-1_49_931)"/>
|
||||||
|
<rect x="46" y="29" width="15" height="4" fill="white" fill-opacity="0.5"/>
|
||||||
|
<rect x="46" y="45" width="15" height="4" fill="white" fill-opacity="0.5"/>
|
||||||
|
<rect x="46" y="35" width="25" height="6" fill="white" fill-opacity="0.5"/>
|
||||||
|
<path d="M46 21H71V25H46V21Z" fill="white" fill-opacity="0.5"/>
|
||||||
|
<rect x="46" y="51" width="25" height="6" fill="white" fill-opacity="0.5"/>
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="paint0_linear_49_931" x1="22" y1="12" x2="92.4561" y2="66.9785" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="white" stop-opacity="0.6"/>
|
||||||
|
<stop offset="1" stop-color="white" stop-opacity="0"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 26 KiB |
Before Width: | Height: | Size: 29 KiB |
Before Width: | Height: | Size: 22 KiB |
|
@ -0,0 +1,44 @@
|
||||||
|
<svg width="118" height="65" viewBox="0 0 118 65" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect width="118" height="65" fill="#765FFE"/>
|
||||||
|
<mask id="path-2-inside-1_4_100" fill="white">
|
||||||
|
<path d="M22 12H94V65H22V12Z"/>
|
||||||
|
</mask>
|
||||||
|
<path d="M22 12H94V65H22V12Z" fill="url(#paint0_linear_4_100)"/>
|
||||||
|
<path d="M22 12V11H21V12H22ZM94 12H95V11H94V12ZM22 13H94V11H22V13ZM93 12V65H95V12H93ZM23 65V12H21V65H23Z" fill="white" fill-opacity="0.2" mask="url(#path-2-inside-1_4_100)"/>
|
||||||
|
<path d="M35.6901 23.0003H44.8169V28.0003H35.6901V23.0003Z" fill="white" fill-opacity="0.9"/>
|
||||||
|
<path d="M35.6901 28.5004H44.8169V33.5004H35.6901V28.5004Z" fill="white" fill-opacity="0.5"/>
|
||||||
|
<path d="M35.6901 34H44.8169V39H35.6901V34Z" fill="white" fill-opacity="0.5"/>
|
||||||
|
<path d="M35.6901 39.5002H44.8169V44.5002H35.6901V39.5002Z" fill="white" fill-opacity="0.5"/>
|
||||||
|
<path d="M35.6901 45.0003H44.8169V50.0003H35.6901V45.0003Z" fill="white" fill-opacity="0.5"/>
|
||||||
|
<path d="M35.6901 50.5004H44.8169V55.5004H35.6901V50.5004Z" fill="white" fill-opacity="0.5"/>
|
||||||
|
<path d="M45.3234 23.0003H54.4502V28.0003H45.3234V23.0003Z" fill="white" fill-opacity="0.9"/>
|
||||||
|
<path d="M45.3234 28.5004H54.4502V33.5004H45.3234V28.5004Z" fill="white" fill-opacity="0.5"/>
|
||||||
|
<path d="M45.3234 34H54.4502V39H45.3234V34Z" fill="white" fill-opacity="0.5"/>
|
||||||
|
<path d="M45.3234 39.5002H54.4502V44.5002H45.3234V39.5002Z" fill="white" fill-opacity="0.5"/>
|
||||||
|
<path d="M45.3234 45.0003H54.4502V50.0003H45.3234V45.0003Z" fill="white" fill-opacity="0.5"/>
|
||||||
|
<path d="M45.3234 50.5004H54.4502V55.5004H45.3234V50.5004Z" fill="white" fill-opacity="0.5"/>
|
||||||
|
<path d="M54.9576 23.0003H64.0844V28.0003H54.9576V23.0003Z" fill="white" fill-opacity="0.9"/>
|
||||||
|
<path d="M54.9576 28.5004H64.0844V33.5004H54.9576V28.5004Z" fill="white" fill-opacity="0.5"/>
|
||||||
|
<path d="M54.9576 34H64.0844V39H54.9576V34Z" fill="white" fill-opacity="0.5"/>
|
||||||
|
<path d="M54.9576 39.5002H64.0844V44.5002H54.9576V39.5002Z" fill="white" fill-opacity="0.5"/>
|
||||||
|
<path d="M54.9576 45.0003H64.0844V50.0003H54.9576V45.0003Z" fill="white" fill-opacity="0.5"/>
|
||||||
|
<path d="M54.9576 50.5004H64.0844V55.5004H54.9576V50.5004Z" fill="white" fill-opacity="0.5"/>
|
||||||
|
<path d="M64.5915 23.0003H73.7183V28.0003H64.5915V23.0003Z" fill="white" fill-opacity="0.9"/>
|
||||||
|
<path d="M64.5915 28.5004H73.7183V33.5004H64.5915V28.5004Z" fill="white" fill-opacity="0.5"/>
|
||||||
|
<path d="M64.5915 34H73.7183V39H64.5915V34Z" fill="white" fill-opacity="0.5"/>
|
||||||
|
<path d="M64.5915 39.5002H73.7183V44.5002H64.5915V39.5002Z" fill="white" fill-opacity="0.5"/>
|
||||||
|
<path d="M64.5915 45.0003H73.7183V50.0003H64.5915V45.0003Z" fill="white" fill-opacity="0.5"/>
|
||||||
|
<path d="M64.5915 50.5004H73.7183V55.5004H64.5915V50.5004Z" fill="white" fill-opacity="0.5"/>
|
||||||
|
<path d="M74.2253 23.0003H83.3521V28.0003H74.2253V23.0003Z" fill="white" fill-opacity="0.9"/>
|
||||||
|
<path d="M74.2253 28.5004H83.3521V33.5004H74.2253V28.5004Z" fill="white" fill-opacity="0.5"/>
|
||||||
|
<path d="M74.2253 34H83.3521V39H74.2253V34Z" fill="white" fill-opacity="0.5"/>
|
||||||
|
<path d="M74.2253 39.5002H83.3521V44.5002H74.2253V39.5002Z" fill="white" fill-opacity="0.5"/>
|
||||||
|
<path d="M74.2253 45.0003H83.3521V50.0003H74.2253V45.0003Z" fill="white" fill-opacity="0.5"/>
|
||||||
|
<path d="M74.2253 50.5004H83.3521V55.5004H74.2253V50.5004Z" fill="white" fill-opacity="0.5"/>
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="paint0_linear_4_100" x1="22" y1="12" x2="92.4561" y2="66.9785" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="white" stop-opacity="0.6"/>
|
||||||
|
<stop offset="1" stop-color="white" stop-opacity="0"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 3.4 KiB |
|
@ -0,0 +1,28 @@
|
||||||
|
<svg width="118" height="65" viewBox="0 0 118 65" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect width="118" height="65" fill="#765FFE"/>
|
||||||
|
<mask id="path-2-inside-1_4_138" fill="white">
|
||||||
|
<path d="M22 12H94V65H22V12Z"/>
|
||||||
|
</mask>
|
||||||
|
<path d="M22 12H94V65H22V12Z" fill="url(#paint0_linear_4_138)"/>
|
||||||
|
<path d="M22 12V11H21V12H22ZM94 12H95V11H94V12ZM22 13H94V11H22V13ZM93 12V65H95V12H93ZM23 65V12H21V65H23Z" fill="white" fill-opacity="0.2" mask="url(#path-2-inside-1_4_138)"/>
|
||||||
|
<g filter="url(#filter0_d_4_138)">
|
||||||
|
<rect x="42" y="17" width="33" height="44" fill="white" fill-opacity="0.4" shape-rendering="crispEdges"/>
|
||||||
|
<rect x="42.5" y="17.5" width="32" height="43" stroke="white" stroke-opacity="0.3" shape-rendering="crispEdges"/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<filter id="filter0_d_4_138" x="39" y="15" width="39" height="50" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||||
|
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||||
|
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||||
|
<feOffset dy="1"/>
|
||||||
|
<feGaussianBlur stdDeviation="1.5"/>
|
||||||
|
<feComposite in2="hardAlpha" operator="out"/>
|
||||||
|
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.2 0"/>
|
||||||
|
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_4_138"/>
|
||||||
|
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_4_138" result="shape"/>
|
||||||
|
</filter>
|
||||||
|
<linearGradient id="paint0_linear_4_138" x1="22" y1="12" x2="92.4561" y2="66.9785" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="white" stop-opacity="0.6"/>
|
||||||
|
<stop offset="1" stop-color="white" stop-opacity="0"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.6 KiB |
|
@ -0,0 +1,20 @@
|
||||||
|
<svg width="118" height="65" viewBox="0 0 118 65" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect width="118" height="65" fill="#765FFE"/>
|
||||||
|
<rect width="118" height="65" fill="#765FFE"/>
|
||||||
|
<mask id="path-3-inside-1_56_20" fill="white">
|
||||||
|
<path d="M22 12H94V65H22V12Z"/>
|
||||||
|
</mask>
|
||||||
|
<path d="M22 12H94V65H22V12Z" fill="url(#paint0_linear_56_20)"/>
|
||||||
|
<path d="M22 12V11H21V12H22ZM94 12H95V11H94V12ZM22 13H94V11H22V13ZM93 12V65H95V12H93ZM23 65V12H21V65H23Z" fill="white" fill-opacity="0.2" mask="url(#path-3-inside-1_56_20)"/>
|
||||||
|
<rect x="46" y="29" width="15" height="4" fill="white" fill-opacity="0.97"/>
|
||||||
|
<rect x="46" y="45" width="15" height="4" fill="white" fill-opacity="0.5"/>
|
||||||
|
<rect x="46" y="35" width="25" height="6" fill="white" fill-opacity="0.97"/>
|
||||||
|
<path d="M46 21H71V25H46V21Z" fill="white" fill-opacity="0.5"/>
|
||||||
|
<rect x="46" y="51" width="25" height="6" fill="white" fill-opacity="0.5"/>
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="paint0_linear_56_20" x1="22" y1="12" x2="92.4561" y2="66.9785" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="white" stop-opacity="0.6"/>
|
||||||
|
<stop offset="1" stop-color="white" stop-opacity="0"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
|
@ -0,0 +1,28 @@
|
||||||
|
<svg width="118" height="65" viewBox="0 0 118 65" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect width="118" height="65" fill="#765FFE"/>
|
||||||
|
<mask id="path-2-inside-1_4_103" fill="white">
|
||||||
|
<path d="M22 12H94V65H22V12Z"/>
|
||||||
|
</mask>
|
||||||
|
<path d="M22 12H94V65H22V12Z" fill="url(#paint0_linear_4_103)"/>
|
||||||
|
<path d="M22 12V11H21V12H22ZM94 12H95V11H94V12ZM22 13H94V11H22V13ZM93 12V65H95V12H93ZM23 65V12H21V65H23Z" fill="white" fill-opacity="0.2" mask="url(#path-2-inside-1_4_103)"/>
|
||||||
|
<g filter="url(#filter0_d_4_103)">
|
||||||
|
<rect x="70" y="17" width="20" height="44" fill="white" fill-opacity="0.4" shape-rendering="crispEdges"/>
|
||||||
|
<rect x="70.5" y="17.5" width="19" height="43" stroke="white" stroke-opacity="0.3" shape-rendering="crispEdges"/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<filter id="filter0_d_4_103" x="67" y="15" width="26" height="50" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||||
|
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||||
|
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||||
|
<feOffset dy="1"/>
|
||||||
|
<feGaussianBlur stdDeviation="1.5"/>
|
||||||
|
<feComposite in2="hardAlpha" operator="out"/>
|
||||||
|
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"/>
|
||||||
|
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_4_103"/>
|
||||||
|
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_4_103" result="shape"/>
|
||||||
|
</filter>
|
||||||
|
<linearGradient id="paint0_linear_4_103" x1="22" y1="12" x2="92.4561" y2="66.9785" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="white" stop-opacity="0.6"/>
|
||||||
|
<stop offset="1" stop-color="white" stop-opacity="0"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.6 KiB |
|
@ -1,10 +1,9 @@
|
||||||
<script>
|
<script>
|
||||||
import { Body } from "@budibase/bbui"
|
import { Body } from "@budibase/bbui"
|
||||||
import CreationPage from "components/common/CreationPage.svelte"
|
import CreationPage from "components/common/CreationPage.svelte"
|
||||||
import blankImage from "./images/blank.png"
|
import blank from "./images/blank.svg"
|
||||||
import tableInline from "./images/tableInline.png"
|
import table from "./images/tableInline.svg"
|
||||||
import tableDetails from "./images/tableDetails.png"
|
import form from "./images/formUpdate.svg"
|
||||||
import formImage from "./images/form.png"
|
|
||||||
import CreateScreenModal from "./CreateScreenModal.svelte"
|
import CreateScreenModal from "./CreateScreenModal.svelte"
|
||||||
import { screenStore } from "stores/builder"
|
import { screenStore } from "stores/builder"
|
||||||
|
|
||||||
|
@ -30,37 +29,27 @@
|
||||||
<div class="cards">
|
<div class="cards">
|
||||||
<div class="card" on:click={() => createScreenModal.show("blank")}>
|
<div class="card" on:click={() => createScreenModal.show("blank")}>
|
||||||
<div class="image">
|
<div class="image">
|
||||||
<img alt="" src={blankImage} />
|
<img alt="A blank screen" src={blank} />
|
||||||
</div>
|
</div>
|
||||||
<div class="text">
|
<div class="text">
|
||||||
<Body size="S">Blank screen</Body>
|
<Body size="S">Blank</Body>
|
||||||
<Body size="XS">Add an empty blank screen</Body>
|
<Body size="XS">Add an empty blank screen</Body>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card" on:click={() => createScreenModal.show("grid")}>
|
<div class="card" on:click={() => createScreenModal.show("table")}>
|
||||||
<div class="image">
|
<div class="image">
|
||||||
<img alt="" src={tableInline} />
|
<img alt="A table of data" src={table} />
|
||||||
</div>
|
</div>
|
||||||
<div class="text">
|
<div class="text">
|
||||||
<Body size="S">Table with inline editing</Body>
|
<Body size="S">Table</Body>
|
||||||
<Body size="XS">View, edit and delete rows inline</Body>
|
<Body size="XS">List rows in a table</Body>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card" on:click={() => createScreenModal.show("gridDetails")}>
|
|
||||||
<div class="image">
|
|
||||||
<img alt="" src={tableDetails} />
|
|
||||||
</div>
|
|
||||||
<div class="text">
|
|
||||||
<Body size="S">Table with details panel</Body>
|
|
||||||
<Body size="XS">Manage your row details in a side panel</Body>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card" on:click={() => createScreenModal.show("form")}>
|
<div class="card" on:click={() => createScreenModal.show("form")}>
|
||||||
<div class="image">
|
<div class="image">
|
||||||
<img alt="" src={formImage} />
|
<img alt="A form containing data" src={form} />
|
||||||
</div>
|
</div>
|
||||||
<div class="text">
|
<div class="text">
|
||||||
<Body size="S">Form</Body>
|
<Body size="S">Form</Body>
|
||||||
|
@ -114,8 +103,9 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.card .image {
|
.card .image {
|
||||||
min-height: 130px;
|
|
||||||
min-width: 235px;
|
min-width: 235px;
|
||||||
|
height: 127px;
|
||||||
|
background-color: var(--grey-2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.text {
|
.text {
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
import tableInline from "./images/tableInline.svg"
|
||||||
|
import tableSidePanel from "./images/tableSidePanel.svg"
|
||||||
|
import tableModal from "./images/tableModal.svg"
|
||||||
|
import tableNewScreen from "./images/tableNewScreen.svg"
|
||||||
|
|
||||||
|
const tableTypes = [
|
||||||
|
{
|
||||||
|
id: "inline",
|
||||||
|
img: {
|
||||||
|
alt: "A table of data",
|
||||||
|
src: tableInline,
|
||||||
|
},
|
||||||
|
title: "Inline",
|
||||||
|
description: "Manage data directly on your table",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "sidePanel",
|
||||||
|
img: {
|
||||||
|
alt: "A side panel",
|
||||||
|
src: tableSidePanel,
|
||||||
|
},
|
||||||
|
title: "Side panel",
|
||||||
|
description: "Open row details in a side panel",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "modal",
|
||||||
|
img: {
|
||||||
|
alt: "A modal",
|
||||||
|
src: tableModal,
|
||||||
|
},
|
||||||
|
title: "Modal",
|
||||||
|
description: "Open row details in a modal",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "newScreen",
|
||||||
|
img: {
|
||||||
|
alt: "A new screen",
|
||||||
|
src: tableNewScreen,
|
||||||
|
},
|
||||||
|
title: "New screen",
|
||||||
|
description: "View row details on a separate screen",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
export default tableTypes
|
|
@ -1,6 +1,6 @@
|
||||||
import { v4 } from "uuid"
|
import { v4 } from "uuid"
|
||||||
import { Component } from "templates/Component"
|
import { Component } from "templates/Component"
|
||||||
import { Screen } from "templates/Screen"
|
import { Screen } from "templates/screenTemplating/Screen"
|
||||||
import { get } from "svelte/store"
|
import { get } from "svelte/store"
|
||||||
import {
|
import {
|
||||||
BUDIBASE_INTERNAL_DB_ID,
|
BUDIBASE_INTERNAL_DB_ID,
|
||||||
|
|
|
@ -2,11 +2,11 @@ import { Helpers } from "@budibase/bbui"
|
||||||
import { BaseStructure } from "./BaseStructure"
|
import { BaseStructure } from "./BaseStructure"
|
||||||
|
|
||||||
export class Component extends BaseStructure {
|
export class Component extends BaseStructure {
|
||||||
constructor(name) {
|
constructor(name, _id = Helpers.uuid()) {
|
||||||
super(false)
|
super(false)
|
||||||
this._children = []
|
this._children = []
|
||||||
this._json = {
|
this._json = {
|
||||||
_id: Helpers.uuid(),
|
_id,
|
||||||
_component: name,
|
_component: name,
|
||||||
_styles: {
|
_styles: {
|
||||||
normal: {},
|
normal: {},
|
||||||
|
@ -50,4 +50,8 @@ export class Component extends BaseStructure {
|
||||||
this._json.text = text
|
this._json.text = text
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getId() {
|
||||||
|
return this._json._id
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
import { Screen } from "./Screen"
|
|
||||||
|
|
||||||
const blankScreen = route => {
|
|
||||||
return new Screen().instanceName("New Screen").route(route).json()
|
|
||||||
}
|
|
||||||
|
|
||||||
export default blankScreen
|
|
|
@ -1,49 +0,0 @@
|
||||||
import { Screen } from "./Screen"
|
|
||||||
import { Component } from "./Component"
|
|
||||||
import sanitizeUrl from "helpers/sanitizeUrl"
|
|
||||||
|
|
||||||
export const FORM_TEMPLATE = "FORM_TEMPLATE"
|
|
||||||
export const formUrl = (tableOrView, actionType) => {
|
|
||||||
if (actionType === "Create") {
|
|
||||||
return sanitizeUrl(`/${tableOrView.name}/new`)
|
|
||||||
} else if (actionType === "Update") {
|
|
||||||
return sanitizeUrl(`/${tableOrView.name}/edit/:id`)
|
|
||||||
} else if (actionType === "View") {
|
|
||||||
return sanitizeUrl(`/${tableOrView.name}/view/:id`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getRole = (permissions, actionType) => {
|
|
||||||
if (actionType === "View") {
|
|
||||||
return permissions.read
|
|
||||||
}
|
|
||||||
|
|
||||||
return permissions.write
|
|
||||||
}
|
|
||||||
|
|
||||||
const generateMultistepFormBlock = (tableOrView, actionType) => {
|
|
||||||
const multistepFormBlock = new Component(
|
|
||||||
"@budibase/standard-components/multistepformblock"
|
|
||||||
)
|
|
||||||
multistepFormBlock
|
|
||||||
.customProps({
|
|
||||||
actionType,
|
|
||||||
dataSource: tableOrView.clientData,
|
|
||||||
steps: [{}],
|
|
||||||
rowId: actionType === "new" ? undefined : `{{ url.id }}`,
|
|
||||||
})
|
|
||||||
.instanceName(`${tableOrView.name} - Multistep Form block`)
|
|
||||||
return multistepFormBlock
|
|
||||||
}
|
|
||||||
|
|
||||||
const createScreen = (tableOrView, actionType, permissions) => {
|
|
||||||
return new Screen()
|
|
||||||
.route(formUrl(tableOrView, actionType))
|
|
||||||
.instanceName(`${tableOrView.name} - Form`)
|
|
||||||
.role(getRole(permissions, actionType))
|
|
||||||
.autoTableId(tableOrView.id)
|
|
||||||
.addChild(generateMultistepFormBlock(tableOrView, actionType))
|
|
||||||
.json()
|
|
||||||
}
|
|
||||||
|
|
||||||
export default createScreen
|
|
|
@ -1,30 +0,0 @@
|
||||||
import sanitizeUrl from "helpers/sanitizeUrl"
|
|
||||||
import { Screen } from "./Screen"
|
|
||||||
import { Component } from "./Component"
|
|
||||||
|
|
||||||
const gridUrl = tableOrView => sanitizeUrl(`/${tableOrView.name}`)
|
|
||||||
|
|
||||||
const createScreen = (tableOrView, permissions) => {
|
|
||||||
const heading = new Component("@budibase/standard-components/heading")
|
|
||||||
.instanceName("Table heading")
|
|
||||||
.customProps({
|
|
||||||
text: tableOrView.name,
|
|
||||||
})
|
|
||||||
|
|
||||||
const gridBlock = new Component("@budibase/standard-components/gridblock")
|
|
||||||
.instanceName(`${tableOrView.name} - Table`)
|
|
||||||
.customProps({
|
|
||||||
table: tableOrView.clientData,
|
|
||||||
})
|
|
||||||
|
|
||||||
return new Screen()
|
|
||||||
.route(gridUrl(tableOrView))
|
|
||||||
.instanceName(`${tableOrView.name} - List`)
|
|
||||||
.role(permissions.write)
|
|
||||||
.autoTableId(tableOrView.id)
|
|
||||||
.addChild(heading)
|
|
||||||
.addChild(gridBlock)
|
|
||||||
.json()
|
|
||||||
}
|
|
||||||
|
|
||||||
export default createScreen
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { BaseStructure } from "./BaseStructure"
|
import { BaseStructure } from "../BaseStructure"
|
||||||
import { Helpers } from "@budibase/bbui"
|
import { Helpers } from "@budibase/bbui"
|
||||||
|
|
||||||
export class Screen extends BaseStructure {
|
export class Screen extends BaseStructure {
|
|
@ -0,0 +1,24 @@
|
||||||
|
import { Screen } from "./Screen"
|
||||||
|
import { capitalise } from "helpers"
|
||||||
|
import getValidRoute from "./getValidRoute"
|
||||||
|
import { Roles } from "constants/backend"
|
||||||
|
|
||||||
|
const blank = ({ route, screens }) => {
|
||||||
|
const validRoute = getValidRoute(screens, route, Roles.BASIC)
|
||||||
|
|
||||||
|
const template = new Screen()
|
||||||
|
.instanceName("Blank screen")
|
||||||
|
.role(Roles.BASIC)
|
||||||
|
.route(validRoute)
|
||||||
|
.json()
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
data: template,
|
||||||
|
navigationLinkLabel:
|
||||||
|
validRoute === "/" ? null : capitalise(validRoute.split("/")[1]),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
export default blank
|
|
@ -0,0 +1,67 @@
|
||||||
|
import { Screen } from "./Screen"
|
||||||
|
import { Component } from "../Component"
|
||||||
|
import getValidRoute from "./getValidRoute"
|
||||||
|
|
||||||
|
export const getTypeSpecificRoute = (tableOrView, type) => {
|
||||||
|
if (type === "create") {
|
||||||
|
return `/${tableOrView.name}/new`
|
||||||
|
} else if (type === "update") {
|
||||||
|
return `/${tableOrView.name}/edit/:id`
|
||||||
|
} else if (type === "view") {
|
||||||
|
return `/${tableOrView.name}/view/:id`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getRole = (permissions, type) => {
|
||||||
|
if (type === "view") {
|
||||||
|
return permissions.read
|
||||||
|
}
|
||||||
|
|
||||||
|
return permissions.write
|
||||||
|
}
|
||||||
|
|
||||||
|
const getActionType = type => {
|
||||||
|
if (type === "create") {
|
||||||
|
return "Create"
|
||||||
|
}
|
||||||
|
if (type === "update") {
|
||||||
|
return "Update"
|
||||||
|
}
|
||||||
|
if (type === "view") {
|
||||||
|
return "View"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const form = ({ tableOrView, type, permissions, screens }) => {
|
||||||
|
const typeSpecificRoute = getTypeSpecificRoute(tableOrView, type)
|
||||||
|
const role = getRole(permissions, type)
|
||||||
|
|
||||||
|
const multistepFormBlock = new Component(
|
||||||
|
"@budibase/standard-components/multistepformblock"
|
||||||
|
)
|
||||||
|
.customProps({
|
||||||
|
actionType: getActionType(type),
|
||||||
|
dataSource: tableOrView.tableSelectFormat,
|
||||||
|
steps: [{}],
|
||||||
|
rowId: type === "new" ? undefined : `{{ url.id }}`,
|
||||||
|
})
|
||||||
|
.instanceName(`${tableOrView.name} - Multistep Form block`)
|
||||||
|
|
||||||
|
const template = new Screen()
|
||||||
|
.route(getValidRoute(screens, typeSpecificRoute, role))
|
||||||
|
.instanceName(`${tableOrView.name} - Form`)
|
||||||
|
.role(role)
|
||||||
|
.autoTableId(tableOrView.id)
|
||||||
|
.addChild(multistepFormBlock)
|
||||||
|
.json()
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
data: template,
|
||||||
|
navigationLinkLabel:
|
||||||
|
type === "create" ? `Create ${tableOrView.name}` : null,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
export default form
|
|
@ -0,0 +1,35 @@
|
||||||
|
import sanitizeUrl from "helpers/sanitizeUrl"
|
||||||
|
|
||||||
|
const arbitraryMax = 10000
|
||||||
|
|
||||||
|
const isScreenUrlValid = (screens, url, role) => {
|
||||||
|
return !screens.some(
|
||||||
|
screen => screen.routing?.route === url && screen.routing?.roleId === role
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const getValidScreenUrl = (screens, url, role) => {
|
||||||
|
const [firstPathSegment = "", ...restPathSegments] = url
|
||||||
|
.split("/")
|
||||||
|
.filter(segment => segment !== "")
|
||||||
|
|
||||||
|
const restOfPath =
|
||||||
|
restPathSegments.length > 0 ? `/${restPathSegments.join("/")}` : ""
|
||||||
|
|
||||||
|
const naiveUrl = sanitizeUrl(`/${firstPathSegment}${restOfPath}`)
|
||||||
|
if (isScreenUrlValid(screens, naiveUrl, role)) {
|
||||||
|
return naiveUrl
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let suffix = 2; suffix < arbitraryMax; suffix++) {
|
||||||
|
const suffixedUrl = sanitizeUrl(
|
||||||
|
`/${firstPathSegment}-${suffix}${restOfPath}`
|
||||||
|
)
|
||||||
|
|
||||||
|
if (isScreenUrlValid(screens, suffixedUrl, role)) {
|
||||||
|
return suffixedUrl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default getValidScreenUrl
|
|
@ -0,0 +1,3 @@
|
||||||
|
export { default as blank } from "./blank"
|
||||||
|
export { default as form } from "./form"
|
||||||
|
export { default as table } from "./table"
|
|
@ -0,0 +1,25 @@
|
||||||
|
import inline from "./inline"
|
||||||
|
import modal from "./modal"
|
||||||
|
import sidePanel from "./sidePanel"
|
||||||
|
import newScreen from "./newScreen"
|
||||||
|
|
||||||
|
const createScreen = ({ tableOrView, type, permissions, screens }) => {
|
||||||
|
if (type === "inline") {
|
||||||
|
return inline({ tableOrView, permissions, screens })
|
||||||
|
}
|
||||||
|
if (type === "modal") {
|
||||||
|
return modal({ tableOrView, permissions, screens })
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === "sidePanel") {
|
||||||
|
return sidePanel({ tableOrView, permissions, screens })
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === "newScreen") {
|
||||||
|
return newScreen({ tableOrView, permissions, screens })
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`Unrecognized table type ${type}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default createScreen
|
|
@ -0,0 +1,36 @@
|
||||||
|
import { Screen } from "../Screen"
|
||||||
|
import { Component } from "../../Component"
|
||||||
|
import { capitalise } from "helpers"
|
||||||
|
import getValidRoute from "../getValidRoute"
|
||||||
|
|
||||||
|
const inline = ({ tableOrView, permissions, screens }) => {
|
||||||
|
const heading = new Component("@budibase/standard-components/heading")
|
||||||
|
.instanceName("Table heading")
|
||||||
|
.customProps({
|
||||||
|
text: tableOrView.name,
|
||||||
|
})
|
||||||
|
|
||||||
|
const tableBlock = new Component("@budibase/standard-components/gridblock")
|
||||||
|
.instanceName(`${tableOrView.name} - Table`)
|
||||||
|
.customProps({
|
||||||
|
table: tableOrView.datasourceSelectFormat,
|
||||||
|
})
|
||||||
|
|
||||||
|
const screenTemplate = new Screen()
|
||||||
|
.route(getValidRoute(screens, tableOrView.name, permissions.write))
|
||||||
|
.instanceName(`${tableOrView.name} - List`)
|
||||||
|
.role(permissions.write)
|
||||||
|
.autoTableId(tableOrView.id)
|
||||||
|
.addChild(heading)
|
||||||
|
.addChild(tableBlock)
|
||||||
|
.json()
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
data: screenTemplate,
|
||||||
|
navigationLinkLabel: capitalise(tableOrView.name),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
export default inline
|
|
@ -0,0 +1,157 @@
|
||||||
|
import { Screen } from "../Screen"
|
||||||
|
import { Component } from "../../Component"
|
||||||
|
import { generate } from "shortid"
|
||||||
|
import { makePropSafe as safe } from "@budibase/string-templates"
|
||||||
|
import { Utils } from "@budibase/frontend-core"
|
||||||
|
import { capitalise } from "helpers"
|
||||||
|
import getValidRoute from "../getValidRoute"
|
||||||
|
|
||||||
|
const modal = ({ tableOrView, permissions, screens }) => {
|
||||||
|
/*
|
||||||
|
Create Row
|
||||||
|
*/
|
||||||
|
const createRowModal = new Component("@budibase/standard-components/modal")
|
||||||
|
.instanceName("New row modal")
|
||||||
|
.customProps({
|
||||||
|
size: "large",
|
||||||
|
})
|
||||||
|
|
||||||
|
const buttonGroup = new Component("@budibase/standard-components/buttongroup")
|
||||||
|
const createButton = new Component("@budibase/standard-components/button")
|
||||||
|
|
||||||
|
createButton.customProps({
|
||||||
|
onClick: [
|
||||||
|
{
|
||||||
|
id: 0,
|
||||||
|
"##eventHandlerType": "Open Modal",
|
||||||
|
parameters: {
|
||||||
|
id: createRowModal._json._id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
text: "Create row",
|
||||||
|
type: "cta",
|
||||||
|
})
|
||||||
|
|
||||||
|
buttonGroup.instanceName(`${tableOrView.name} - Create`).customProps({
|
||||||
|
hAlign: "right",
|
||||||
|
buttons: [createButton.json()],
|
||||||
|
})
|
||||||
|
|
||||||
|
const tableHeader = new Component("@budibase/standard-components/container")
|
||||||
|
.instanceName("Heading container")
|
||||||
|
.customProps({
|
||||||
|
direction: "row",
|
||||||
|
hAlign: "stretch",
|
||||||
|
})
|
||||||
|
|
||||||
|
const heading = new Component("@budibase/standard-components/heading")
|
||||||
|
.instanceName("Table heading")
|
||||||
|
.customProps({
|
||||||
|
text: tableOrView.name,
|
||||||
|
})
|
||||||
|
|
||||||
|
tableHeader.addChild(heading)
|
||||||
|
tableHeader.addChild(buttonGroup)
|
||||||
|
|
||||||
|
const createFormBlock = new Component(
|
||||||
|
"@budibase/standard-components/formblock"
|
||||||
|
)
|
||||||
|
createFormBlock.instanceName("Create row form block").customProps({
|
||||||
|
dataSource: tableOrView.tableSelectFormat,
|
||||||
|
labelPosition: "left",
|
||||||
|
buttonPosition: "top",
|
||||||
|
actionType: "Create",
|
||||||
|
title: "Create row",
|
||||||
|
buttons: Utils.buildFormBlockButtonConfig({
|
||||||
|
_id: createFormBlock._json._id,
|
||||||
|
showDeleteButton: false,
|
||||||
|
showSaveButton: true,
|
||||||
|
saveButtonLabel: "Save",
|
||||||
|
actionType: "Create",
|
||||||
|
dataSource: tableOrView.tableSelectFormat,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
|
createRowModal.addChild(createFormBlock)
|
||||||
|
|
||||||
|
/*
|
||||||
|
Edit Row
|
||||||
|
*/
|
||||||
|
const stateKey = `ID_${generate()}`
|
||||||
|
const detailsModal = new Component("@budibase/standard-components/modal")
|
||||||
|
.instanceName("Edit row modal")
|
||||||
|
.customProps({
|
||||||
|
size: "large",
|
||||||
|
})
|
||||||
|
|
||||||
|
const editFormBlock = new Component("@budibase/standard-components/formblock")
|
||||||
|
editFormBlock.instanceName("Edit row form block").customProps({
|
||||||
|
dataSource: tableOrView.tableSelectFormat,
|
||||||
|
labelPosition: "left",
|
||||||
|
buttonPosition: "top",
|
||||||
|
actionType: "Update",
|
||||||
|
title: "Edit",
|
||||||
|
rowId: `{{ ${safe("state")}.${safe(stateKey)} }}`,
|
||||||
|
buttons: Utils.buildFormBlockButtonConfig({
|
||||||
|
_id: editFormBlock._json._id,
|
||||||
|
showDeleteButton: true,
|
||||||
|
showSaveButton: true,
|
||||||
|
saveButtonLabel: "Save",
|
||||||
|
deleteButtonLabel: "Delete",
|
||||||
|
actionType: "Update",
|
||||||
|
dataSource: tableOrView.tableSelectFormat,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
|
detailsModal.addChild(editFormBlock)
|
||||||
|
|
||||||
|
const tableBlock = new Component("@budibase/standard-components/gridblock")
|
||||||
|
tableBlock
|
||||||
|
.customProps({
|
||||||
|
table: tableOrView.datasourceSelectFormat,
|
||||||
|
allowAddRows: false,
|
||||||
|
allowEditRows: false,
|
||||||
|
allowDeleteRows: false,
|
||||||
|
onRowClick: [
|
||||||
|
{
|
||||||
|
id: 0,
|
||||||
|
"##eventHandlerType": "Update State",
|
||||||
|
parameters: {
|
||||||
|
key: stateKey,
|
||||||
|
type: "set",
|
||||||
|
persist: false,
|
||||||
|
value: `{{ ${safe("eventContext")}.${safe("row")}._id }}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
"##eventHandlerType": "Open Modal",
|
||||||
|
parameters: {
|
||||||
|
id: detailsModal._json._id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
.instanceName(`${tableOrView.name} - Table`)
|
||||||
|
|
||||||
|
const template = new Screen()
|
||||||
|
.route(getValidRoute(screens, tableOrView.name, permissions.write))
|
||||||
|
.instanceName(`${tableOrView.name} - List and details`)
|
||||||
|
.role(permissions.write)
|
||||||
|
.autoTableId(tableOrView.id)
|
||||||
|
.addChild(tableHeader)
|
||||||
|
.addChild(tableBlock)
|
||||||
|
.addChild(createRowModal)
|
||||||
|
.addChild(detailsModal)
|
||||||
|
.json()
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
data: template,
|
||||||
|
navigationLinkLabel: capitalise(tableOrView.name),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
export default modal
|
|
@ -0,0 +1,322 @@
|
||||||
|
import { Screen } from "../Screen"
|
||||||
|
import { Component } from "../../Component"
|
||||||
|
import { capitalise } from "helpers"
|
||||||
|
import { makePropSafe as safe } from "@budibase/string-templates"
|
||||||
|
import getValidRoute from "../getValidRoute"
|
||||||
|
import { Helpers } from "@budibase/bbui"
|
||||||
|
|
||||||
|
const getTableScreenTemplate = ({
|
||||||
|
route,
|
||||||
|
updateScreenRoute,
|
||||||
|
createScreenRoute,
|
||||||
|
tableOrView,
|
||||||
|
permissions,
|
||||||
|
}) => {
|
||||||
|
const newButton = new Component("@budibase/standard-components/button")
|
||||||
|
.instanceName("New button")
|
||||||
|
.customProps({
|
||||||
|
text: "Create row",
|
||||||
|
onClick: [
|
||||||
|
{
|
||||||
|
"##eventHandlerType": "Navigate To",
|
||||||
|
parameters: {
|
||||||
|
type: "url",
|
||||||
|
url: createScreenRoute,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
const heading = new Component("@budibase/standard-components/heading")
|
||||||
|
.instanceName("Table heading")
|
||||||
|
.customProps({
|
||||||
|
text: tableOrView.name,
|
||||||
|
})
|
||||||
|
|
||||||
|
const tableHeader = new Component("@budibase/standard-components/container")
|
||||||
|
.instanceName("Heading container")
|
||||||
|
.customProps({
|
||||||
|
direction: "row",
|
||||||
|
hAlign: "stretch",
|
||||||
|
})
|
||||||
|
.addChild(heading)
|
||||||
|
.addChild(newButton)
|
||||||
|
|
||||||
|
const updateScreenRouteSegments = updateScreenRoute.split(":id")
|
||||||
|
if (updateScreenRouteSegments.length !== 2) {
|
||||||
|
throw new Error("Provided edit screen route is invalid")
|
||||||
|
}
|
||||||
|
|
||||||
|
const tableBlock = new Component("@budibase/standard-components/gridblock")
|
||||||
|
.instanceName(`${tableOrView.name} - Table`)
|
||||||
|
.customProps({
|
||||||
|
table: tableOrView.datasourceSelectFormat,
|
||||||
|
allowAddRows: false,
|
||||||
|
allowEditRows: false,
|
||||||
|
allowDeleteRows: false,
|
||||||
|
onRowClick: [
|
||||||
|
{
|
||||||
|
id: 0,
|
||||||
|
"##eventHandlerType": "Navigate To",
|
||||||
|
parameters: {
|
||||||
|
type: "url",
|
||||||
|
url: `${updateScreenRouteSegments[0]}{{ ${safe(
|
||||||
|
"eventContext"
|
||||||
|
)}.${safe("row")}._id }}${updateScreenRouteSegments[1]}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
const template = new Screen()
|
||||||
|
.route(route)
|
||||||
|
.instanceName(`${tableOrView.name} - List`)
|
||||||
|
.role(permissions.write)
|
||||||
|
.autoTableId(tableOrView.id)
|
||||||
|
.addChild(tableHeader)
|
||||||
|
.addChild(tableBlock)
|
||||||
|
.json()
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: template,
|
||||||
|
navigationLinkLabel: capitalise(tableOrView.name),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getUpdateScreenTemplate = ({
|
||||||
|
route,
|
||||||
|
tableScreenRoute,
|
||||||
|
tableOrView,
|
||||||
|
permissions,
|
||||||
|
}) => {
|
||||||
|
const formBlockId = Helpers.uuid()
|
||||||
|
const formId = `${formBlockId}-form`
|
||||||
|
const repeaterId = `${formBlockId}-repeater`
|
||||||
|
|
||||||
|
const backButton = new Component("@budibase/standard-components/button")
|
||||||
|
.instanceName("Back button")
|
||||||
|
.customProps({
|
||||||
|
type: "primary",
|
||||||
|
icon: "ri-arrow-go-back-fill",
|
||||||
|
text: "Back",
|
||||||
|
onClick: [
|
||||||
|
{
|
||||||
|
"##eventHandlerType": "Navigate To",
|
||||||
|
parameters: {
|
||||||
|
type: "url",
|
||||||
|
url: tableScreenRoute,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
const deleteButton = new Component("@budibase/standard-components/button")
|
||||||
|
.instanceName("Delete button")
|
||||||
|
.customProps({
|
||||||
|
type: "secondary",
|
||||||
|
text: "Delete",
|
||||||
|
onClick: [
|
||||||
|
{
|
||||||
|
"##eventHandlerType": "Delete Row",
|
||||||
|
parameters: {
|
||||||
|
confirm: true,
|
||||||
|
tableId: tableOrView.id,
|
||||||
|
rowId: `{{ ${safe(repeaterId)}.${safe("_id")} }}`,
|
||||||
|
revId: `{{ ${safe(repeaterId)}.${safe("_rev")} }}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"##eventHandlerType": "Navigate To",
|
||||||
|
parameters: {
|
||||||
|
type: "url",
|
||||||
|
url: tableScreenRoute,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
const saveButton = new Component("@budibase/standard-components/button")
|
||||||
|
.instanceName("Save button")
|
||||||
|
.customProps({
|
||||||
|
type: "cta",
|
||||||
|
text: "Save",
|
||||||
|
onClick: [
|
||||||
|
{
|
||||||
|
"##eventHandlerType": "Validate Form",
|
||||||
|
parameters: {
|
||||||
|
componentId: formId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"##eventHandlerType": "Save Row",
|
||||||
|
parameters: {
|
||||||
|
providerId: formId,
|
||||||
|
tableId: tableOrView.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"##eventHandlerType": "Navigate To",
|
||||||
|
parameters: {
|
||||||
|
type: "url",
|
||||||
|
url: tableScreenRoute,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
const updateFormBlock = new Component(
|
||||||
|
"@budibase/standard-components/formblock",
|
||||||
|
formBlockId
|
||||||
|
)
|
||||||
|
.instanceName("Update row form block")
|
||||||
|
.customProps({
|
||||||
|
dataSource: tableOrView.tableSelectFormat,
|
||||||
|
labelPosition: "left",
|
||||||
|
buttonPosition: "top",
|
||||||
|
actionType: "Update",
|
||||||
|
title: `Update ${tableOrView.name} row`,
|
||||||
|
buttons: [backButton.json(), saveButton.json(), deleteButton.json()],
|
||||||
|
})
|
||||||
|
|
||||||
|
const template = new Screen()
|
||||||
|
.route(route)
|
||||||
|
.instanceName(`Update row`)
|
||||||
|
.role(permissions.write)
|
||||||
|
.autoTableId(tableOrView.id)
|
||||||
|
.addChild(updateFormBlock)
|
||||||
|
.json()
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: template,
|
||||||
|
navigationLinkLabel: null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getCreateScreenTemplate = ({
|
||||||
|
route,
|
||||||
|
tableScreenRoute,
|
||||||
|
tableOrView,
|
||||||
|
permissions,
|
||||||
|
}) => {
|
||||||
|
const formBlockId = Helpers.uuid()
|
||||||
|
const formId = `${formBlockId}-form`
|
||||||
|
|
||||||
|
const backButton = new Component("@budibase/standard-components/button")
|
||||||
|
.instanceName("Back button")
|
||||||
|
.customProps({
|
||||||
|
type: "primary",
|
||||||
|
icon: "ri-arrow-go-back-fill",
|
||||||
|
text: "Back",
|
||||||
|
onClick: [
|
||||||
|
{
|
||||||
|
"##eventHandlerType": "Navigate To",
|
||||||
|
parameters: {
|
||||||
|
type: "url",
|
||||||
|
url: tableScreenRoute,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
const saveButton = new Component("@budibase/standard-components/button")
|
||||||
|
.instanceName("Save button")
|
||||||
|
.customProps({
|
||||||
|
type: "cta",
|
||||||
|
text: "Save",
|
||||||
|
onClick: [
|
||||||
|
{
|
||||||
|
"##eventHandlerType": "Validate Form",
|
||||||
|
parameters: {
|
||||||
|
componentId: formId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"##eventHandlerType": "Save Row",
|
||||||
|
parameters: {
|
||||||
|
providerId: formId,
|
||||||
|
tableId: tableOrView.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"##eventHandlerType": "Navigate To",
|
||||||
|
parameters: {
|
||||||
|
type: "url",
|
||||||
|
url: tableScreenRoute,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
const createFormBlock = new Component(
|
||||||
|
"@budibase/standard-components/formblock",
|
||||||
|
formBlockId
|
||||||
|
)
|
||||||
|
.instanceName("Create row form block")
|
||||||
|
.customProps({
|
||||||
|
dataSource: tableOrView.tableSelectFormat,
|
||||||
|
labelPosition: "left",
|
||||||
|
buttonPosition: "top",
|
||||||
|
actionType: "Create",
|
||||||
|
title: `Create ${tableOrView.name} row`,
|
||||||
|
buttons: [backButton.json(), saveButton.json()],
|
||||||
|
})
|
||||||
|
|
||||||
|
const template = new Screen()
|
||||||
|
.route(route)
|
||||||
|
.instanceName("Create row")
|
||||||
|
.role(permissions.write)
|
||||||
|
.autoTableId(tableOrView.id)
|
||||||
|
.addChild(createFormBlock)
|
||||||
|
.json()
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: template,
|
||||||
|
navigationLinkLabel: null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const newScreen = ({ tableOrView, permissions, screens }) => {
|
||||||
|
const tableScreenRoute = getValidRoute(
|
||||||
|
screens,
|
||||||
|
tableOrView.name,
|
||||||
|
permissions.write
|
||||||
|
)
|
||||||
|
|
||||||
|
const updateScreenRoute = getValidRoute(
|
||||||
|
screens,
|
||||||
|
`/${tableOrView.name}/edit/:id`,
|
||||||
|
permissions.write
|
||||||
|
)
|
||||||
|
|
||||||
|
const createScreenRoute = getValidRoute(
|
||||||
|
screens,
|
||||||
|
`/${tableOrView.name}/new`,
|
||||||
|
permissions.write
|
||||||
|
)
|
||||||
|
|
||||||
|
const tableScreenTemplate = getTableScreenTemplate({
|
||||||
|
route: tableScreenRoute,
|
||||||
|
updateScreenRoute,
|
||||||
|
createScreenRoute,
|
||||||
|
permissions,
|
||||||
|
tableOrView,
|
||||||
|
})
|
||||||
|
|
||||||
|
const updateScreenTemplate = getUpdateScreenTemplate({
|
||||||
|
route: updateScreenRoute,
|
||||||
|
tableScreenRoute,
|
||||||
|
tableOrView,
|
||||||
|
permissions,
|
||||||
|
})
|
||||||
|
|
||||||
|
const createScreenTemplate = getCreateScreenTemplate({
|
||||||
|
route: createScreenRoute,
|
||||||
|
tableScreenRoute,
|
||||||
|
tableOrView,
|
||||||
|
permissions,
|
||||||
|
})
|
||||||
|
|
||||||
|
return [tableScreenTemplate, updateScreenTemplate, createScreenTemplate]
|
||||||
|
}
|
||||||
|
|
||||||
|
export default newScreen
|
|
@ -1,13 +1,12 @@
|
||||||
import sanitizeUrl from "helpers/sanitizeUrl"
|
import { Screen } from "../Screen"
|
||||||
import { Screen } from "./Screen"
|
import { Component } from "../../Component"
|
||||||
import { Component } from "./Component"
|
|
||||||
import { generate } from "shortid"
|
import { generate } from "shortid"
|
||||||
import { makePropSafe as safe } from "@budibase/string-templates"
|
import { makePropSafe as safe } from "@budibase/string-templates"
|
||||||
import { Utils } from "@budibase/frontend-core"
|
import { Utils } from "@budibase/frontend-core"
|
||||||
|
import { capitalise } from "helpers"
|
||||||
|
import getValidRoute from "../getValidRoute"
|
||||||
|
|
||||||
const gridDetailsUrl = tableOrView => sanitizeUrl(`/${tableOrView.name}`)
|
const sidePanel = ({ tableOrView, permissions, screens }) => {
|
||||||
|
|
||||||
const createScreen = (tableOrView, permissions) => {
|
|
||||||
/*
|
/*
|
||||||
Create Row
|
Create Row
|
||||||
*/
|
*/
|
||||||
|
@ -37,7 +36,7 @@ const createScreen = (tableOrView, permissions) => {
|
||||||
buttons: [createButton.json()],
|
buttons: [createButton.json()],
|
||||||
})
|
})
|
||||||
|
|
||||||
const gridHeader = new Component("@budibase/standard-components/container")
|
const tableHeader = new Component("@budibase/standard-components/container")
|
||||||
.instanceName("Heading container")
|
.instanceName("Heading container")
|
||||||
.customProps({
|
.customProps({
|
||||||
direction: "row",
|
direction: "row",
|
||||||
|
@ -50,14 +49,14 @@ const createScreen = (tableOrView, permissions) => {
|
||||||
text: tableOrView.name,
|
text: tableOrView.name,
|
||||||
})
|
})
|
||||||
|
|
||||||
gridHeader.addChild(heading)
|
tableHeader.addChild(heading)
|
||||||
gridHeader.addChild(buttonGroup)
|
tableHeader.addChild(buttonGroup)
|
||||||
|
|
||||||
const createFormBlock = new Component(
|
const createFormBlock = new Component(
|
||||||
"@budibase/standard-components/formblock"
|
"@budibase/standard-components/formblock"
|
||||||
)
|
)
|
||||||
createFormBlock.instanceName("Create row form block").customProps({
|
createFormBlock.instanceName("Create row form block").customProps({
|
||||||
dataSource: tableOrView.clientData,
|
dataSource: tableOrView.tableSelectFormat,
|
||||||
labelPosition: "left",
|
labelPosition: "left",
|
||||||
buttonPosition: "top",
|
buttonPosition: "top",
|
||||||
actionType: "Create",
|
actionType: "Create",
|
||||||
|
@ -68,7 +67,7 @@ const createScreen = (tableOrView, permissions) => {
|
||||||
showSaveButton: true,
|
showSaveButton: true,
|
||||||
saveButtonLabel: "Save",
|
saveButtonLabel: "Save",
|
||||||
actionType: "Create",
|
actionType: "Create",
|
||||||
dataSource: tableOrView.clientData,
|
dataSource: tableOrView.tableSelectFormat,
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -84,7 +83,7 @@ const createScreen = (tableOrView, permissions) => {
|
||||||
|
|
||||||
const editFormBlock = new Component("@budibase/standard-components/formblock")
|
const editFormBlock = new Component("@budibase/standard-components/formblock")
|
||||||
editFormBlock.instanceName("Edit row form block").customProps({
|
editFormBlock.instanceName("Edit row form block").customProps({
|
||||||
dataSource: tableOrView.clientData,
|
dataSource: tableOrView.tableSelectFormat,
|
||||||
labelPosition: "left",
|
labelPosition: "left",
|
||||||
buttonPosition: "top",
|
buttonPosition: "top",
|
||||||
actionType: "Update",
|
actionType: "Update",
|
||||||
|
@ -97,16 +96,16 @@ const createScreen = (tableOrView, permissions) => {
|
||||||
saveButtonLabel: "Save",
|
saveButtonLabel: "Save",
|
||||||
deleteButtonLabel: "Delete",
|
deleteButtonLabel: "Delete",
|
||||||
actionType: "Update",
|
actionType: "Update",
|
||||||
dataSource: tableOrView.clientData,
|
dataSource: tableOrView.tableSelectFormat,
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
|
|
||||||
detailsSidePanel.addChild(editFormBlock)
|
detailsSidePanel.addChild(editFormBlock)
|
||||||
|
|
||||||
const gridBlock = new Component("@budibase/standard-components/gridblock")
|
const tableBlock = new Component("@budibase/standard-components/gridblock")
|
||||||
gridBlock
|
tableBlock
|
||||||
.customProps({
|
.customProps({
|
||||||
table: tableOrView.clientData,
|
table: tableOrView.datasourceSelectFormat,
|
||||||
allowAddRows: false,
|
allowAddRows: false,
|
||||||
allowEditRows: false,
|
allowEditRows: false,
|
||||||
allowDeleteRows: false,
|
allowDeleteRows: false,
|
||||||
|
@ -132,16 +131,23 @@ const createScreen = (tableOrView, permissions) => {
|
||||||
})
|
})
|
||||||
.instanceName(`${tableOrView.name} - Table`)
|
.instanceName(`${tableOrView.name} - Table`)
|
||||||
|
|
||||||
return new Screen()
|
const template = new Screen()
|
||||||
.route(gridDetailsUrl(tableOrView))
|
.route(getValidRoute(screens, tableOrView.name, permissions.write))
|
||||||
.instanceName(`${tableOrView.name} - List and details`)
|
.instanceName(`${tableOrView.name} - List and details`)
|
||||||
.role(permissions.write)
|
.role(permissions.write)
|
||||||
.autoTableId(tableOrView.resourceId)
|
.autoTableId(tableOrView.id)
|
||||||
.addChild(gridHeader)
|
.addChild(tableHeader)
|
||||||
.addChild(gridBlock)
|
.addChild(tableBlock)
|
||||||
.addChild(createRowSidePanel)
|
.addChild(createRowSidePanel)
|
||||||
.addChild(detailsSidePanel)
|
.addChild(detailsSidePanel)
|
||||||
.json()
|
.json()
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
data: template,
|
||||||
|
navigationLinkLabel: capitalise(tableOrView.name),
|
||||||
|
},
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
export default createScreen
|
export default sidePanel
|
|
@ -155,6 +155,7 @@ export const buildFormBlockButtonConfig = props => {
|
||||||
providerId: formId,
|
providerId: formId,
|
||||||
tableId: resourceId,
|
tableId: resourceId,
|
||||||
notificationOverride,
|
notificationOverride,
|
||||||
|
confirm: null,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|