[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 onCancel
|
||||
export let screenUrl
|
||||
export let screenRole
|
||||
export let route
|
||||
export let role
|
||||
export let confirmText = "Continue"
|
||||
|
||||
const appPrefix = "/app"
|
||||
|
@ -15,17 +15,17 @@
|
|||
let error
|
||||
let modal
|
||||
|
||||
$: appUrl = screenUrl
|
||||
? `${window.location.origin}${appPrefix}${screenUrl}`
|
||||
$: appUrl = route
|
||||
? `${window.location.origin}${appPrefix}${route}`
|
||||
: `${window.location.origin}${appPrefix}`
|
||||
|
||||
const routeChanged = event => {
|
||||
if (!event.detail.startsWith("/")) {
|
||||
screenUrl = "/" + event.detail
|
||||
route = "/" + event.detail
|
||||
}
|
||||
touched = true
|
||||
screenUrl = sanitizeUrl(screenUrl)
|
||||
if (routeExists(screenUrl)) {
|
||||
route = sanitizeUrl(route)
|
||||
if (routeExists(route)) {
|
||||
error = "This URL is already taken for this access role"
|
||||
} else {
|
||||
error = null
|
||||
|
@ -33,19 +33,19 @@
|
|||
}
|
||||
|
||||
const routeExists = url => {
|
||||
if (!screenRole) {
|
||||
if (!role) {
|
||||
return false
|
||||
}
|
||||
return get(screenStore).screens.some(
|
||||
screen =>
|
||||
screen.routing.route.toLowerCase() === url.toLowerCase() &&
|
||||
screen.routing.roleId === screenRole
|
||||
screen.routing.roleId === role
|
||||
)
|
||||
}
|
||||
|
||||
const confirmScreenDetails = async () => {
|
||||
await onConfirm({
|
||||
screenUrl,
|
||||
route,
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
@ -58,13 +58,13 @@
|
|||
onConfirm={confirmScreenDetails}
|
||||
{onCancel}
|
||||
cancelText={"Back"}
|
||||
disabled={!screenUrl || error || !touched}
|
||||
disabled={!route || error || !touched}
|
||||
>
|
||||
<form on:submit|preventDefault={() => modal.confirm()}>
|
||||
<Input
|
||||
label="Enter a URL for the new screen"
|
||||
{error}
|
||||
bind:value={screenUrl}
|
||||
bind:value={route}
|
||||
on:change={routeChanged}
|
||||
/>
|
||||
<div class="app-server" title={appUrl}>
|
||||
|
|
|
@ -34,6 +34,7 @@
|
|||
import ClientBindingPanel from "components/common/bindings/ClientBindingPanel.svelte"
|
||||
import DataSourceCategory from "components/design/settings/controls/DataSourceSelect/DataSourceCategory.svelte"
|
||||
import { API } from "api"
|
||||
import { datasourceSelect as format } from "helpers/data/format"
|
||||
|
||||
export let value = {}
|
||||
export let otherSources
|
||||
|
@ -51,24 +52,15 @@
|
|||
let modal
|
||||
|
||||
$: text = value?.label ?? "Choose an option"
|
||||
$: tables = $tablesStore.list.map(m => ({
|
||||
label: m.name,
|
||||
tableId: m._id,
|
||||
type: "table",
|
||||
datasource: $datasources.list.find(
|
||||
ds => ds._id === m.sourceId || m.datasourceId
|
||||
),
|
||||
}))
|
||||
$: tables = $tablesStore.list.map(table =>
|
||||
format.table(table, $datasources.list)
|
||||
)
|
||||
$: viewsV1 = $viewsStore.list.map(view => ({
|
||||
...view,
|
||||
label: view.name,
|
||||
type: "view",
|
||||
}))
|
||||
$: viewsV2 = $viewsV2Store.list.map(view => ({
|
||||
...view,
|
||||
label: view.name,
|
||||
type: "viewV2",
|
||||
}))
|
||||
$: viewsV2 = $viewsV2Store.list.map(format.viewV2)
|
||||
$: views = [...(viewsV1 || []), ...(viewsV2 || [])]
|
||||
$: queries = $queriesStore.list
|
||||
.filter(q => showAllQueries || q.queryVerb === "read" || q.readable)
|
||||
|
|
|
@ -2,24 +2,14 @@
|
|||
import { Select } from "@budibase/bbui"
|
||||
import { createEventDispatcher, onMount } from "svelte"
|
||||
import { tables as tablesStore, viewsV2 } from "stores/builder"
|
||||
import { tableSelect as format } from "helpers/data/format"
|
||||
|
||||
export let value
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
$: tables = $tablesStore.list.map(table => ({
|
||||
type: "table",
|
||||
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,
|
||||
}))
|
||||
$: tables = $tablesStore.list.map(format.table)
|
||||
$: views = $viewsV2.list.map(format.viewV2)
|
||||
$: options = [...(tables || []), ...(views || [])]
|
||||
|
||||
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 screenDetailsModal
|
||||
|
||||
const createDuplicateScreen = async ({ screenName, screenUrl }) => {
|
||||
const createDuplicateScreen = async ({ route }) => {
|
||||
// Create a dupe and ensure it is unique
|
||||
let duplicateScreen = Helpers.cloneDeep(screen)
|
||||
delete duplicateScreen._id
|
||||
|
@ -28,9 +28,8 @@
|
|||
duplicateScreen.props = makeComponentUnique(duplicateScreen.props)
|
||||
|
||||
// Attach the new name and URL
|
||||
duplicateScreen.routing.route = sanitizeUrl(screenUrl)
|
||||
duplicateScreen.routing.route = sanitizeUrl(route)
|
||||
duplicateScreen.routing.homeScreen = false
|
||||
duplicateScreen.props._instanceName = screenName
|
||||
|
||||
try {
|
||||
// Create the screen
|
||||
|
@ -136,8 +135,8 @@
|
|||
<Modal bind:this={screenDetailsModal}>
|
||||
<ScreenDetailsModal
|
||||
onConfirm={createDuplicateScreen}
|
||||
screenUrl={screen?.routing.route}
|
||||
screenRole={screen?.routing.roleId}
|
||||
route={screen?.routing.route}
|
||||
role={screen?.routing.roleId}
|
||||
confirmText="Duplicate"
|
||||
/>
|
||||
</Modal>
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
<script>
|
||||
import ScreenDetailsModal from "components/design/ScreenDetailsModal.svelte"
|
||||
import DatasourceModal from "./DatasourceModal.svelte"
|
||||
import sanitizeUrl from "helpers/sanitizeUrl"
|
||||
import FormTypeModal from "./FormTypeModal.svelte"
|
||||
import TypeModal from "./TypeModal.svelte"
|
||||
import tableTypes from "./tableTypes"
|
||||
import formTypes from "./formTypes"
|
||||
import { Modal, notifications } from "@budibase/bbui"
|
||||
import {
|
||||
screenStore,
|
||||
|
@ -11,14 +12,9 @@
|
|||
builderStore,
|
||||
} from "stores/builder"
|
||||
import { auth } from "stores/portal"
|
||||
import { get } from "svelte/store"
|
||||
import { capitalise } from "helpers"
|
||||
import { goto } from "@roxi/routify"
|
||||
import { TOUR_KEYS } from "components/portal/onboarding/tours.js"
|
||||
import blankScreen from "templates/blankScreen"
|
||||
import formScreen from "templates/formScreen"
|
||||
import gridScreen from "templates/gridScreen"
|
||||
import gridDetailsScreen from "templates/gridDetailsScreen"
|
||||
import * as screenTemplating from "templates/screenTemplating"
|
||||
import { Roles } from "constants/backend"
|
||||
|
||||
let mode
|
||||
|
@ -26,16 +22,19 @@
|
|||
let screenDetailsModal
|
||||
let datasourceModal
|
||||
let formTypeModal
|
||||
let tableTypeModal
|
||||
|
||||
let selectedTablesAndViews = []
|
||||
let permissions = {}
|
||||
|
||||
$: screens = $screenStore.screens
|
||||
|
||||
export const show = newMode => {
|
||||
mode = newMode
|
||||
selectedTablesAndViews = []
|
||||
permissions = {}
|
||||
|
||||
if (mode === "grid" || mode === "gridDetails" || mode === "form") {
|
||||
if (mode === "table" || mode === "form") {
|
||||
datasourceModal.show()
|
||||
} else if (mode === "blank") {
|
||||
screenDetailsModal.show()
|
||||
|
@ -44,136 +43,83 @@
|
|||
}
|
||||
}
|
||||
|
||||
const createScreen = async screen => {
|
||||
const createScreen = async screenTemplate => {
|
||||
try {
|
||||
// Check we aren't clashing with an existing URL
|
||||
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)
|
||||
return await screenStore.save(screenTemplate)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
notifications.error("Error creating screens")
|
||||
}
|
||||
}
|
||||
|
||||
const addNavigationLink = async screen =>
|
||||
await navigationStore.saveLink(
|
||||
screen.routing.route,
|
||||
capitalise(screen.routing.route.split("/")[1]),
|
||||
screen.routing.roleId
|
||||
)
|
||||
const createScreens = async screenTemplates => {
|
||||
const newScreens = []
|
||||
|
||||
// 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)
|
||||
for (let screenTemplate of screenTemplates) {
|
||||
await addNavigationLink(
|
||||
screenTemplate.data,
|
||||
screenTemplate.navigationLinkLabel
|
||||
)
|
||||
newScreens.push(await createScreen(screenTemplate.data))
|
||||
}
|
||||
|
||||
return newScreens
|
||||
}
|
||||
|
||||
// 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 addNavigationLink = async (screen, linkLabel) => {
|
||||
if (linkLabel == null) return
|
||||
|
||||
await navigationStore.saveLink(
|
||||
screen.routing.route,
|
||||
linkLabel,
|
||||
screen.routing.roleId
|
||||
)
|
||||
}
|
||||
|
||||
const onSelectDatasources = async () => {
|
||||
if (mode === "form") {
|
||||
formTypeModal.show()
|
||||
} else if (mode === "grid") {
|
||||
await createGridScreen()
|
||||
} else if (mode === "gridDetails") {
|
||||
await createGridDetailsScreen()
|
||||
} else if (mode === "table") {
|
||||
tableTypeModal.show()
|
||||
}
|
||||
}
|
||||
|
||||
const createBlankScreen = async ({ screenUrl }) => {
|
||||
const screenTemplate = blankScreen(screenUrl)
|
||||
const screen = await createScreen(screenTemplate)
|
||||
await addNavigationLink(screenTemplate)
|
||||
const createBlankScreen = async ({ route }) => {
|
||||
const screenTemplates = screenTemplating.blank({ route, screens })
|
||||
|
||||
loadNewScreen(screen)
|
||||
const newScreens = await createScreens(screenTemplates)
|
||||
loadNewScreen(newScreens[0])
|
||||
}
|
||||
|
||||
const createGridScreen = async () => {
|
||||
let firstScreen = null
|
||||
|
||||
for (let tableOrView of selectedTablesAndViews) {
|
||||
const screenTemplate = gridScreen(
|
||||
const createTableScreen = async type => {
|
||||
const screenTemplates = selectedTablesAndViews.flatMap(tableOrView =>
|
||||
screenTemplating.table({
|
||||
screens,
|
||||
tableOrView,
|
||||
permissions[tableOrView.id]
|
||||
)
|
||||
type,
|
||||
permissions: permissions[tableOrView.id],
|
||||
})
|
||||
)
|
||||
|
||||
const screen = await createScreen(screenTemplate)
|
||||
await addNavigationLink(screen)
|
||||
|
||||
firstScreen ??= screen
|
||||
}
|
||||
|
||||
loadNewScreen(firstScreen)
|
||||
const newScreens = await createScreens(screenTemplates)
|
||||
loadNewScreen(newScreens[0])
|
||||
}
|
||||
|
||||
const createGridDetailsScreen = async () => {
|
||||
let firstScreen = null
|
||||
|
||||
for (let tableOrView of selectedTablesAndViews) {
|
||||
const screenTemplate = gridDetailsScreen(
|
||||
const createFormScreen = async type => {
|
||||
const screenTemplates = selectedTablesAndViews.flatMap(tableOrView =>
|
||||
screenTemplating.form({
|
||||
screens,
|
||||
tableOrView,
|
||||
permissions[tableOrView.id]
|
||||
)
|
||||
type,
|
||||
permissions: permissions[tableOrView.id],
|
||||
})
|
||||
)
|
||||
|
||||
const screen = await createScreen(screenTemplate)
|
||||
await addNavigationLink(screen)
|
||||
const newScreens = await createScreens(screenTemplates)
|
||||
|
||||
firstScreen ??= screen
|
||||
}
|
||||
|
||||
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") {
|
||||
if (type === "update" || type === "create") {
|
||||
const associatedTour =
|
||||
formType === "Update"
|
||||
type === "update"
|
||||
? TOUR_KEYS.BUILDER_FORM_VIEW_UPDATE
|
||||
: TOUR_KEYS.BUILDER_FORM_CREATE
|
||||
|
||||
|
@ -183,7 +129,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
loadNewScreen(firstScreen)
|
||||
loadNewScreen(newScreens[0])
|
||||
}
|
||||
|
||||
const loadNewScreen = screen => {
|
||||
|
@ -199,7 +145,11 @@
|
|||
}
|
||||
|
||||
const fetchPermission = resourceId => {
|
||||
permissions[resourceId] = { loading: true, read: null, write: null }
|
||||
permissions[resourceId] = {
|
||||
loading: true,
|
||||
read: Roles.BASIC,
|
||||
write: Roles.BASIC,
|
||||
}
|
||||
|
||||
permissionsStore
|
||||
.forResource(resourceId)
|
||||
|
@ -218,8 +168,8 @@
|
|||
if (permissions[resourceId]?.loading) {
|
||||
permissions[resourceId] = {
|
||||
loading: false,
|
||||
read: Roles.PUBLIC,
|
||||
write: Roles.PUBLIC,
|
||||
read: Roles.BASIC,
|
||||
write: Roles.BASIC,
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -250,18 +200,31 @@
|
|||
<Modal bind:this={datasourceModal} autoFocus={false}>
|
||||
<DatasourceModal
|
||||
{selectedTablesAndViews}
|
||||
{permissions}
|
||||
onConfirm={onSelectDatasources}
|
||||
on:toggle={handleTableOrViewToggle}
|
||||
/>
|
||||
</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}>
|
||||
<ScreenDetailsModal onConfirm={createBlankScreen} />
|
||||
</Modal>
|
||||
|
||||
<Modal bind:this={formTypeModal}>
|
||||
<FormTypeModal
|
||||
<TypeModal
|
||||
title="Select form type"
|
||||
types={formTypes}
|
||||
onConfirm={createFormScreen}
|
||||
onCancel={() => {
|
||||
formTypeModal.hide()
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
<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 ICONS from "components/backend/DatasourceNavigator/icons"
|
||||
import { IntegrationNames } from "constants"
|
||||
import { createEventDispatcher, onMount } from "svelte"
|
||||
import TableOrViewOption from "./TableOrViewOption.svelte"
|
||||
import * as format from "helpers/data/format"
|
||||
|
||||
export let onConfirm
|
||||
export let selectedTablesAndViews
|
||||
export let permissions
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
|
@ -21,38 +21,37 @@
|
|||
icon: "Remove",
|
||||
name: view.name,
|
||||
id: view.id,
|
||||
clientData: {
|
||||
...view,
|
||||
type: "viewV2",
|
||||
label: view.name,
|
||||
},
|
||||
tableSelectFormat: format.tableSelect.viewV2(view),
|
||||
datasourceSelectFormat: format.datasourceSelect.viewV2(view),
|
||||
}))
|
||||
}
|
||||
|
||||
const getTablesAndViews = datasource => {
|
||||
let tablesAndViews = []
|
||||
const rawTables = Array.isArray(datasource.entities)
|
||||
const tables = Array.isArray(datasource.entities)
|
||||
? datasource.entities
|
||||
: Object.values(datasource.entities ?? {})
|
||||
|
||||
for (const rawTable of rawTables) {
|
||||
if (rawTable._id === "ta_users") {
|
||||
for (const table of tables) {
|
||||
if (table._id === "ta_users") {
|
||||
continue
|
||||
}
|
||||
|
||||
const table = {
|
||||
const formattedTable = {
|
||||
icon: "Table",
|
||||
name: rawTable.name,
|
||||
id: rawTable._id,
|
||||
clientData: {
|
||||
...rawTable,
|
||||
label: rawTable.name,
|
||||
tableId: rawTable._id,
|
||||
type: "table",
|
||||
},
|
||||
name: table.name,
|
||||
id: table._id,
|
||||
tableSelectFormat: format.tableSelect.table(table),
|
||||
datasourceSelectFormat: format.datasourceSelect.table(
|
||||
table,
|
||||
$datasourcesStore.list
|
||||
),
|
||||
}
|
||||
|
||||
tablesAndViews = tablesAndViews.concat([table, ...getViews(rawTable)])
|
||||
tablesAndViews = tablesAndViews.concat([
|
||||
formattedTable,
|
||||
...getViews(table),
|
||||
])
|
||||
}
|
||||
|
||||
return tablesAndViews
|
||||
|
@ -96,60 +95,76 @@
|
|||
})
|
||||
</script>
|
||||
|
||||
<span>
|
||||
<ModalContent
|
||||
title="Autogenerated screens"
|
||||
confirmText="Confirm"
|
||||
cancelText="Back"
|
||||
{onConfirm}
|
||||
disabled={!selectedTablesAndViews.length}
|
||||
size="L"
|
||||
>
|
||||
<Body size="S">
|
||||
Select which datasources you would like to use to create your screens
|
||||
</Body>
|
||||
<Layout noPadding gap="S">
|
||||
{#each datasources as datasource}
|
||||
<div class="data-source-wrap">
|
||||
<div class="data-source-header">
|
||||
<svelte:component
|
||||
this={datasource.iconComponent}
|
||||
height="24"
|
||||
width="24"
|
||||
/>
|
||||
<div class="data-source-name">{datasource.name}</div>
|
||||
</div>
|
||||
<!-- List all tables -->
|
||||
{#each datasource.tablesAndViews as tableOrView}
|
||||
{@const selected = selectedTablesAndViews.some(
|
||||
selected => selected.id === tableOrView.id
|
||||
)}
|
||||
<TableOrViewOption
|
||||
roles={permissions[tableOrView.id]}
|
||||
on:click={() => toggleSelection(tableOrView)}
|
||||
{selected}
|
||||
{tableOrView}
|
||||
/>
|
||||
{/each}
|
||||
<ModalContent
|
||||
title="Autogenerated screens"
|
||||
confirmText="Next"
|
||||
cancelText="Cancel"
|
||||
{onConfirm}
|
||||
disabled={!selectedTablesAndViews.length}
|
||||
size="L"
|
||||
>
|
||||
<Body size="S">
|
||||
Select which datasources you would like to use to create your screens
|
||||
</Body>
|
||||
<Layout noPadding gap="S">
|
||||
{#each datasources as datasource}
|
||||
<div class="datasource">
|
||||
<div class="header">
|
||||
<svelte:component
|
||||
this={datasource.iconComponent}
|
||||
height="18"
|
||||
width="18"
|
||||
/>
|
||||
<h2>{datasource.name}</h2>
|
||||
</div>
|
||||
{/each}
|
||||
</Layout>
|
||||
</ModalContent>
|
||||
</span>
|
||||
<!-- List all tables -->
|
||||
{#each datasource.tablesAndViews as tableOrView}
|
||||
{@const selected = selectedTablesAndViews.some(
|
||||
selected => selected.id === tableOrView.id
|
||||
)}
|
||||
<TableOrViewOption
|
||||
on:click={() => toggleSelection(tableOrView)}
|
||||
{selected}
|
||||
{tableOrView}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
{/each}
|
||||
</Layout>
|
||||
</ModalContent>
|
||||
|
||||
<style>
|
||||
.data-source-wrap {
|
||||
padding-bottom: var(--spectrum-alias-item-padding-s);
|
||||
.datasource {
|
||||
padding-bottom: 15px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
grid-gap: var(--spacing-s);
|
||||
max-width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
.data-source-header {
|
||||
|
||||
.datasource:last-child {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-m);
|
||||
padding-bottom: var(--spacing-xs);
|
||||
padding-bottom: var(--spacing-m);
|
||||
}
|
||||
|
||||
.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>
|
||||
|
|
|
@ -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>
|
||||
import { Icon, AbsTooltip } from "@budibase/bbui"
|
||||
import RoleIcon from "components/common/RoleIcon.svelte"
|
||||
import { Icon } from "@budibase/bbui"
|
||||
|
||||
export let tableOrView
|
||||
export let roles
|
||||
export let selected = false
|
||||
|
||||
$: hideRoles = roles == undefined || roles?.loading
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div role="button" tabindex="0" class="datasource" class:selected on:click>
|
||||
<div class="content">
|
||||
<Icon name={tableOrView.icon} />
|
||||
<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>
|
||||
<Icon name={tableOrView.icon} />
|
||||
<span>{tableOrView.name}</span>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
|
@ -52,18 +17,8 @@
|
|||
border: 1px solid var(--spectrum-global-color-gray-300);
|
||||
transition: 160ms all;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
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);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
@ -71,7 +26,12 @@
|
|||
min-width: 0;
|
||||
}
|
||||
|
||||
.content span {
|
||||
.datasource :global(svg) {
|
||||
transition: 160ms all;
|
||||
color: var(--spectrum-global-color-gray-600);
|
||||
}
|
||||
|
||||
.datasource span {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
@ -84,29 +44,4 @@
|
|||
.selected {
|
||||
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>
|
||||
|
|
|
@ -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>
|
||||
import { Body } from "@budibase/bbui"
|
||||
import CreationPage from "components/common/CreationPage.svelte"
|
||||
import blankImage from "./images/blank.png"
|
||||
import tableInline from "./images/tableInline.png"
|
||||
import tableDetails from "./images/tableDetails.png"
|
||||
import formImage from "./images/form.png"
|
||||
import blank from "./images/blank.svg"
|
||||
import table from "./images/tableInline.svg"
|
||||
import form from "./images/formUpdate.svg"
|
||||
import CreateScreenModal from "./CreateScreenModal.svelte"
|
||||
import { screenStore } from "stores/builder"
|
||||
|
||||
|
@ -30,37 +29,27 @@
|
|||
<div class="cards">
|
||||
<div class="card" on:click={() => createScreenModal.show("blank")}>
|
||||
<div class="image">
|
||||
<img alt="" src={blankImage} />
|
||||
<img alt="A blank screen" src={blank} />
|
||||
</div>
|
||||
<div class="text">
|
||||
<Body size="S">Blank screen</Body>
|
||||
<Body size="S">Blank</Body>
|
||||
<Body size="XS">Add an empty blank screen</Body>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card" on:click={() => createScreenModal.show("grid")}>
|
||||
<div class="card" on:click={() => createScreenModal.show("table")}>
|
||||
<div class="image">
|
||||
<img alt="" src={tableInline} />
|
||||
<img alt="A table of data" src={table} />
|
||||
</div>
|
||||
<div class="text">
|
||||
<Body size="S">Table with inline editing</Body>
|
||||
<Body size="XS">View, edit and delete rows inline</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>
|
||||
<Body size="S">Table</Body>
|
||||
<Body size="XS">List rows in a table</Body>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card" on:click={() => createScreenModal.show("form")}>
|
||||
<div class="image">
|
||||
<img alt="" src={formImage} />
|
||||
<img alt="A form containing data" src={form} />
|
||||
</div>
|
||||
<div class="text">
|
||||
<Body size="S">Form</Body>
|
||||
|
@ -114,8 +103,9 @@
|
|||
}
|
||||
|
||||
.card .image {
|
||||
min-height: 130px;
|
||||
min-width: 235px;
|
||||
height: 127px;
|
||||
background-color: var(--grey-2);
|
||||
}
|
||||
|
||||
.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 { Component } from "templates/Component"
|
||||
import { Screen } from "templates/Screen"
|
||||
import { Screen } from "templates/screenTemplating/Screen"
|
||||
import { get } from "svelte/store"
|
||||
import {
|
||||
BUDIBASE_INTERNAL_DB_ID,
|
||||
|
|
|
@ -2,11 +2,11 @@ import { Helpers } from "@budibase/bbui"
|
|||
import { BaseStructure } from "./BaseStructure"
|
||||
|
||||
export class Component extends BaseStructure {
|
||||
constructor(name) {
|
||||
constructor(name, _id = Helpers.uuid()) {
|
||||
super(false)
|
||||
this._children = []
|
||||
this._json = {
|
||||
_id: Helpers.uuid(),
|
||||
_id,
|
||||
_component: name,
|
||||
_styles: {
|
||||
normal: {},
|
||||
|
@ -50,4 +50,8 @@ export class Component extends BaseStructure {
|
|||
this._json.text = text
|
||||
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"
|
||||
|
||||
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 { Component } from "./Component"
|
||||
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 gridDetailsUrl = tableOrView => sanitizeUrl(`/${tableOrView.name}`)
|
||||
|
||||
const createScreen = (tableOrView, permissions) => {
|
||||
const sidePanel = ({ tableOrView, permissions, screens }) => {
|
||||
/*
|
||||
Create Row
|
||||
*/
|
||||
|
@ -37,7 +36,7 @@ const createScreen = (tableOrView, permissions) => {
|
|||
buttons: [createButton.json()],
|
||||
})
|
||||
|
||||
const gridHeader = new Component("@budibase/standard-components/container")
|
||||
const tableHeader = new Component("@budibase/standard-components/container")
|
||||
.instanceName("Heading container")
|
||||
.customProps({
|
||||
direction: "row",
|
||||
|
@ -50,14 +49,14 @@ const createScreen = (tableOrView, permissions) => {
|
|||
text: tableOrView.name,
|
||||
})
|
||||
|
||||
gridHeader.addChild(heading)
|
||||
gridHeader.addChild(buttonGroup)
|
||||
tableHeader.addChild(heading)
|
||||
tableHeader.addChild(buttonGroup)
|
||||
|
||||
const createFormBlock = new Component(
|
||||
"@budibase/standard-components/formblock"
|
||||
)
|
||||
createFormBlock.instanceName("Create row form block").customProps({
|
||||
dataSource: tableOrView.clientData,
|
||||
dataSource: tableOrView.tableSelectFormat,
|
||||
labelPosition: "left",
|
||||
buttonPosition: "top",
|
||||
actionType: "Create",
|
||||
|
@ -68,7 +67,7 @@ const createScreen = (tableOrView, permissions) => {
|
|||
showSaveButton: true,
|
||||
saveButtonLabel: "Save",
|
||||
actionType: "Create",
|
||||
dataSource: tableOrView.clientData,
|
||||
dataSource: tableOrView.tableSelectFormat,
|
||||
}),
|
||||
})
|
||||
|
||||
|
@ -84,7 +83,7 @@ const createScreen = (tableOrView, permissions) => {
|
|||
|
||||
const editFormBlock = new Component("@budibase/standard-components/formblock")
|
||||
editFormBlock.instanceName("Edit row form block").customProps({
|
||||
dataSource: tableOrView.clientData,
|
||||
dataSource: tableOrView.tableSelectFormat,
|
||||
labelPosition: "left",
|
||||
buttonPosition: "top",
|
||||
actionType: "Update",
|
||||
|
@ -97,16 +96,16 @@ const createScreen = (tableOrView, permissions) => {
|
|||
saveButtonLabel: "Save",
|
||||
deleteButtonLabel: "Delete",
|
||||
actionType: "Update",
|
||||
dataSource: tableOrView.clientData,
|
||||
dataSource: tableOrView.tableSelectFormat,
|
||||
}),
|
||||
})
|
||||
|
||||
detailsSidePanel.addChild(editFormBlock)
|
||||
|
||||
const gridBlock = new Component("@budibase/standard-components/gridblock")
|
||||
gridBlock
|
||||
const tableBlock = new Component("@budibase/standard-components/gridblock")
|
||||
tableBlock
|
||||
.customProps({
|
||||
table: tableOrView.clientData,
|
||||
table: tableOrView.datasourceSelectFormat,
|
||||
allowAddRows: false,
|
||||
allowEditRows: false,
|
||||
allowDeleteRows: false,
|
||||
|
@ -132,16 +131,23 @@ const createScreen = (tableOrView, permissions) => {
|
|||
})
|
||||
.instanceName(`${tableOrView.name} - Table`)
|
||||
|
||||
return new Screen()
|
||||
.route(gridDetailsUrl(tableOrView))
|
||||
const template = new Screen()
|
||||
.route(getValidRoute(screens, tableOrView.name, permissions.write))
|
||||
.instanceName(`${tableOrView.name} - List and details`)
|
||||
.role(permissions.write)
|
||||
.autoTableId(tableOrView.resourceId)
|
||||
.addChild(gridHeader)
|
||||
.addChild(gridBlock)
|
||||
.autoTableId(tableOrView.id)
|
||||
.addChild(tableHeader)
|
||||
.addChild(tableBlock)
|
||||
.addChild(createRowSidePanel)
|
||||
.addChild(detailsSidePanel)
|
||||
.json()
|
||||
|
||||
return [
|
||||
{
|
||||
data: template,
|
||||
navigationLinkLabel: capitalise(tableOrView.name),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
export default createScreen
|
||||
export default sidePanel
|
|
@ -155,6 +155,7 @@ export const buildFormBlockButtonConfig = props => {
|
|||
providerId: formId,
|
||||
tableId: resourceId,
|
||||
notificationOverride,
|
||||
confirm: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|