Merge branch 'linked-records' of github.com:Budibase/budibase into linked-records
This commit is contained in:
commit
4ccd76fb5b
|
@ -66,12 +66,14 @@ Cypress.Commands.add("createTestTableWithData", () => {
|
||||||
Cypress.Commands.add("createTable", tableName => {
|
Cypress.Commands.add("createTable", tableName => {
|
||||||
// Enter model name
|
// Enter model name
|
||||||
cy.contains("Create New Table").click()
|
cy.contains("Create New Table").click()
|
||||||
cy.get(".menu-container")
|
cy.get(".modal").within(() => {
|
||||||
.get("input")
|
cy.get("input")
|
||||||
.first()
|
.first()
|
||||||
.type(tableName)
|
.type(tableName)
|
||||||
|
cy.get(".buttons")
|
||||||
cy.contains("Save").click()
|
.contains("Create")
|
||||||
|
.click()
|
||||||
|
})
|
||||||
cy.contains(tableName).should("be.visible")
|
cy.contains(tableName).should("be.visible")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -63,7 +63,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/bbui": "^1.40.1",
|
"@budibase/bbui": "^1.41.0",
|
||||||
"@budibase/client": "^0.1.25",
|
"@budibase/client": "^0.1.25",
|
||||||
"@budibase/colorpicker": "^1.0.1",
|
"@budibase/colorpicker": "^1.0.1",
|
||||||
"@fortawesome/fontawesome-free": "^5.14.0",
|
"@fortawesome/fontawesome-free": "^5.14.0",
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
import { routes } from "../routify/routes"
|
import { routes } from "../routify/routes"
|
||||||
import { initialise } from "builderStore"
|
import { initialise } from "builderStore"
|
||||||
import NotificationDisplay from "components/common/Notification/NotificationDisplay.svelte"
|
import NotificationDisplay from "components/common/Notification/NotificationDisplay.svelte"
|
||||||
import { ModalContainer } from "components/common/Modal"
|
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
await initialise()
|
await initialise()
|
||||||
|
@ -15,4 +14,3 @@
|
||||||
|
|
||||||
<NotificationDisplay />
|
<NotificationDisplay />
|
||||||
<Router {routes} />
|
<Router {routes} />
|
||||||
<ModalContainer />
|
|
||||||
|
|
|
@ -78,6 +78,11 @@ const contextToBindables = (models, walkResult) => context => {
|
||||||
typeof context.model === "string" ? context.model : context.model.modelId
|
typeof context.model === "string" ? context.model : context.model.modelId
|
||||||
const model = models.find(model => model._id === modelId)
|
const model = models.find(model => model._id === modelId)
|
||||||
|
|
||||||
|
// Avoid crashing whenever no data source has been selected
|
||||||
|
if (model == null) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
const newBindable = key => ({
|
const newBindable = key => ({
|
||||||
type: "context",
|
type: "context",
|
||||||
instance: context.instance,
|
instance: context.instance,
|
||||||
|
|
|
@ -2,10 +2,9 @@
|
||||||
import { onMount } from "svelte"
|
import { onMount } from "svelte"
|
||||||
import { automationStore } from "builderStore"
|
import { automationStore } from "builderStore"
|
||||||
import CreateAutomationModal from "./CreateAutomationModal.svelte"
|
import CreateAutomationModal from "./CreateAutomationModal.svelte"
|
||||||
import { Button } from "@budibase/bbui"
|
import { Button, Modal } from "@budibase/bbui"
|
||||||
import { Modal } from "components/common/Modal"
|
|
||||||
|
|
||||||
let modalVisible = false
|
let modal
|
||||||
|
|
||||||
$: selectedAutomationId = $automationStore.selectedAutomation?.automation?._id
|
$: selectedAutomationId = $automationStore.selectedAutomation?.automation?._id
|
||||||
|
|
||||||
|
@ -15,9 +14,7 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<Button primary wide on:click={() => (modalVisible = true)}>
|
<Button primary wide on:click={modal.show}>Create New Automation</Button>
|
||||||
Create New Automation
|
|
||||||
</Button>
|
|
||||||
<ul>
|
<ul>
|
||||||
{#each $automationStore.automations as automation}
|
{#each $automationStore.automations as automation}
|
||||||
<li
|
<li
|
||||||
|
@ -30,9 +27,9 @@
|
||||||
{/each}
|
{/each}
|
||||||
</ul>
|
</ul>
|
||||||
</section>
|
</section>
|
||||||
{#if modalVisible}
|
<Modal bind:this={modal}>
|
||||||
<CreateAutomationModal bind:visible={modalVisible} />
|
<CreateAutomationModal />
|
||||||
{/if}
|
</Modal>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
section {
|
section {
|
||||||
|
@ -51,7 +48,7 @@
|
||||||
color: var(--grey-6);
|
color: var(--grey-6);
|
||||||
}
|
}
|
||||||
i.live {
|
i.live {
|
||||||
color: var(--purple);
|
color: var(--ink);
|
||||||
}
|
}
|
||||||
|
|
||||||
li {
|
li {
|
||||||
|
|
|
@ -1,11 +1,8 @@
|
||||||
<script>
|
<script>
|
||||||
import { store, backendUiStore, automationStore } from "builderStore"
|
import { store, backendUiStore, automationStore } from "builderStore"
|
||||||
import { notifier } from "builderStore/store/notifications"
|
import { notifier } from "builderStore/store/notifications"
|
||||||
import { Input } from "@budibase/bbui"
|
import { Input, ModalContent } from "@budibase/bbui"
|
||||||
import analytics from "analytics"
|
import analytics from "analytics"
|
||||||
import { Modal } from "components/common/Modal"
|
|
||||||
|
|
||||||
export let visible
|
|
||||||
|
|
||||||
let name
|
let name
|
||||||
|
|
||||||
|
@ -29,22 +26,21 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Modal
|
<ModalContent
|
||||||
bind:visible
|
|
||||||
title="Create Automation"
|
title="Create Automation"
|
||||||
confirmText="Create"
|
confirmText="Create"
|
||||||
onConfirm={createAutomation}
|
onConfirm={createAutomation}
|
||||||
disabled={!valid}>
|
disabled={!valid}>
|
||||||
<Input bind:value={name} label="Name" />
|
<Input bind:value={name} label="Name" />
|
||||||
<slot name="footer">
|
<div slot="footer">
|
||||||
<a
|
<a
|
||||||
target="_blank"
|
target="_blank"
|
||||||
href="https://docs.budibase.com/automate/introduction-to-automate">
|
href="https://docs.budibase.com/automate/introduction-to-automate">
|
||||||
<i class="ri-information-line" />
|
<i class="ri-information-line" />
|
||||||
<span>Learn about automations</span>
|
<span>Learn about automations</span>
|
||||||
</a>
|
</a>
|
||||||
</slot>
|
</div>
|
||||||
</Modal>
|
</ModalContent>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
a {
|
a {
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
export let fieldName
|
export let fieldName
|
||||||
|
|
||||||
let record
|
let record
|
||||||
|
let title
|
||||||
|
|
||||||
$: data = record?.[fieldName] ?? []
|
$: data = record?.[fieldName] ?? []
|
||||||
$: linkedModelId = data?.length ? data[0].modelId : null
|
$: linkedModelId = data?.length ? data[0].modelId : null
|
||||||
|
@ -17,8 +18,15 @@
|
||||||
)
|
)
|
||||||
$: schema = linkedModel?.schema
|
$: schema = linkedModel?.schema
|
||||||
$: model = $backendUiStore.models.find(model => model._id === modelId)
|
$: model = $backendUiStore.models.find(model => model._id === modelId)
|
||||||
$: title = `${record?.[model?.primaryDisplay]} - ${fieldName}`
|
|
||||||
$: fetchData(modelId, recordId)
|
$: fetchData(modelId, recordId)
|
||||||
|
$: {
|
||||||
|
let recordLabel = record?.[model?.primaryDisplay]
|
||||||
|
if (recordLabel) {
|
||||||
|
title = `${recordLabel} - ${fieldName}`
|
||||||
|
} else {
|
||||||
|
title = fieldName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function fetchData(modelId, recordId) {
|
async function fetchData(modelId, recordId) {
|
||||||
const QUERY_VIEW_URL = `/api/${modelId}/${recordId}/enrich`
|
const QUERY_VIEW_URL = `/api/${modelId}/${recordId}/enrich`
|
||||||
|
|
|
@ -1,17 +1,16 @@
|
||||||
<script>
|
<script>
|
||||||
import { TextButton as Button, Icon } from "@budibase/bbui"
|
import { TextButton as Button, Icon, Modal } from "@budibase/bbui"
|
||||||
import CreateEditRecordModal from "../modals/CreateEditRecordModal.svelte"
|
import CreateEditRecordModal from "../modals/CreateEditRecordModal.svelte"
|
||||||
import { Modal } from "components/common/Modal"
|
|
||||||
|
|
||||||
let modalVisible
|
let modal
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Button text small on:click={() => (modalVisible = true)}>
|
<Button text small on:click={modal.show}>
|
||||||
<Icon name="addrow" />
|
<Icon name="addrow" />
|
||||||
Create New Row
|
Create New Row
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
{#if modalVisible}
|
<Modal bind:this={modal}>
|
||||||
<CreateEditRecordModal bind:visible={modalVisible} />
|
<CreateEditRecordModal />
|
||||||
{/if}
|
</Modal>
|
||||||
|
|
|
@ -3,13 +3,11 @@
|
||||||
import { notifier } from "builderStore/store/notifications"
|
import { notifier } from "builderStore/store/notifications"
|
||||||
import RecordFieldControl from "../RecordFieldControl.svelte"
|
import RecordFieldControl from "../RecordFieldControl.svelte"
|
||||||
import * as api from "../api"
|
import * as api from "../api"
|
||||||
import { Modal } from "components/common/Modal"
|
import { ModalContent } from "@budibase/bbui"
|
||||||
import ErrorsBox from "components/common/ErrorsBox.svelte"
|
import ErrorsBox from "components/common/ErrorsBox.svelte"
|
||||||
|
|
||||||
export let record = {}
|
export let record = {}
|
||||||
export let visible
|
|
||||||
|
|
||||||
let modal
|
|
||||||
let errors = []
|
let errors = []
|
||||||
|
|
||||||
$: creating = record?._id == null
|
$: creating = record?._id == null
|
||||||
|
@ -35,8 +33,7 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Modal
|
<ModalContent
|
||||||
bind:visible
|
|
||||||
title={creating ? 'Create Row' : 'Edit Row'}
|
title={creating ? 'Create Row' : 'Edit Row'}
|
||||||
confirmText={creating ? 'Create Row' : 'Save Row'}
|
confirmText={creating ? 'Create Row' : 'Save Row'}
|
||||||
onConfirm={saveRecord}>
|
onConfirm={saveRecord}>
|
||||||
|
@ -46,4 +43,4 @@
|
||||||
<RecordFieldControl {meta} bind:value={record[key]} />
|
<RecordFieldControl {meta} bind:value={record[key]} />
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</Modal>
|
</ModalContent>
|
||||||
|
|
|
@ -1,22 +1,21 @@
|
||||||
<script>
|
<script>
|
||||||
import { backendUiStore } from "builderStore"
|
import { backendUiStore } from "builderStore"
|
||||||
import { DropdownMenu, Icon } from "@budibase/bbui"
|
import { DropdownMenu, Icon, Modal } from "@budibase/bbui"
|
||||||
import CreateEditRecordModal from "../modals/CreateEditRecordModal.svelte"
|
import CreateEditRecordModal from "../modals/CreateEditRecordModal.svelte"
|
||||||
import * as api from "../api"
|
import * as api from "../api"
|
||||||
import { notifier } from "builderStore/store/notifications"
|
import { notifier } from "builderStore/store/notifications"
|
||||||
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
||||||
import { Modal } from "components/common/Modal"
|
|
||||||
|
|
||||||
export let row
|
export let row
|
||||||
|
|
||||||
let anchor
|
let anchor
|
||||||
let dropdown
|
let dropdown
|
||||||
let confirmDeleteDialog
|
let confirmDeleteDialog
|
||||||
let editModalVisible
|
let modal
|
||||||
|
|
||||||
function showModal() {
|
function showModal() {
|
||||||
dropdown.hide()
|
dropdown.hide()
|
||||||
editModalVisible = true
|
modal.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
function showDelete() {
|
function showDelete() {
|
||||||
|
@ -52,9 +51,9 @@
|
||||||
okText="Delete Row"
|
okText="Delete Row"
|
||||||
onOk={deleteRow}
|
onOk={deleteRow}
|
||||||
title="Confirm Delete" />
|
title="Confirm Delete" />
|
||||||
{#if editModalVisible}
|
<Modal bind:this={modal}>
|
||||||
<CreateEditRecordModal bind:visible={editModalVisible} record={row} />
|
<CreateEditRecordModal record={row} />
|
||||||
{/if}
|
</Modal>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.ri-more-line:hover {
|
.ri-more-line:hover {
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
import { goto } from "@sveltech/routify"
|
import { goto } from "@sveltech/routify"
|
||||||
import { backendUiStore } from "builderStore"
|
import { backendUiStore } from "builderStore"
|
||||||
import ListItem from "./ListItem.svelte"
|
import ListItem from "./ListItem.svelte"
|
||||||
import CreateTablePopover from "./popovers/CreateTablePopover.svelte"
|
import CreateTableModal from "./modals/CreateTableModal.svelte"
|
||||||
import EditTablePopover from "./popovers/EditTablePopover.svelte"
|
import EditTablePopover from "./popovers/EditTablePopover.svelte"
|
||||||
import EditViewPopover from "./popovers/EditViewPopover.svelte"
|
import EditViewPopover from "./popovers/EditViewPopover.svelte"
|
||||||
import { Heading } from "@budibase/bbui"
|
import { Heading } from "@budibase/bbui"
|
||||||
|
@ -28,7 +28,7 @@
|
||||||
<div class="components-list-container">
|
<div class="components-list-container">
|
||||||
<Heading small>Tables</Heading>
|
<Heading small>Tables</Heading>
|
||||||
<Spacer medium />
|
<Spacer medium />
|
||||||
<CreateTablePopover />
|
<CreateTableModal />
|
||||||
<div class="hierarchy-items-container">
|
<div class="hierarchy-items-container">
|
||||||
{#each $backendUiStore.models as model}
|
{#each $backendUiStore.models as model}
|
||||||
<ListItem
|
<ListItem
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
<script>
|
||||||
|
import { goto } from "@sveltech/routify"
|
||||||
|
import { backendUiStore } from "builderStore"
|
||||||
|
import { notifier } from "builderStore/store/notifications"
|
||||||
|
import { Button, Input, Label, ModalContent, Modal } from "@budibase/bbui"
|
||||||
|
import Spinner from "components/common/Spinner.svelte"
|
||||||
|
import TableDataImport from "../TableDataImport.svelte"
|
||||||
|
import analytics from "analytics"
|
||||||
|
|
||||||
|
let modal
|
||||||
|
let name
|
||||||
|
let dataImport
|
||||||
|
|
||||||
|
function resetState() {
|
||||||
|
name = ""
|
||||||
|
dataImport = undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveTable() {
|
||||||
|
const model = await backendUiStore.actions.models.save({
|
||||||
|
name,
|
||||||
|
schema: dataImport.schema || {},
|
||||||
|
dataImport,
|
||||||
|
})
|
||||||
|
notifier.success(`Table ${name} created successfully.`)
|
||||||
|
$goto(`./model/${model._id}`)
|
||||||
|
analytics.captureEvent("Table Created", { name })
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Button primary wide on:click={modal.show}>Create New Table</Button>
|
||||||
|
<Modal bind:this={modal} on:hide={resetState}>
|
||||||
|
<ModalContent
|
||||||
|
title="Create Table"
|
||||||
|
confirmText="Create"
|
||||||
|
onConfirm={saveTable}
|
||||||
|
disabled={!name || (dataImport && !dataImport.valid)}>
|
||||||
|
<Input
|
||||||
|
data-cy="table-name-input"
|
||||||
|
thin
|
||||||
|
label="Table Name"
|
||||||
|
bind:value={name} />
|
||||||
|
<div>
|
||||||
|
<Label grey extraSmall>Create Table from CSV (Optional)</Label>
|
||||||
|
<TableDataImport bind:dataImport />
|
||||||
|
</div>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
|
@ -1,84 +0,0 @@
|
||||||
<script>
|
|
||||||
import { goto } from "@sveltech/routify"
|
|
||||||
import { backendUiStore } from "builderStore"
|
|
||||||
import { notifier } from "builderStore/store/notifications"
|
|
||||||
import { Popover, Button, Icon, Input, Select, Label } from "@budibase/bbui"
|
|
||||||
import Spinner from "components/common/Spinner.svelte"
|
|
||||||
import TableDataImport from "../TableDataImport.svelte"
|
|
||||||
import analytics from "analytics"
|
|
||||||
|
|
||||||
let anchor
|
|
||||||
let dropdown
|
|
||||||
let name
|
|
||||||
let dataImport
|
|
||||||
let loading
|
|
||||||
|
|
||||||
async function saveTable() {
|
|
||||||
loading = true
|
|
||||||
const model = await backendUiStore.actions.models.save({
|
|
||||||
name,
|
|
||||||
schema: dataImport.schema || {},
|
|
||||||
dataImport,
|
|
||||||
})
|
|
||||||
notifier.success(`Table ${name} created successfully.`)
|
|
||||||
$goto(`./model/${model._id}`)
|
|
||||||
analytics.captureEvent("Table Created", { name })
|
|
||||||
name = ""
|
|
||||||
dropdown.hide()
|
|
||||||
loading = false
|
|
||||||
}
|
|
||||||
|
|
||||||
const onClosed = () => {
|
|
||||||
name = ""
|
|
||||||
dropdown.hide()
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div bind:this={anchor}>
|
|
||||||
<Button primary wide on:click={dropdown.show}>Create New Table</Button>
|
|
||||||
</div>
|
|
||||||
<Popover bind:this={dropdown} {anchor} align="left">
|
|
||||||
<div class="actions">
|
|
||||||
<h5>Create Table</h5>
|
|
||||||
<Input
|
|
||||||
data-cy="table-name-input"
|
|
||||||
thin
|
|
||||||
label="Table Name"
|
|
||||||
bind:value={name} />
|
|
||||||
<div>
|
|
||||||
<Label grey extraSmall>Create Table from CSV (Optional)</Label>
|
|
||||||
<TableDataImport bind:dataImport />
|
|
||||||
</div>
|
|
||||||
<footer>
|
|
||||||
<Button secondary on:click={onClosed}>Cancel</Button>
|
|
||||||
<Button
|
|
||||||
disabled={!name || (dataImport && !dataImport.valid)}
|
|
||||||
primary
|
|
||||||
on:click={saveTable}>
|
|
||||||
<span style={`margin-right: ${loading ? '10px' : 0};`}>Save</span>
|
|
||||||
{#if loading}
|
|
||||||
<Spinner size="10" />
|
|
||||||
{/if}
|
|
||||||
</Button>
|
|
||||||
</footer>
|
|
||||||
</div>
|
|
||||||
</Popover>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.actions {
|
|
||||||
display: grid;
|
|
||||||
grid-gap: var(--spacing-xl);
|
|
||||||
min-width: 400px;
|
|
||||||
}
|
|
||||||
|
|
||||||
h5 {
|
|
||||||
margin: 0;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
footer {
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
gap: var(--spacing-m);
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,33 +1,27 @@
|
||||||
<script>
|
<script>
|
||||||
import { Modal } from "components/common/Modal"
|
import { Modal, ModalContent } from "@budibase/bbui"
|
||||||
|
|
||||||
export let title = ""
|
export let title = ""
|
||||||
export let body = ""
|
export let body = ""
|
||||||
export let okText = "Confirm"
|
export let okText = "Confirm"
|
||||||
export let cancelText = "Cancel"
|
export let cancelText = "Cancel"
|
||||||
export let onOk = () => {}
|
export let onOk = undefined
|
||||||
export let onCancel = () => {}
|
export let onCancel = undefined
|
||||||
|
|
||||||
let visible = false
|
let modal
|
||||||
|
|
||||||
export const show = () => {
|
export const show = () => {
|
||||||
visible = true
|
modal.show()
|
||||||
}
|
}
|
||||||
export const hide = () => {
|
export const hide = () => {
|
||||||
visible = false
|
modal.hide()
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Modal
|
<Modal bind:this={modal} on:hide={onCancel}>
|
||||||
id={title}
|
<ModalContent onConfirm={onOk} {title} confirmText={okText} {cancelText} red>
|
||||||
bind:visible
|
|
||||||
on:hide={onCancel}
|
|
||||||
{title}
|
|
||||||
confirmText={okText}
|
|
||||||
{cancelText}
|
|
||||||
onConfirm={onOk}
|
|
||||||
red>
|
|
||||||
<div class="body">{body}</div>
|
<div class="body">{body}</div>
|
||||||
|
</ModalContent>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
|
@ -8,17 +8,24 @@
|
||||||
export let schema
|
export let schema
|
||||||
export let linkedRecords = []
|
export let linkedRecords = []
|
||||||
|
|
||||||
|
let records = []
|
||||||
|
|
||||||
$: label = capitalise(schema.name)
|
$: label = capitalise(schema.name)
|
||||||
$: linkedModelId = schema.modelId
|
$: linkedModelId = schema.modelId
|
||||||
$: linkedModel = $backendUiStore.models.find(
|
$: linkedModel = $backendUiStore.models.find(
|
||||||
model => model._id === linkedModelId
|
model => model._id === linkedModelId
|
||||||
)
|
)
|
||||||
$: promise = fetchRecords(linkedModelId)
|
$: fetchRecords(linkedModelId)
|
||||||
|
|
||||||
async function fetchRecords(linkedModelId) {
|
async function fetchRecords(linkedModelId) {
|
||||||
const FETCH_RECORDS_URL = `/api/${linkedModelId}/records`
|
const FETCH_RECORDS_URL = `/api/${linkedModelId}/records`
|
||||||
|
try {
|
||||||
const response = await api.get(FETCH_RECORDS_URL)
|
const response = await api.get(FETCH_RECORDS_URL)
|
||||||
return await response.json()
|
records = await response.json()
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
records = []
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPrettyName(record) {
|
function getPrettyName(record) {
|
||||||
|
@ -34,7 +41,6 @@
|
||||||
table.
|
table.
|
||||||
</Label>
|
</Label>
|
||||||
{:else}
|
{:else}
|
||||||
{#await promise then records}
|
|
||||||
<Multiselect
|
<Multiselect
|
||||||
secondary
|
secondary
|
||||||
bind:value={linkedRecords}
|
bind:value={linkedRecords}
|
||||||
|
@ -44,5 +50,4 @@
|
||||||
<option value={record._id}>{getPrettyName(record)}</option>
|
<option value={record._id}>{getPrettyName(record)}</option>
|
||||||
{/each}
|
{/each}
|
||||||
</Multiselect>
|
</Multiselect>
|
||||||
{/await}
|
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
@ -1,215 +0,0 @@
|
||||||
<script>
|
|
||||||
/**
|
|
||||||
* Confirmation is handled as a callback rather than an event to allow
|
|
||||||
* handling the result - meaning a parent can prevent the modal closing.
|
|
||||||
*
|
|
||||||
* A show/hide API is exposed as part of the modal and also via context for
|
|
||||||
* children inside the modal.
|
|
||||||
* "show" and "hide" events are emitted as visibility changes.
|
|
||||||
*
|
|
||||||
* Modals are rendered at the top of the DOM tree.
|
|
||||||
*/
|
|
||||||
import { createEventDispatcher, setContext } from "svelte"
|
|
||||||
import { fade, fly } from "svelte/transition"
|
|
||||||
import Portal from "svelte-portal"
|
|
||||||
import { Button } from "@budibase/bbui"
|
|
||||||
import { ContextKey } from "./context"
|
|
||||||
const dispatch = createEventDispatcher()
|
|
||||||
|
|
||||||
export let wide = false
|
|
||||||
export let padded = true
|
|
||||||
export let title = undefined
|
|
||||||
export let cancelText = "Cancel"
|
|
||||||
export let confirmText = "Confirm"
|
|
||||||
export let showCancelButton = true
|
|
||||||
export let showConfirmButton = true
|
|
||||||
export let onConfirm = () => {}
|
|
||||||
export let visible = false
|
|
||||||
|
|
||||||
let loading = false
|
|
||||||
|
|
||||||
function show() {
|
|
||||||
if (visible) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
visible = true
|
|
||||||
dispatch("show")
|
|
||||||
}
|
|
||||||
|
|
||||||
function hide() {
|
|
||||||
if (!visible) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
visible = false
|
|
||||||
dispatch("hide")
|
|
||||||
}
|
|
||||||
|
|
||||||
async function confirm() {
|
|
||||||
loading = true
|
|
||||||
if (!onConfirm || (await onConfirm()) !== false) {
|
|
||||||
hide()
|
|
||||||
}
|
|
||||||
loading = false
|
|
||||||
}
|
|
||||||
|
|
||||||
setContext(ContextKey, { show, hide })
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{#if visible}
|
|
||||||
<Portal target="#modal-container">
|
|
||||||
<div
|
|
||||||
class="overlay"
|
|
||||||
on:click|self={hide}
|
|
||||||
transition:fade={{ duration: 200 }}>
|
|
||||||
<div
|
|
||||||
class="scroll-wrapper"
|
|
||||||
on:click|self={hide}
|
|
||||||
transition:fly={{ y: 50 }}>
|
|
||||||
<div class="content-wrapper" on:click|self={hide}>
|
|
||||||
<div class="modal" class:wide class:padded>
|
|
||||||
{#if title}
|
|
||||||
<header>
|
|
||||||
<h5>{title}</h5>
|
|
||||||
<div class="header-content">
|
|
||||||
<slot name="header" />
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
{/if}
|
|
||||||
<slot />
|
|
||||||
{#if showCancelButton || showConfirmButton}
|
|
||||||
<footer>
|
|
||||||
<div class="footer-content">
|
|
||||||
<slot name="footer" />
|
|
||||||
</div>
|
|
||||||
<div class="buttons">
|
|
||||||
{#if showCancelButton}
|
|
||||||
<Button secondary on:click={hide}>{cancelText}</Button>
|
|
||||||
{/if}
|
|
||||||
{#if showConfirmButton}
|
|
||||||
<Button
|
|
||||||
primary
|
|
||||||
{...$$restProps}
|
|
||||||
disabled={$$restProps.disabled || loading}
|
|
||||||
on:click={confirm}>
|
|
||||||
{confirmText}
|
|
||||||
</Button>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</footer>
|
|
||||||
{/if}
|
|
||||||
<i class="ri-close-line" on:click={hide} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Portal>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.overlay {
|
|
||||||
position: fixed;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
top: 0;
|
|
||||||
bottom: 0;
|
|
||||||
overflow-x: hidden;
|
|
||||||
overflow-y: auto;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
background-color: rgba(0, 0, 0, 0.25);
|
|
||||||
}
|
|
||||||
|
|
||||||
.scroll-wrapper {
|
|
||||||
flex: 1 1 auto;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: flex-start;
|
|
||||||
max-height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content-wrapper {
|
|
||||||
flex: 1 1 auto;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: flex-start;
|
|
||||||
width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal {
|
|
||||||
background-color: white;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: flex-start;
|
|
||||||
align-items: stretch;
|
|
||||||
box-shadow: 0 0 2.4rem 1.5rem rgba(0, 0, 0, 0.15);
|
|
||||||
position: relative;
|
|
||||||
flex: 0 0 400px;
|
|
||||||
margin: 2rem 0;
|
|
||||||
border-radius: var(--border-radius-m);
|
|
||||||
gap: var(--spacing-xl);
|
|
||||||
}
|
|
||||||
.modal.wide {
|
|
||||||
flex: 0 0 600px;
|
|
||||||
}
|
|
||||||
.modal.padded {
|
|
||||||
padding: var(--spacing-xl);
|
|
||||||
}
|
|
||||||
|
|
||||||
header {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
margin-right: 40px;
|
|
||||||
}
|
|
||||||
header h5 {
|
|
||||||
margin: 0;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header-content {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: flex-end;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
i {
|
|
||||||
position: absolute;
|
|
||||||
top: var(--spacing-xl);
|
|
||||||
right: var(--spacing-xl);
|
|
||||||
color: var(--ink);
|
|
||||||
font-size: var(--font-size-xl);
|
|
||||||
}
|
|
||||||
i:hover {
|
|
||||||
color: var(--grey-6);
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
footer {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
gap: var(--spacing-m);
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer-content {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: flex-start;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.buttons {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: flex-end;
|
|
||||||
align-items: center;
|
|
||||||
gap: var(--spacing-m);
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,10 +0,0 @@
|
||||||
<div id="modal-container" />
|
|
||||||
|
|
||||||
<style>
|
|
||||||
#modal-container {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
z-index: 999;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1 +0,0 @@
|
||||||
export const ContextKey = "budibase-modal"
|
|
|
@ -1,3 +0,0 @@
|
||||||
export { default as Modal } from "./Modal.svelte"
|
|
||||||
export { default as ModalContainer } from "./ModalContainer.svelte"
|
|
||||||
export { ContextKey } from "./context"
|
|
|
@ -1,17 +1,17 @@
|
||||||
<script>
|
<script>
|
||||||
import SettingsModal from "./SettingsModal.svelte"
|
import SettingsModal from "./SettingsModal.svelte"
|
||||||
import { SettingsIcon } from "components/common/Icons/"
|
import { SettingsIcon } from "components/common/Icons/"
|
||||||
import { Modal } from "components/common/Modal"
|
import { Modal } from "@budibase/bbui"
|
||||||
|
|
||||||
let modalVisible
|
let modal
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<span class="topnavitemright settings" on:click={() => (modalVisible = true)}>
|
<span class="topnavitemright settings" on:click={modal.show}>
|
||||||
<SettingsIcon />
|
<SettingsIcon />
|
||||||
</span>
|
</span>
|
||||||
{#if modalVisible}
|
<Modal bind:this={modal} width="600px">
|
||||||
<SettingsModal bind:visible={modalVisible} />
|
<SettingsModal />
|
||||||
{/if}
|
</Modal>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
span:first-letter {
|
span:first-letter {
|
||||||
|
@ -20,8 +20,7 @@
|
||||||
.topnavitemright {
|
.topnavitemright {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
color: var(--grey-7);
|
color: var(--grey-7);
|
||||||
margin: 0px 20px 0px 0px;
|
margin: 0 20px 0 0;
|
||||||
padding-top: 4px;
|
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import { General, Users, DangerZone, APIKeys } from "./tabs"
|
import { General, Users, DangerZone, APIKeys } from "./tabs"
|
||||||
import { Switcher } from "@budibase/bbui"
|
import { Switcher, ModalContent } from "@budibase/bbui"
|
||||||
import { Modal } from "components/common/Modal"
|
|
||||||
|
|
||||||
const tabs = [
|
const tabs = [
|
||||||
{
|
{
|
||||||
|
@ -26,17 +25,13 @@
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
export let visible
|
|
||||||
|
|
||||||
let value = "GENERAL"
|
let value = "GENERAL"
|
||||||
|
|
||||||
$: selectedTab = tabs.find(tab => tab.key === value).component
|
$: selectedTab = tabs.find(tab => tab.key === value).component
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Modal
|
<ModalContent
|
||||||
title="Settings"
|
title="Settings"
|
||||||
wide
|
|
||||||
bind:visible
|
|
||||||
showConfirmButton={false}
|
showConfirmButton={false}
|
||||||
showCancelButton={false}>
|
showCancelButton={false}>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
@ -44,7 +39,7 @@
|
||||||
<svelte:component this={selectedTab} />
|
<svelte:component this={selectedTab} />
|
||||||
</Switcher>
|
</Switcher>
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</ModalContent>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.container :global(section > header) {
|
.container :global(section > header) {
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import { writable } from "svelte/store"
|
import { writable } from "svelte/store"
|
||||||
import { Modal } from "components/common/Modal"
|
|
||||||
import { store, automationStore, backendUiStore } from "builderStore"
|
import { store, automationStore, backendUiStore } from "builderStore"
|
||||||
import { string, object } from "yup"
|
import { string, object } from "yup"
|
||||||
import api, { get } from "builderStore/api"
|
import api, { get } from "builderStore/api"
|
||||||
|
@ -18,7 +17,6 @@
|
||||||
//Move this to context="module" once svelte-forms is updated so that it can bind to stores correctly
|
//Move this to context="module" once svelte-forms is updated so that it can bind to stores correctly
|
||||||
const createAppStore = writable({ currentStep: 0, values: {} })
|
const createAppStore = writable({ currentStep: 0, values: {} })
|
||||||
|
|
||||||
export let visible
|
|
||||||
export let hasKey
|
export let hasKey
|
||||||
export let template
|
export let template
|
||||||
|
|
||||||
|
@ -201,13 +199,7 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Modal
|
<div class="container">
|
||||||
bind:visible
|
|
||||||
wide
|
|
||||||
padded={false}
|
|
||||||
showCancelButton={false}
|
|
||||||
showConfirmButton={false}>
|
|
||||||
<div class="container">
|
|
||||||
<div class="sidebar">
|
<div class="sidebar">
|
||||||
{#each steps as { active, done }, i}
|
{#each steps as { active, done }, i}
|
||||||
<Indicator
|
<Indicator
|
||||||
|
@ -261,8 +253,7 @@
|
||||||
<span class="spinner-text">Creating your app...</span>
|
<span class="spinner-text">Creating your app...</span>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.container {
|
.container {
|
||||||
|
|
|
@ -3,21 +3,18 @@
|
||||||
import {
|
import {
|
||||||
TextButton,
|
TextButton,
|
||||||
Button,
|
Button,
|
||||||
Heading,
|
|
||||||
Body,
|
Body,
|
||||||
Spacer,
|
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
|
ModalContent,
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
import { AddIcon, ArrowDownIcon } from "components/common/Icons/"
|
import { AddIcon, ArrowDownIcon } from "components/common/Icons/"
|
||||||
import { EVENT_TYPE_MEMBER_NAME } from "../../common/eventHandlers"
|
import { EVENT_TYPE_MEMBER_NAME } from "../../common/eventHandlers"
|
||||||
import actionTypes from "./actions"
|
import actionTypes from "./actions"
|
||||||
import { createEventDispatcher } from "svelte"
|
import { createEventDispatcher } from "svelte"
|
||||||
import { Modal } from "components/common/Modal"
|
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
export let event
|
export let event
|
||||||
export let visible
|
|
||||||
|
|
||||||
let addActionButton
|
let addActionButton
|
||||||
let addActionDropdown
|
let addActionDropdown
|
||||||
|
@ -60,12 +57,7 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Modal
|
<ModalContent title="Actions" confirmText="Save" onConfirm={saveEventData}>
|
||||||
bind:visible
|
|
||||||
title="Actions"
|
|
||||||
wide
|
|
||||||
confirmText="Save"
|
|
||||||
onConfirm={saveEventData}>
|
|
||||||
<div slot="header">
|
<div slot="header">
|
||||||
<div bind:this={addActionButton}>
|
<div bind:this={addActionButton}>
|
||||||
<TextButton text small blue on:click={addActionDropdown.show}>
|
<TextButton text small blue on:click={addActionDropdown.show}>
|
||||||
|
@ -94,9 +86,7 @@
|
||||||
{#each actions as action, index}
|
{#each actions as action, index}
|
||||||
<div class="action-container">
|
<div class="action-container">
|
||||||
<div class="action-header" on:click={selectAction(action)}>
|
<div class="action-header" on:click={selectAction(action)}>
|
||||||
<Body extraSmall lh>
|
<Body small lh>{index + 1}. {action[EVENT_TYPE_MEMBER_NAME]}</Body>
|
||||||
{index + 1}. {action[EVENT_TYPE_MEMBER_NAME]}
|
|
||||||
</Body>
|
|
||||||
<div class="row-expander" class:rotate={action !== selectedAction}>
|
<div class="row-expander" class:rotate={action !== selectedAction}>
|
||||||
<ArrowDownIcon />
|
<ArrowDownIcon />
|
||||||
</div>
|
</div>
|
||||||
|
@ -121,7 +111,7 @@
|
||||||
<div slot="footer">
|
<div slot="footer">
|
||||||
<a href="https://docs.budibase.com">Learn more about Actions</a>
|
<a href="https://docs.budibase.com">Learn more about Actions</a>
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</ModalContent>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.action-header {
|
.action-header {
|
||||||
|
@ -151,8 +141,7 @@
|
||||||
|
|
||||||
.actions-container {
|
.actions-container {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-height: 0px;
|
min-height: 0;
|
||||||
padding-bottom: var(--spacing-s);
|
|
||||||
padding-top: 0;
|
padding-top: 0;
|
||||||
border: var(--border-light);
|
border: var(--border-light);
|
||||||
border-width: 0 0 1px 0;
|
border-width: 0 0 1px 0;
|
||||||
|
@ -162,10 +151,6 @@
|
||||||
.action-container {
|
.action-container {
|
||||||
border: var(--border-light);
|
border: var(--border-light);
|
||||||
border-width: 1px 0 0 0;
|
border-width: 1px 0 0 0;
|
||||||
padding-left: var(--spacing-xl);
|
|
||||||
padding-right: var(--spacing-xl);
|
|
||||||
padding-top: 0;
|
|
||||||
padding-bottom: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.selected-action-container {
|
.selected-action-container {
|
||||||
|
|
|
@ -1,23 +1,17 @@
|
||||||
<script>
|
<script>
|
||||||
import { Button, Modal } from "@budibase/bbui"
|
import { Button, Modal } from "@budibase/bbui"
|
||||||
import EventEditorModal from "./EventEditorModal.svelte"
|
import EventEditorModal from "./EventEditorModal.svelte"
|
||||||
import { createEventDispatcher, onMount } from "svelte"
|
import { createEventDispatcher } from "svelte"
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
export let value
|
export let value
|
||||||
export let name
|
export let name
|
||||||
|
|
||||||
let modalVisible = false
|
let modal
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Button secondary small on:click={() => (modalVisible = true)}>
|
<Button secondary small on:click={modal.show}>Define Actions</Button>
|
||||||
Define Actions
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
{#if modalVisible}
|
<Modal bind:this={modal} width="600px">
|
||||||
<EventEditorModal
|
<EventEditorModal event={value} eventType={name} on:change />
|
||||||
bind:visible={modalVisible}
|
</Modal>
|
||||||
event={value}
|
|
||||||
eventType={name}
|
|
||||||
on:change />
|
|
||||||
{/if}
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
import { keys, map, includes, filter } from "lodash/fp"
|
import { keys, map, includes, filter } from "lodash/fp"
|
||||||
import EventEditorModal from "./EventEditorModal.svelte"
|
import EventEditorModal from "./EventEditorModal.svelte"
|
||||||
import { Modal } from "components/common/Modal"
|
import { Modal } from "@budibase/bbui"
|
||||||
|
|
||||||
export const EVENT_TYPE = "event"
|
export const EVENT_TYPE = "event"
|
||||||
export let component
|
export let component
|
||||||
|
@ -47,11 +47,8 @@
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Modal bind:this={modal}>
|
<Modal bind:this={modal} width="600px">
|
||||||
<EventEditorModal
|
<EventEditorModal eventOptions={events} event={selectedEvent} />
|
||||||
eventOptions={events}
|
|
||||||
event={selectedEvent}
|
|
||||||
on:hide={() => (selectedEvent = null)} />
|
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
|
@ -60,7 +60,7 @@
|
||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
}
|
}
|
||||||
|
|
||||||
.root :global(.relative:nth-child(2)) {
|
.root :global(> div:nth-child(2)) {
|
||||||
grid-column-start: 2;
|
grid-column-start: 2;
|
||||||
grid-column-end: 6;
|
grid-column-end: 6;
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@
|
||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
}
|
}
|
||||||
|
|
||||||
.root :global(.relative) {
|
.root :global(> div) {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
margin-left: var(--spacing-l);
|
margin-left: var(--spacing-l);
|
||||||
}
|
}
|
||||||
|
|
|
@ -119,7 +119,7 @@
|
||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
}
|
}
|
||||||
|
|
||||||
.root :global(.relative:nth-child(2)) {
|
.root :global(> div:nth-child(2)) {
|
||||||
grid-column-start: 2;
|
grid-column-start: 2;
|
||||||
grid-column-end: 6;
|
grid-column-end: 6;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,21 +1,18 @@
|
||||||
<script>
|
<script>
|
||||||
import { store, backendUiStore } from "builderStore"
|
import { store } from "builderStore"
|
||||||
import ComponentsHierarchy from "components/userInterface/ComponentsHierarchy.svelte"
|
import ComponentsHierarchy from "components/userInterface/ComponentsHierarchy.svelte"
|
||||||
import PageLayout from "components/userInterface/PageLayout.svelte"
|
import PageLayout from "components/userInterface/PageLayout.svelte"
|
||||||
import PagesList from "components/userInterface/PagesList.svelte"
|
import PagesList from "components/userInterface/PagesList.svelte"
|
||||||
import NewScreen from "components/userInterface/NewScreen.svelte"
|
import NewScreenModal from "components/userInterface/NewScreenModal.svelte"
|
||||||
import { Button } from "@budibase/bbui"
|
import { Button, Spacer, Modal } from "@budibase/bbui"
|
||||||
import { Spacer } from "@budibase/bbui"
|
|
||||||
|
|
||||||
let modalVisible = false
|
let modal
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<PagesList />
|
<PagesList />
|
||||||
|
|
||||||
<Spacer medium />
|
<Spacer medium />
|
||||||
<Button primary wide on:click={() => (modalVisible = true)}>
|
<Button primary wide on:click={modal.show}>Create New Screen</Button>
|
||||||
Create New Screen
|
|
||||||
</Button>
|
|
||||||
<Spacer medium />
|
<Spacer medium />
|
||||||
<PageLayout layout={$store.pages[$store.currentPageName]} />
|
<PageLayout layout={$store.pages[$store.currentPageName]} />
|
||||||
|
|
||||||
|
@ -23,38 +20,6 @@
|
||||||
<ComponentsHierarchy screens={$store.screens} />
|
<ComponentsHierarchy screens={$store.screens} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if modalVisible}
|
<Modal bind:this={modal}>
|
||||||
<NewScreen bind:visible={modalVisible} />
|
<NewScreenModal />
|
||||||
{/if}
|
</Modal>
|
||||||
|
|
||||||
<style>
|
|
||||||
.newscreen {
|
|
||||||
cursor: pointer;
|
|
||||||
border: 1px solid var(--purple);
|
|
||||||
border-radius: 5px;
|
|
||||||
width: 100%;
|
|
||||||
height: 36px;
|
|
||||||
padding: 8px 16px;
|
|
||||||
margin: 20px 0px 12px 0px;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
background: var(--purple);
|
|
||||||
color: var(--white);
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 500;
|
|
||||||
transition: all 3ms;
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.newscreen:hover {
|
|
||||||
background: var(--purple-light);
|
|
||||||
color: var(--purple);
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon {
|
|
||||||
color: var(--ink);
|
|
||||||
font-size: 16px;
|
|
||||||
margin-right: 4px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
@ -1,14 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
import { store } from "builderStore"
|
import { store } from "builderStore"
|
||||||
import { pipe } from "components/common/core"
|
import { Input, Select, ModalContent } from "@budibase/bbui"
|
||||||
import { isRootComponent } from "./pagesParsing/searchComponents"
|
import { find, filter, some } from "lodash/fp"
|
||||||
import { splitName } from "./pagesParsing/splitRootComponentName.js"
|
|
||||||
import { Input, Select, Button, Spacer } from "@budibase/bbui"
|
|
||||||
import { Modal } from "components/common/Modal"
|
|
||||||
import { find, filter, some, map, includes } from "lodash/fp"
|
|
||||||
import { assign } from "lodash"
|
|
||||||
|
|
||||||
export let visible
|
|
||||||
|
|
||||||
let dialog
|
let dialog
|
||||||
let layoutComponents
|
let layoutComponents
|
||||||
|
@ -60,11 +53,7 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Modal
|
<ModalContent title="New Screen" confirmText="Create Screen" onConfirm={save}>
|
||||||
bind:visible
|
|
||||||
title="New Screen"
|
|
||||||
confirmText="Create Screen"
|
|
||||||
onConfirm={save}>
|
|
||||||
<Input label="Name" bind:value={name} />
|
<Input label="Name" bind:value={name} />
|
||||||
<Input
|
<Input
|
||||||
label="Url"
|
label="Url"
|
||||||
|
@ -76,4 +65,4 @@
|
||||||
<option value={_component}>{name}</option>
|
<option value={_component}>{name}</option>
|
||||||
{/each}
|
{/each}
|
||||||
</Select>
|
</Select>
|
||||||
</Modal>
|
</ModalContent>
|
|
@ -10,8 +10,6 @@
|
||||||
export let onStyleChanged = () => {}
|
export let onStyleChanged = () => {}
|
||||||
export let open = false
|
export let open = false
|
||||||
|
|
||||||
$: console.log(properties)
|
|
||||||
|
|
||||||
$: style = componentInstance["_styles"][styleCategory] || {}
|
$: style = componentInstance["_styles"][styleCategory] || {}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -4,19 +4,20 @@
|
||||||
import PageLayout from "./PageLayout.svelte"
|
import PageLayout from "./PageLayout.svelte"
|
||||||
import PagesList from "./PagesList.svelte"
|
import PagesList from "./PagesList.svelte"
|
||||||
import { store } from "builderStore"
|
import { store } from "builderStore"
|
||||||
import NewScreen from "./NewScreen.svelte"
|
import NewScreenModal from "./NewScreenModal.svelte"
|
||||||
import CurrentItemPreview from "./CurrentItemPreview.svelte"
|
import CurrentItemPreview from "./AppPreview/CurrentItemPreview.svelte"
|
||||||
import SettingsView from "./SettingsView.svelte"
|
import SettingsView from "./SettingsView.svelte"
|
||||||
import ComponentsPaneSwitcher from "./ComponentsPaneSwitcher.svelte"
|
import ComponentsPaneSwitcher from "./ComponentsPaneSwitcher.svelte"
|
||||||
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
||||||
import { last } from "lodash/fp"
|
import { last } from "lodash/fp"
|
||||||
import { AddIcon } from "components/common/Icons"
|
import { AddIcon } from "components/common/Icons"
|
||||||
|
import { Modal } from "@budibase/bbui"
|
||||||
|
|
||||||
let newScreenPicker
|
let newScreenPicker
|
||||||
let confirmDeleteDialog
|
let confirmDeleteDialog
|
||||||
let componentToDelete = ""
|
let componentToDelete = ""
|
||||||
let settingsView
|
let settingsView
|
||||||
let modalVisible = false
|
let modal
|
||||||
|
|
||||||
const settings = () => {
|
const settings = () => {
|
||||||
settingsView.show()
|
settingsView.show()
|
||||||
|
@ -26,30 +27,24 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="root">
|
<div class="root">
|
||||||
|
|
||||||
<div class="ui-nav">
|
<div class="ui-nav">
|
||||||
|
|
||||||
<div class="pages-list-container">
|
<div class="pages-list-container">
|
||||||
<div class="nav-header">
|
<div class="nav-header">
|
||||||
<span class="navigator-title">Navigator</span>
|
<span class="navigator-title">Navigator</span>
|
||||||
|
|
||||||
<span class="components-nav-page">Pages</span>
|
<span class="components-nav-page">Pages</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="nav-items-container">
|
<div class="nav-items-container">
|
||||||
<PagesList />
|
<PagesList />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<PageLayout layout={$store.pages[$store.currentPageName]} />
|
<PageLayout layout={$store.pages[$store.currentPageName]} />
|
||||||
|
|
||||||
<div class="components-list-container">
|
<div class="components-list-container">
|
||||||
<div class="nav-group-header">
|
<div class="nav-group-header">
|
||||||
<span class="components-nav-header" style="margin-top: 0;">
|
<span class="components-nav-header" style="margin-top: 0;">
|
||||||
Screens
|
Screens
|
||||||
</span>
|
</span>
|
||||||
<div>
|
<div>
|
||||||
<button on:click={() => (modalVisible = true)}>
|
<button on:click={modal.show}>
|
||||||
<AddIcon />
|
<AddIcon />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -58,24 +53,21 @@
|
||||||
<ComponentsHierarchy screens={$store.screens} />
|
<ComponentsHierarchy screens={$store.screens} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="preview-pane">
|
<div class="preview-pane">
|
||||||
<CurrentItemPreview />
|
<CurrentItemPreview />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if $store.currentFrontEndType === 'screen' || $store.currentFrontEndType === 'page'}
|
{#if $store.currentFrontEndType === 'screen' || $store.currentFrontEndType === 'page'}
|
||||||
<div class="components-pane">
|
<div class="components-pane">
|
||||||
<ComponentsPaneSwitcher />
|
<ComponentsPaneSwitcher />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if modalVisible}
|
<Modal bind:this={modal}>
|
||||||
<NewScreen bind:visible={modalVisible} />
|
<NewScreenModal />
|
||||||
{/if}
|
</Modal>
|
||||||
|
|
||||||
<SettingsView bind:this={settingsView} />
|
<SettingsView bind:this={settingsView} />
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
|
@ -127,8 +127,7 @@
|
||||||
.topnavitem {
|
.topnavitem {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
color: var(--grey-5);
|
color: var(--grey-5);
|
||||||
margin: 0px 00px 0px 20px;
|
margin: 0 0 0 20px;
|
||||||
padding-top: 4px;
|
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
font-size: var(--font-size-m);
|
font-size: var(--font-size-m);
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
@ -149,8 +148,7 @@
|
||||||
.topnavitemright {
|
.topnavitemright {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
color: var(--grey-7);
|
color: var(--grey-7);
|
||||||
margin: 0px 20px 0px 0px;
|
margin: 0 20px 0 0;
|
||||||
padding-top: 4px;
|
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
|
@ -3,5 +3,7 @@
|
||||||
import RelationshipDataTable from "components/backend/DataTable/RelationshipDataTable.svelte"
|
import RelationshipDataTable from "components/backend/DataTable/RelationshipDataTable.svelte"
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<RelationshipDataTable modelId={$params.selectedModel} recordId={$params.selectedRecord}
|
<RelationshipDataTable
|
||||||
fieldName={$params.selectedField}/>
|
modelId={$params.selectedModel}
|
||||||
|
recordId={$params.selectedRecord}
|
||||||
|
fieldName={decodeURI($params.selectedField)} />
|
||||||
|
|
|
@ -7,15 +7,14 @@
|
||||||
import { get } from "builderStore/api"
|
import { get } from "builderStore/api"
|
||||||
import Spinner from "components/common/Spinner.svelte"
|
import Spinner from "components/common/Spinner.svelte"
|
||||||
import CreateAppModal from "components/start/CreateAppModal.svelte"
|
import CreateAppModal from "components/start/CreateAppModal.svelte"
|
||||||
import { Button, Heading } from "@budibase/bbui"
|
import { Button, Heading, Modal } from "@budibase/bbui"
|
||||||
import TemplateList from "components/start/TemplateList.svelte"
|
import TemplateList from "components/start/TemplateList.svelte"
|
||||||
import analytics from "analytics"
|
import analytics from "analytics"
|
||||||
import { Modal } from "components/common/Modal"
|
|
||||||
|
|
||||||
let promise = getApps()
|
let promise = getApps()
|
||||||
let hasKey
|
let hasKey
|
||||||
let template
|
let template
|
||||||
let modalVisible = false
|
let modal
|
||||||
|
|
||||||
async function getApps() {
|
async function getApps() {
|
||||||
const res = await get("/api/applications")
|
const res = await get("/api/applications")
|
||||||
|
@ -42,13 +41,13 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!keys.budibase) {
|
if (!keys.budibase) {
|
||||||
modalVisible = true
|
modal.show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function selectTemplate(newTemplate) {
|
function selectTemplate(newTemplate) {
|
||||||
template = newTemplate
|
template = newTemplate
|
||||||
modalVisible = true
|
modal.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
checkIfKeysAndApps()
|
checkIfKeysAndApps()
|
||||||
|
@ -57,9 +56,7 @@
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<Heading medium black>Welcome to the Budibase Beta</Heading>
|
<Heading medium black>Welcome to the Budibase Beta</Heading>
|
||||||
<Button primary purple on:click={() => (modalVisible = true)}>
|
<Button primary purple on:click={modal.show}>Create New Web App</Button>
|
||||||
Create New Web App
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="banner">
|
<div class="banner">
|
||||||
|
@ -72,12 +69,12 @@
|
||||||
<TemplateList onSelect={selectTemplate} />
|
<TemplateList onSelect={selectTemplate} />
|
||||||
|
|
||||||
<AppList />
|
<AppList />
|
||||||
|
|
||||||
{#if modalVisible}
|
|
||||||
<CreateAppModal bind:visible={modalVisible} {hasKey} {template} />
|
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<Modal bind:this={modal} padding={false} width="600px">
|
||||||
|
<CreateAppModal {hasKey} {template} />
|
||||||
|
</Modal>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.container {
|
.container {
|
||||||
display: grid;
|
display: grid;
|
||||||
|
|
|
@ -709,13 +709,14 @@
|
||||||
lodash "^4.17.13"
|
lodash "^4.17.13"
|
||||||
to-fast-properties "^2.0.0"
|
to-fast-properties "^2.0.0"
|
||||||
|
|
||||||
"@budibase/bbui@^1.40.1":
|
"@budibase/bbui@^1.41.0":
|
||||||
version "1.40.1"
|
version "1.41.0"
|
||||||
resolved "https://registry.yarnpkg.com/@budibase/bbui/-/bbui-1.40.1.tgz#7ebfd52b4da822312d3395447a4f73caa41f8014"
|
resolved "https://registry.yarnpkg.com/@budibase/bbui/-/bbui-1.41.0.tgz#cb239db3071a4a6c6f0ef48ddde55f5eab9808ce"
|
||||||
integrity sha512-0t5Makyn5jOURKZIQPvd+8G4m6ps4GyfLUkAz5rKJSnAQSAgLiuZ+RihcEReDEJK8tnfW7h2echJTffJduQRRQ==
|
integrity sha512-pT5u6HDdXcylWgSE1TBt3jETg92GwgAXpUsBVqX+OUE/2lNbmThb8egAckpemHDvm91FAL0nApQYpV7c/qLzvw==
|
||||||
dependencies:
|
dependencies:
|
||||||
sirv-cli "^0.4.6"
|
sirv-cli "^0.4.6"
|
||||||
svelte-flatpickr "^2.4.0"
|
svelte-flatpickr "^2.4.0"
|
||||||
|
svelte-portal "^1.0.0"
|
||||||
|
|
||||||
"@budibase/colorpicker@^1.0.1":
|
"@budibase/colorpicker@^1.0.1":
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
|
@ -5844,6 +5845,11 @@ svelte-portal@^0.1.0:
|
||||||
version "0.1.0"
|
version "0.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/svelte-portal/-/svelte-portal-0.1.0.tgz#cc2821cc84b05ed5814e0218dcdfcbebc53c1742"
|
resolved "https://registry.yarnpkg.com/svelte-portal/-/svelte-portal-0.1.0.tgz#cc2821cc84b05ed5814e0218dcdfcbebc53c1742"
|
||||||
|
|
||||||
|
svelte-portal@^1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/svelte-portal/-/svelte-portal-1.0.0.tgz#36a47c5578b1a4d9b4dc60fa32a904640ec4cdd3"
|
||||||
|
integrity sha512-nHf+DS/jZ6jjnZSleBMSaZua9JlG5rZv9lOGKgJuaZStfevtjIlUJrkLc3vbV8QdBvPPVmvcjTlazAzfKu0v3Q==
|
||||||
|
|
||||||
svelte@^3.24.1:
|
svelte@^3.24.1:
|
||||||
version "3.25.1"
|
version "3.25.1"
|
||||||
resolved "https://registry.yarnpkg.com/svelte/-/svelte-3.25.1.tgz#218def1243fea5a97af6eb60f5e232315bb57ac4"
|
resolved "https://registry.yarnpkg.com/svelte/-/svelte-3.25.1.tgz#218def1243fea5a97af6eb60f5e232315bb57ac4"
|
||||||
|
|
|
@ -84,7 +84,7 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@jest/test-sequencer": "^24.8.0",
|
"@jest/test-sequencer": "^24.8.0",
|
||||||
"electron": "8.2.5",
|
"electron": "10.1.3",
|
||||||
"electron-builder": "^22.7.0",
|
"electron-builder": "^22.7.0",
|
||||||
"electron-builder-notarize": "^1.1.2",
|
"electron-builder-notarize": "^1.1.2",
|
||||||
"eslint": "^6.8.0",
|
"eslint": "^6.8.0",
|
||||||
|
|
|
@ -304,6 +304,16 @@ function coerceRecordValues(rec, model) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const TYPE_TRANSFORM_MAP = {
|
const TYPE_TRANSFORM_MAP = {
|
||||||
|
link: {
|
||||||
|
"": [],
|
||||||
|
[null]: [],
|
||||||
|
[undefined]: undefined,
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
"": "",
|
||||||
|
[null]: "",
|
||||||
|
[undefined]: undefined,
|
||||||
|
},
|
||||||
string: {
|
string: {
|
||||||
"": "",
|
"": "",
|
||||||
[null]: "",
|
[null]: "",
|
||||||
|
|
|
@ -172,10 +172,10 @@
|
||||||
lodash "^4.17.13"
|
lodash "^4.17.13"
|
||||||
to-fast-properties "^2.0.0"
|
to-fast-properties "^2.0.0"
|
||||||
|
|
||||||
"@budibase/client@^0.1.24":
|
"@budibase/client@^0.1.25":
|
||||||
version "0.1.24"
|
version "0.1.25"
|
||||||
resolved "https://registry.yarnpkg.com/@budibase/client/-/client-0.1.24.tgz#d2967c050af9f559791e0189137f80e621ea2d69"
|
resolved "https://registry.yarnpkg.com/@budibase/client/-/client-0.1.25.tgz#f08c4a614f9018eb0f0faa6d20bb05f7a3215c70"
|
||||||
integrity sha512-2Plu9PpF3TOPTDkAAIkPFEjZFolGkty0Sc0vbLk8lee4yqeonBj5paXT44O6kpxLFW47YjN5VCA4+EnwGl358w==
|
integrity sha512-vZ0cqJwLYcs7MHihFnJO3qOe7qxibnB4Va1+IYNfnPc9kcxy4KvfQxCx/G/DDxP9CXfEvsguy9ymzR3RUAvBHw==
|
||||||
dependencies:
|
dependencies:
|
||||||
deep-equal "^2.0.1"
|
deep-equal "^2.0.1"
|
||||||
mustache "^4.0.1"
|
mustache "^4.0.1"
|
||||||
|
@ -2042,9 +2042,10 @@ electron-updater@^4.3.1:
|
||||||
lodash.isequal "^4.5.0"
|
lodash.isequal "^4.5.0"
|
||||||
semver "^7.1.3"
|
semver "^7.1.3"
|
||||||
|
|
||||||
electron@8.2.5:
|
electron@10.1.3:
|
||||||
version "8.2.5"
|
version "10.1.3"
|
||||||
resolved "https://registry.yarnpkg.com/electron/-/electron-8.2.5.tgz#ae3cb23d5517b2189fd35298e487198d65d1a291"
|
resolved "https://registry.yarnpkg.com/electron/-/electron-10.1.3.tgz#7e276e373bf30078bd4cb1184850a91268dc0e6c"
|
||||||
|
integrity sha512-CR8LrlG47MdAp317SQ3vGYa2o2cIMdMSMPYH46OVitFLk35dwE9fn3VqvhUIXhCHYcNWIAPzMhkVHpkoFdKWuw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@electron/get" "^1.0.1"
|
"@electron/get" "^1.0.1"
|
||||||
"@types/node" "^12.0.12"
|
"@types/node" "^12.0.12"
|
||||||
|
|
|
@ -36,7 +36,7 @@
|
||||||
"gitHead": "284cceb9b703c38566c6e6363c022f79a08d5691",
|
"gitHead": "284cceb9b703c38566c6e6363c022f79a08d5691",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@beyonk/svelte-googlemaps": "^2.2.0",
|
"@beyonk/svelte-googlemaps": "^2.2.0",
|
||||||
"@budibase/bbui": "^1.40.1",
|
"@budibase/bbui": "^1.41.0",
|
||||||
"@fortawesome/fontawesome-free": "^5.14.0",
|
"@fortawesome/fontawesome-free": "^5.14.0",
|
||||||
"britecharts": "^2.16.1",
|
"britecharts": "^2.16.1",
|
||||||
"d3-selection": "^1.4.2",
|
"d3-selection": "^1.4.2",
|
||||||
|
|
|
@ -1,189 +1,10 @@
|
||||||
<script>
|
<script>
|
||||||
import { onMount } from "svelte"
|
import Form from "./Form.svelte"
|
||||||
import { fade } from "svelte/transition"
|
|
||||||
import {
|
|
||||||
Label,
|
|
||||||
DatePicker,
|
|
||||||
Input,
|
|
||||||
Select,
|
|
||||||
Button,
|
|
||||||
Toggle,
|
|
||||||
} from "@budibase/bbui"
|
|
||||||
import Dropzone from "./attachments/Dropzone.svelte"
|
|
||||||
import LinkedRecordSelector from "./LinkedRecordSelector.svelte"
|
|
||||||
import debounce from "lodash.debounce"
|
|
||||||
import ErrorsBox from "./ErrorsBox.svelte"
|
|
||||||
import { capitalise } from "./helpers"
|
|
||||||
|
|
||||||
export let _bb
|
export let _bb
|
||||||
export let model
|
export let model
|
||||||
export let title
|
export let title
|
||||||
export let buttonText
|
export let buttonText
|
||||||
|
|
||||||
const TYPE_MAP = {
|
|
||||||
string: "text",
|
|
||||||
boolean: "checkbox",
|
|
||||||
number: "number",
|
|
||||||
}
|
|
||||||
|
|
||||||
const DEFAULTS_FOR_TYPE = {
|
|
||||||
string: "",
|
|
||||||
boolean: false,
|
|
||||||
number: null,
|
|
||||||
link: [],
|
|
||||||
}
|
|
||||||
|
|
||||||
let record
|
|
||||||
let store = _bb.store
|
|
||||||
let schema = {}
|
|
||||||
let modelDef = {}
|
|
||||||
let saved = false
|
|
||||||
let recordId
|
|
||||||
let isNew = true
|
|
||||||
let errors = {}
|
|
||||||
|
|
||||||
$: fields = schema ? Object.keys(schema) : []
|
|
||||||
$: if (model && model.length !== 0) {
|
|
||||||
fetchModel()
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchModel() {
|
|
||||||
const FETCH_MODEL_URL = `/api/models/${model}`
|
|
||||||
const response = await _bb.api.get(FETCH_MODEL_URL)
|
|
||||||
modelDef = await response.json()
|
|
||||||
schema = modelDef.schema
|
|
||||||
record = {
|
|
||||||
modelId: model,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const save = debounce(async () => {
|
|
||||||
for (let field of fields) {
|
|
||||||
// Assign defaults to empty fields to prevent validation issues
|
|
||||||
if (!(field in record)) {
|
|
||||||
record[field] = DEFAULTS_FOR_TYPE[schema[field].type]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const SAVE_RECORD_URL = `/api/${model}/records`
|
|
||||||
const response = await _bb.api.post(SAVE_RECORD_URL, record)
|
|
||||||
|
|
||||||
const json = await response.json()
|
|
||||||
|
|
||||||
if (response.status === 200) {
|
|
||||||
store.update(state => {
|
|
||||||
state[model] = state[model] ? [...state[model], json] : [json]
|
|
||||||
return state
|
|
||||||
})
|
|
||||||
|
|
||||||
errors = {}
|
|
||||||
|
|
||||||
// wipe form, if new record, otherwise update
|
|
||||||
// model to get new _rev
|
|
||||||
record = isNew ? { modelId: model } : json
|
|
||||||
|
|
||||||
// set saved, and unset after 1 second
|
|
||||||
// i.e. make the success notifier appear, then disappear again after time
|
|
||||||
saved = true
|
|
||||||
setTimeout(() => {
|
|
||||||
saved = false
|
|
||||||
}, 3000)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response.status === 400) {
|
|
||||||
errors = Object.keys(json.errors)
|
|
||||||
.map(k => ({ dataPath: k, message: json.errors[k] }))
|
|
||||||
.flat()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
onMount(async () => {
|
|
||||||
const routeParams = _bb.routeParams()
|
|
||||||
recordId =
|
|
||||||
Object.keys(routeParams).length > 0 && (routeParams.id || routeParams[0])
|
|
||||||
isNew = !recordId || recordId === "new"
|
|
||||||
|
|
||||||
if (isNew) {
|
|
||||||
record = { modelId: model }
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const GET_RECORD_URL = `/api/${model}/records/${recordId}`
|
|
||||||
const response = await _bb.api.get(GET_RECORD_URL)
|
|
||||||
record = await response.json()
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<form class="form" on:submit|preventDefault>
|
<Form {_bb} {model} {title} {buttonText} wide={false} />
|
||||||
{#if title}
|
|
||||||
<h1>{title}</h1>
|
|
||||||
{/if}
|
|
||||||
<div class="form-content">
|
|
||||||
<ErrorsBox {errors} />
|
|
||||||
{#each fields as field}
|
|
||||||
{#if schema[field].type === 'options'}
|
|
||||||
<Select
|
|
||||||
secondary
|
|
||||||
label={capitalise(schema[field].name)}
|
|
||||||
bind:value={record[field]}>
|
|
||||||
<option value="">Choose an option</option>
|
|
||||||
{#each schema[field].constraints.inclusion as opt}
|
|
||||||
<option>{opt}</option>
|
|
||||||
{/each}
|
|
||||||
</Select>
|
|
||||||
{:else if schema[field].type === 'datetime'}
|
|
||||||
<DatePicker
|
|
||||||
label={capitalise(schema[field].name)}
|
|
||||||
bind:value={record[field]} />
|
|
||||||
{:else if schema[field].type === 'boolean'}
|
|
||||||
<Toggle
|
|
||||||
text={capitalise(schema[field].name)}
|
|
||||||
bind:checked={record[field]} />
|
|
||||||
{:else if schema[field].type === 'number'}
|
|
||||||
<Input
|
|
||||||
label={capitalise(schema[field].name)}
|
|
||||||
type="number"
|
|
||||||
bind:value={record[field]} />
|
|
||||||
{:else if schema[field].type === 'string'}
|
|
||||||
<Input
|
|
||||||
label={capitalise(schema[field].name)}
|
|
||||||
bind:value={record[field]} />
|
|
||||||
{:else if schema[field].type === 'attachment'}
|
|
||||||
<div>
|
|
||||||
<Label extraSmall grey>{schema[field].name}</Label>
|
|
||||||
<Dropzone bind:files={record[field]} />
|
|
||||||
</div>
|
|
||||||
{:else if schema[field].type === 'link'}
|
|
||||||
<LinkedRecordSelector
|
|
||||||
secondary
|
|
||||||
bind:linkedRecords={record[field]}
|
|
||||||
schema={schema[field]} />
|
|
||||||
{/if}
|
|
||||||
{/each}
|
|
||||||
<div class="buttons">
|
|
||||||
<Button primary on:click={save} green={saved}>
|
|
||||||
{#if saved}Success{:else}{buttonText || 'Submit Form'}{/if}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.form {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-content {
|
|
||||||
margin-bottom: var(--spacing-xl);
|
|
||||||
display: grid;
|
|
||||||
gap: var(--spacing-xl);
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.buttons {
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
@ -1,252 +1,10 @@
|
||||||
<script>
|
<script>
|
||||||
import { onMount } from "svelte"
|
import Form from "./Form.svelte"
|
||||||
import { fade } from "svelte/transition"
|
|
||||||
import { Label, DatePicker } from "@budibase/bbui"
|
|
||||||
import debounce from "lodash.debounce"
|
|
||||||
|
|
||||||
export let _bb
|
export let _bb
|
||||||
export let model
|
export let model
|
||||||
export let title
|
export let title
|
||||||
export let buttonText
|
export let buttonText
|
||||||
|
|
||||||
const TYPE_MAP = {
|
|
||||||
string: "text",
|
|
||||||
boolean: "checkbox",
|
|
||||||
number: "number",
|
|
||||||
}
|
|
||||||
|
|
||||||
const DEFAULTS_FOR_TYPE = {
|
|
||||||
string: "",
|
|
||||||
boolean: false,
|
|
||||||
number: null,
|
|
||||||
link: [],
|
|
||||||
}
|
|
||||||
|
|
||||||
let record
|
|
||||||
let store = _bb.store
|
|
||||||
let schema = {}
|
|
||||||
let modelDef = {}
|
|
||||||
let saved = false
|
|
||||||
let recordId
|
|
||||||
let isNew = true
|
|
||||||
let errors = {}
|
|
||||||
|
|
||||||
$: if (model && model.length !== 0) {
|
|
||||||
fetchModel()
|
|
||||||
}
|
|
||||||
|
|
||||||
$: fields = schema ? Object.keys(schema) : []
|
|
||||||
|
|
||||||
$: errorMessages = Object.entries(errors).map(
|
|
||||||
([field, message]) => `${field} ${message}`
|
|
||||||
)
|
|
||||||
|
|
||||||
async function fetchModel() {
|
|
||||||
const FETCH_MODEL_URL = `/api/models/${model}`
|
|
||||||
const response = await _bb.api.get(FETCH_MODEL_URL)
|
|
||||||
modelDef = await response.json()
|
|
||||||
schema = modelDef.schema
|
|
||||||
record = {
|
|
||||||
modelId: model,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const save = debounce(async () => {
|
|
||||||
for (let field of fields) {
|
|
||||||
// Assign defaults to empty fields to prevent validation issues
|
|
||||||
if (!(field in record))
|
|
||||||
record[field] = DEFAULTS_FOR_TYPE[schema[field].type]
|
|
||||||
}
|
|
||||||
|
|
||||||
const SAVE_RECORD_URL = `/api/${model}/records`
|
|
||||||
const response = await _bb.api.post(SAVE_RECORD_URL, record)
|
|
||||||
|
|
||||||
const json = await response.json()
|
|
||||||
|
|
||||||
if (response.status === 200) {
|
|
||||||
store.update(state => {
|
|
||||||
state[model] = state[model] ? [...state[model], json] : [json]
|
|
||||||
return state
|
|
||||||
})
|
|
||||||
|
|
||||||
errors = {}
|
|
||||||
|
|
||||||
// wipe form, if new record, otherwise update
|
|
||||||
// model to get new _rev
|
|
||||||
record = isNew ? { modelId: model } : json
|
|
||||||
|
|
||||||
// set saved, and unset after 1 second
|
|
||||||
// i.e. make the success notifier appear, then disappear again after time
|
|
||||||
saved = true
|
|
||||||
setTimeout(() => {
|
|
||||||
saved = false
|
|
||||||
}, 1000)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response.status === 400) {
|
|
||||||
errors = json.errors
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
onMount(async () => {
|
|
||||||
const routeParams = _bb.routeParams()
|
|
||||||
recordId =
|
|
||||||
Object.keys(routeParams).length > 0 && (routeParams.id || routeParams[0])
|
|
||||||
isNew = !recordId || recordId === "new"
|
|
||||||
|
|
||||||
if (isNew) {
|
|
||||||
record = { modelId: model }
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const GET_RECORD_URL = `/api/${model}/records/${recordId}`
|
|
||||||
const response = await _bb.api.get(GET_RECORD_URL)
|
|
||||||
const json = await response.json()
|
|
||||||
record = json
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<form class="form" on:submit|preventDefault>
|
<Form {_bb} {model} {title} {buttonText} wide={true} />
|
||||||
{#if title}
|
|
||||||
<h1>{title}</h1>
|
|
||||||
{/if}
|
|
||||||
{#each errorMessages as error}
|
|
||||||
<p class="error">{error}</p>
|
|
||||||
{/each}
|
|
||||||
<hr />
|
|
||||||
<div class="form-content">
|
|
||||||
{#each fields as field}
|
|
||||||
<div class="form-item">
|
|
||||||
<Label small forAttr={'form-stacked-text'}>{field}</Label>
|
|
||||||
{#if schema[field].type === 'string' && schema[field].constraints.inclusion}
|
|
||||||
<select bind:value={record[field]}>
|
|
||||||
{#each schema[field].constraints.inclusion as opt}
|
|
||||||
<option>{opt}</option>
|
|
||||||
{/each}
|
|
||||||
</select>
|
|
||||||
{:else if schema[field].type === 'datetime'}
|
|
||||||
<DatePicker bind:value={record[field]} />
|
|
||||||
{:else if schema[field].type === 'boolean'}
|
|
||||||
<input class="input" type="checkbox" bind:checked={record[field]} />
|
|
||||||
{:else if schema[field].type === 'number'}
|
|
||||||
<input class="input" type="number" bind:value={record[field]} />
|
|
||||||
{:else if schema[field].type === 'string'}
|
|
||||||
<input class="input" type="text" bind:value={record[field]} />
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
<hr />
|
|
||||||
{/each}
|
|
||||||
<div class="button-block">
|
|
||||||
<button on:click={save} class:saved>
|
|
||||||
{#if saved}
|
|
||||||
<div in:fade>
|
|
||||||
<span class:saved>Success</span>
|
|
||||||
</div>
|
|
||||||
{:else}
|
|
||||||
<div>{buttonText || 'Submit Form'}</div>
|
|
||||||
{/if}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.form {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
margin: auto;
|
|
||||||
padding: 40px;
|
|
||||||
}
|
|
||||||
.form-content {
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
.input {
|
|
||||||
border-radius: 5px;
|
|
||||||
border: 1px solid #e6e6e6;
|
|
||||||
padding: 1em;
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-item {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 30% 1fr;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
align-items: center;
|
|
||||||
gap: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
hr {
|
|
||||||
border: 1px solid #f5f5f5;
|
|
||||||
margin: 40px 0px;
|
|
||||||
}
|
|
||||||
hr:nth-last-child(2) {
|
|
||||||
border: 1px solid #f5f5f5;
|
|
||||||
margin: 40px 0px;
|
|
||||||
}
|
|
||||||
.button-block {
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
}
|
|
||||||
button {
|
|
||||||
font-size: 16px;
|
|
||||||
padding: 8px 16px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
border-radius: 4px;
|
|
||||||
color: white;
|
|
||||||
background-color: black;
|
|
||||||
outline: none;
|
|
||||||
height: 40px;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.2s ease 0s;
|
|
||||||
overflow: hidden;
|
|
||||||
outline: none;
|
|
||||||
user-select: none;
|
|
||||||
white-space: nowrap;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
button.saved {
|
|
||||||
background-color: #84c991;
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
button:hover {
|
|
||||||
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1),
|
|
||||||
0 4px 6px -2px rgba(0, 0, 0, 0.05);
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type="checkbox"] {
|
|
||||||
transform: scale(2);
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
select::-ms-expand {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
cursor: pointer;
|
|
||||||
display: inline-block;
|
|
||||||
align-items: baseline;
|
|
||||||
box-sizing: border-box;
|
|
||||||
padding: 1em 1em;
|
|
||||||
border: 1px solid #eaeaea;
|
|
||||||
border-radius: 5px;
|
|
||||||
font: inherit;
|
|
||||||
line-height: inherit;
|
|
||||||
-webkit-appearance: none;
|
|
||||||
-moz-appearance: none;
|
|
||||||
-ms-appearance: none;
|
|
||||||
appearance: none;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),
|
|
||||||
linear-gradient(135deg, currentColor 50%, transparent 50%);
|
|
||||||
background-position: right 17px top 1.5em, right 10px top 1.5em;
|
|
||||||
background-size: 7px 7px, 7px 7px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error {
|
|
||||||
color: red;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
@ -0,0 +1,197 @@
|
||||||
|
<script>
|
||||||
|
import { onMount } from "svelte"
|
||||||
|
import { fade } from "svelte/transition"
|
||||||
|
import {
|
||||||
|
Label,
|
||||||
|
DatePicker,
|
||||||
|
Input,
|
||||||
|
Select,
|
||||||
|
Button,
|
||||||
|
Toggle,
|
||||||
|
} from "@budibase/bbui"
|
||||||
|
import Dropzone from "./attachments/Dropzone.svelte"
|
||||||
|
import LinkedRecordSelector from "./LinkedRecordSelector.svelte"
|
||||||
|
import debounce from "lodash.debounce"
|
||||||
|
import ErrorsBox from "./ErrorsBox.svelte"
|
||||||
|
import { capitalise } from "./helpers"
|
||||||
|
|
||||||
|
export let _bb
|
||||||
|
export let model
|
||||||
|
export let title
|
||||||
|
export let buttonText
|
||||||
|
export let wide = false
|
||||||
|
|
||||||
|
const TYPE_MAP = {
|
||||||
|
string: "text",
|
||||||
|
boolean: "checkbox",
|
||||||
|
number: "number",
|
||||||
|
}
|
||||||
|
|
||||||
|
const DEFAULTS_FOR_TYPE = {
|
||||||
|
string: "",
|
||||||
|
boolean: false,
|
||||||
|
number: null,
|
||||||
|
link: [],
|
||||||
|
}
|
||||||
|
|
||||||
|
let record
|
||||||
|
let store = _bb.store
|
||||||
|
let schema = {}
|
||||||
|
let modelDef = {}
|
||||||
|
let saved = false
|
||||||
|
let recordId
|
||||||
|
let isNew = true
|
||||||
|
let errors = {}
|
||||||
|
|
||||||
|
$: fields = schema ? Object.keys(schema) : []
|
||||||
|
$: if (model && model.length !== 0) {
|
||||||
|
fetchModel()
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchModel() {
|
||||||
|
const FETCH_MODEL_URL = `/api/models/${model}`
|
||||||
|
const response = await _bb.api.get(FETCH_MODEL_URL)
|
||||||
|
modelDef = await response.json()
|
||||||
|
schema = modelDef.schema
|
||||||
|
record = {
|
||||||
|
modelId: model,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const save = debounce(async () => {
|
||||||
|
for (let field of fields) {
|
||||||
|
// Assign defaults to empty fields to prevent validation issues
|
||||||
|
if (!(field in record)) {
|
||||||
|
record[field] = DEFAULTS_FOR_TYPE[schema[field].type]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const SAVE_RECORD_URL = `/api/${model}/records`
|
||||||
|
const response = await _bb.api.post(SAVE_RECORD_URL, record)
|
||||||
|
|
||||||
|
const json = await response.json()
|
||||||
|
|
||||||
|
if (response.status === 200) {
|
||||||
|
store.update(state => {
|
||||||
|
state[model] = state[model] ? [...state[model], json] : [json]
|
||||||
|
return state
|
||||||
|
})
|
||||||
|
|
||||||
|
errors = {}
|
||||||
|
|
||||||
|
// wipe form, if new record, otherwise update
|
||||||
|
// model to get new _rev
|
||||||
|
record = isNew ? { modelId: model } : json
|
||||||
|
|
||||||
|
// set saved, and unset after 1 second
|
||||||
|
// i.e. make the success notifier appear, then disappear again after time
|
||||||
|
saved = true
|
||||||
|
setTimeout(() => {
|
||||||
|
saved = false
|
||||||
|
}, 3000)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.status === 400) {
|
||||||
|
errors = Object.keys(json.errors)
|
||||||
|
.map(k => ({ dataPath: k, message: json.errors[k] }))
|
||||||
|
.flat()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
const routeParams = _bb.routeParams()
|
||||||
|
recordId =
|
||||||
|
Object.keys(routeParams).length > 0 && (routeParams.id || routeParams[0])
|
||||||
|
isNew = !recordId || recordId === "new"
|
||||||
|
|
||||||
|
if (isNew) {
|
||||||
|
record = { modelId: model }
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const GET_RECORD_URL = `/api/${model}/records/${recordId}`
|
||||||
|
const response = await _bb.api.get(GET_RECORD_URL)
|
||||||
|
record = await response.json()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<form class="form" on:submit|preventDefault>
|
||||||
|
{#if title}
|
||||||
|
<h1>{title}</h1>
|
||||||
|
{/if}
|
||||||
|
<div class="form-content">
|
||||||
|
<ErrorsBox {errors} />
|
||||||
|
{#each fields as field}
|
||||||
|
<div class="form-field" class:wide>
|
||||||
|
{#if !(schema[field].type === 'boolean' && !wide)}
|
||||||
|
<Label extraSmall={!wide} grey={!wide}>
|
||||||
|
{capitalise(schema[field].name)}
|
||||||
|
</Label>
|
||||||
|
{/if}
|
||||||
|
{#if schema[field].type === 'options'}
|
||||||
|
<Select secondary bind:value={record[field]}>
|
||||||
|
<option value="">Choose an option</option>
|
||||||
|
{#each schema[field].constraints.inclusion as opt}
|
||||||
|
<option>{opt}</option>
|
||||||
|
{/each}
|
||||||
|
</Select>
|
||||||
|
{:else if schema[field].type === 'datetime'}
|
||||||
|
<DatePicker bind:value={record[field]} />
|
||||||
|
{:else if schema[field].type === 'boolean'}
|
||||||
|
<Toggle
|
||||||
|
text={wide ? null : capitalise(schema[field].name)}
|
||||||
|
bind:checked={record[field]} />
|
||||||
|
{:else if schema[field].type === 'number'}
|
||||||
|
<Input type="number" bind:value={record[field]} />
|
||||||
|
{:else if schema[field].type === 'string'}
|
||||||
|
<Input bind:value={record[field]} />
|
||||||
|
{:else if schema[field].type === 'attachment'}
|
||||||
|
<Dropzone bind:files={record[field]} />
|
||||||
|
{:else if schema[field].type === 'link'}
|
||||||
|
<LinkedRecordSelector
|
||||||
|
secondary
|
||||||
|
showLabel={false}
|
||||||
|
bind:linkedRecords={record[field]}
|
||||||
|
schema={schema[field]} />
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
<div class="buttons">
|
||||||
|
<Button primary on:click={save} green={saved}>
|
||||||
|
{#if saved}Success{:else}{buttonText || 'Submit Form'}{/if}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-content {
|
||||||
|
margin-bottom: var(--spacing-xl);
|
||||||
|
display: grid;
|
||||||
|
gap: var(--spacing-xl);
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-field {
|
||||||
|
display: grid;
|
||||||
|
}
|
||||||
|
.form-field.wide {
|
||||||
|
align-items: center;
|
||||||
|
grid-template-columns: 30% 1fr;
|
||||||
|
gap: var(--spacing-xl);
|
||||||
|
}
|
||||||
|
.form-field.wide :global(label) {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttons {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
</style>
|
Loading…
Reference in New Issue