mirror of https://github.com/Budibase/budibase.git
38 changed files with 1837 additions and 1971 deletions
@ -1,22 +0,0 @@ |
|||
xcontext('Create a Model', () => { |
|||
|
|||
before(() => { |
|||
cy.visit('localhost:4001/_builder') |
|||
// https://on.cypress.io/type
|
|||
cy.createApp('Model App', 'Model App Description') |
|||
}) |
|||
|
|||
// https://on.cypress.io/interacting-with-elements
|
|||
it('should create a new model', () => { |
|||
|
|||
cy.createModel('dog', 'name', 'age') |
|||
|
|||
// Check if model exists
|
|||
cy.get('.title').should('have.text', 'dog') |
|||
}) |
|||
it('should add a record', () => { |
|||
cy.addRecord('bob', '15') |
|||
|
|||
cy.contains('bob').should('have.text', 'bob') |
|||
}) |
|||
}) |
|||
@ -0,0 +1,70 @@ |
|||
context('Create a Table', () => { |
|||
before(() => { |
|||
cy.visit('localhost:4001/_builder') |
|||
cy.createApp('Table App', 'Table App Description') |
|||
}) |
|||
|
|||
it('should create a new Table', () => { |
|||
cy.createTable('dog') |
|||
|
|||
// Check if Table exists
|
|||
cy.get('.title').should('have.text', 'dog') |
|||
}) |
|||
|
|||
it('adds a new column to the table', () => { |
|||
cy.addColumn('dog', 'name', 'Plain Text') |
|||
|
|||
cy.contains('name').should("be.visible") |
|||
}) |
|||
|
|||
it('creates a record in the table', () => { |
|||
cy.addRecord(["Rover"]) |
|||
|
|||
cy.contains('Rover').should("be.visible") |
|||
}) |
|||
|
|||
it('updates a column on the table', () => { |
|||
cy.contains("name").click() |
|||
cy.get("[data-cy='edit-column-header']").click() |
|||
|
|||
cy.get("[placeholder=Name]").type("updated") |
|||
cy.get("select").select("Plain Text") |
|||
|
|||
cy.contains("Save Column").click() |
|||
|
|||
cy.contains('nameupdated').should('have.text', 'nameupdated ') |
|||
}) |
|||
|
|||
it('edits a record', () => { |
|||
cy.get("tbody .ri-more-line").click() |
|||
cy.get("[data-cy=edit-row]").click() |
|||
cy.get(".actions input").type("updatedRecord") |
|||
cy.contains("Save").click() |
|||
|
|||
cy.contains('updatedRecord').should('have.text', 'updatedRecord') |
|||
}) |
|||
|
|||
it('deletes a record', () => { |
|||
cy.get("tbody .ri-more-line").click() |
|||
cy.get("[data-cy=delete-row]").click() |
|||
cy.get(".modal-actions").contains("Delete").click() |
|||
|
|||
cy.contains('updatedRecord').should('not.exist') |
|||
}) |
|||
|
|||
it('deletes a column', () => { |
|||
cy.contains("name").click() |
|||
cy.get("[data-cy='delete-column-header']").click() |
|||
|
|||
cy.contains('nameupdated').should('not.exist') |
|||
}) |
|||
|
|||
it('deletes a table', () => { |
|||
cy.contains("div", "dog").get(".ri-more-line").click() |
|||
cy.get("[data-cy=delete-table]").click() |
|||
cy.get(".modal-actions").contains("Delete").click() |
|||
|
|||
cy.contains('dog').should('not.exist') |
|||
}) |
|||
|
|||
}) |
|||
@ -1,40 +0,0 @@ |
|||
<script> |
|||
import { store } from "builderStore" |
|||
import Modal from "components/common/Modal.svelte" |
|||
import ActionButton from "components/common/ActionButton.svelte" |
|||
import * as api from "../api" |
|||
|
|||
export let onClosed |
|||
|
|||
let databaseName |
|||
|
|||
async function createDatabase() { |
|||
const response = await api.createDatabase($store.appId, databaseName) |
|||
store.createDatabaseForApp(response) |
|||
onClosed() |
|||
} |
|||
</script> |
|||
|
|||
<div> |
|||
<section> |
|||
Database Name |
|||
<input class="uk-input" type="text" bind:value={databaseName} /> |
|||
</section> |
|||
<footer> |
|||
<ActionButton alert on:click={onClosed}>Cancel</ActionButton> |
|||
<ActionButton disabled={!databaseName} on:click={createDatabase}> |
|||
Save |
|||
</ActionButton> |
|||
</footer> |
|||
</div> |
|||
|
|||
<style> |
|||
section { |
|||
padding: 30px; |
|||
} |
|||
footer { |
|||
padding: 20px; |
|||
background: var(--grey-1); |
|||
border-radius: 0.5rem; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,140 @@ |
|||
<script> |
|||
import { onMount } from "svelte" |
|||
import { Input, TextArea, Button, Select } from "@budibase/bbui" |
|||
import { store, backendUiStore } from "builderStore" |
|||
import { FIELDS } from "constants/backend" |
|||
import { notifier } from "builderStore/store/notifications" |
|||
import Dropdown from "components/common/Dropdown.svelte" |
|||
import Textbox from "components/common/Textbox.svelte" |
|||
import ButtonGroup from "components/common/ButtonGroup.svelte" |
|||
import NumberBox from "components/common/NumberBox.svelte" |
|||
import ValuesList from "components/common/ValuesList.svelte" |
|||
import ErrorsBox from "components/common/ErrorsBox.svelte" |
|||
import Checkbox from "components/common/Checkbox.svelte" |
|||
import ActionButton from "components/common/ActionButton.svelte" |
|||
import DatePicker from "components/common/DatePicker.svelte" |
|||
import LinkedRecordSelector from "components/common/LinkedRecordSelector.svelte" |
|||
import * as api from "../api" |
|||
|
|||
export let onClosed |
|||
export let field = {} |
|||
|
|||
let originalName = field.name |
|||
|
|||
$: required = |
|||
field.constraints && |
|||
field.constraints.presence && |
|||
!field.constraints.presence.allowEmpty |
|||
$: if (field.type) { |
|||
field.constraints = FIELDS[field.type.toUpperCase()].constraints |
|||
} |
|||
|
|||
async function saveColumn() { |
|||
backendUiStore.update(state => { |
|||
backendUiStore.actions.models.saveField({ |
|||
originalName, |
|||
field, |
|||
}) |
|||
|
|||
return state |
|||
}) |
|||
onClosed() |
|||
} |
|||
</script> |
|||
|
|||
<div class="actions"> |
|||
<Input placeholder="Name" thin bind:value={field.name} /> |
|||
|
|||
<Select secondary thin bind:value={field.type}> |
|||
{#each Object.values(FIELDS) as field} |
|||
<option value={field.type}>{field.name}</option> |
|||
{/each} |
|||
</Select> |
|||
|
|||
<div class="info"> |
|||
<div class="field"> |
|||
<label>Required</label> |
|||
<input |
|||
type="checkbox" |
|||
bind:checked={required} |
|||
on:change={() => (field.constraints.presence.allowEmpty = required)} /> |
|||
</div> |
|||
|
|||
{#if field.type === 'string'} |
|||
<NumberBox |
|||
label="Max Length" |
|||
bind:value={field.constraints.length.maximum} /> |
|||
<ValuesList |
|||
label="Categories" |
|||
bind:values={field.constraints.inclusion} /> |
|||
{:else if field.type === 'datetime'} |
|||
<DatePicker |
|||
label="Min Value" |
|||
bind:value={field.constraints.datetime.earliest} /> |
|||
<DatePicker |
|||
label="Max Value" |
|||
bind:value={field.constraints.datetime.latest} /> |
|||
{:else if field.type === 'number'} |
|||
<NumberBox |
|||
label="Min Value" |
|||
bind:value={field.constraints.numericality.greaterThanOrEqualTo} /> |
|||
<NumberBox |
|||
label="Max Value" |
|||
bind:value={field.constraints.numericality.lessThanOrEqualTo} /> |
|||
{:else if field.type === 'link'} |
|||
<div class="field"> |
|||
<label>Link</label> |
|||
<select class="budibase__input" bind:value={field.modelId}> |
|||
<option value={''} /> |
|||
{#each $backendUiStore.models as model} |
|||
{#if model._id !== $backendUiStore.draftModel._id} |
|||
<option value={model._id}>{model.name}</option> |
|||
{/if} |
|||
{/each} |
|||
</select> |
|||
</div> |
|||
{/if} |
|||
</div> |
|||
|
|||
</div> |
|||
<footer> |
|||
<div class="button-margin-3"> |
|||
<Button secondary on:click={onClosed}>Cancel</Button> |
|||
</div> |
|||
<div class="button-margin-4"> |
|||
<Button primary on:click={saveColumn}>Save Column</Button> |
|||
</div> |
|||
</footer> |
|||
|
|||
<style> |
|||
.actions { |
|||
padding: var(--spacing-l) var(--spacing-xl); |
|||
display: grid; |
|||
grid-gap: var(--spacing-xl); |
|||
} |
|||
|
|||
footer { |
|||
padding: 20px 30px; |
|||
display: grid; |
|||
grid-template-columns: 1fr 1fr 1fr 1fr; |
|||
gap: 20px; |
|||
background: var(--grey-1); |
|||
border-bottom-left-radius: 0.5rem; |
|||
border-bottom-left-radius: 0.5rem; |
|||
} |
|||
|
|||
.field { |
|||
margin-top: var(--spacing-xl); |
|||
margin-bottom: var(--spacing-xl); |
|||
} |
|||
|
|||
.button-margin-3 { |
|||
grid-column-start: 3; |
|||
display: grid; |
|||
} |
|||
|
|||
.button-margin-4 { |
|||
grid-column-start: 4; |
|||
display: grid; |
|||
} |
|||
</style> |
|||
@ -1,165 +0,0 @@ |
|||
<script> |
|||
import { tick } from "svelte" |
|||
import Textbox from "components/common/Textbox.svelte" |
|||
import Button from "components/common/Button.svelte" |
|||
import Select from "components/common/Select.svelte" |
|||
import ActionButton from "components/common/ActionButton.svelte" |
|||
import getIcon from "components/common/icon" |
|||
import FieldView from "./FieldView.svelte" |
|||
import api from "builderStore/api" |
|||
import { store, backendUiStore } from "builderStore" |
|||
import { pipe } from "components/common/core" |
|||
import ErrorsBox from "components/common/ErrorsBox.svelte" |
|||
|
|||
export let model = { schema: {} } |
|||
export let onClosed |
|||
|
|||
let showFieldView = false |
|||
let fieldToEdit |
|||
|
|||
$: modelFields = model.schema ? Object.entries(model.schema) : [] |
|||
|
|||
function editField() {} |
|||
|
|||
function deleteField() {} |
|||
|
|||
function onFinishedFieldEdit() {} |
|||
|
|||
async function saveModel() { |
|||
const SAVE_MODEL_URL = `/api/models` |
|||
const response = await api.post(SAVE_MODEL_URL, model) |
|||
const newModel = await response.json() |
|||
backendUiStore.actions.models.create(newModel) |
|||
onClosed() |
|||
} |
|||
</script> |
|||
|
|||
<div class="heading"> |
|||
{#if !showFieldView} |
|||
<i class="ri-list-settings-line button--toggled" /> |
|||
<h3 class="budibase__title--3">Create / Edit Table</h3> |
|||
{:else} |
|||
<i class="ri-file-list-line button--toggled" /> |
|||
<h3 class="budibase__title--3">Create / Edit Field</h3> |
|||
{/if} |
|||
</div> |
|||
{#if !showFieldView} |
|||
<div class="padding"> |
|||
{#if $store.errors && $store.errors.length > 0} |
|||
<ErrorsBox errors={$store.errors} /> |
|||
{/if} |
|||
<div class="textbox"> |
|||
<Textbox label="Name" bind:text={model.name} /> |
|||
</div> |
|||
<div class="table-controls"> |
|||
<span class="label">Fields</span> |
|||
<div |
|||
data-cy="add-new-model-field" |
|||
class="hoverable new-field" |
|||
on:click={() => (showFieldView = true)}> |
|||
Add new field |
|||
</div> |
|||
</div> |
|||
|
|||
<table class="uk-table fields-table budibase__table"> |
|||
<thead> |
|||
<tr> |
|||
<th>Edit</th> |
|||
<th>Name</th> |
|||
<th>Type</th> |
|||
<th /> |
|||
</tr> |
|||
</thead> |
|||
<tbody> |
|||
{#each modelFields as [key, meta]} |
|||
<tr> |
|||
<td> |
|||
<i class="ri-more-line" on:click={() => editField(meta)} /> |
|||
</td> |
|||
<td> |
|||
<div>{key}</div> |
|||
</td> |
|||
<td>{meta.type}</td> |
|||
<td> |
|||
<i |
|||
class="ri-delete-bin-6-line hoverable" |
|||
on:click={() => deleteField(meta)} /> |
|||
</td> |
|||
</tr> |
|||
{/each} |
|||
</tbody> |
|||
</table> |
|||
<footer> |
|||
<ActionButton color="secondary" on:click={saveModel}>Save</ActionButton> |
|||
</footer> |
|||
</div> |
|||
{:else} |
|||
<FieldView |
|||
field={fieldToEdit} |
|||
onFinished={onFinishedFieldEdit} |
|||
schema={model.schema} |
|||
goBack={() => (showFieldView = false)} /> |
|||
{/if} |
|||
|
|||
<style> |
|||
.padding { |
|||
padding-top: 40px; |
|||
} |
|||
|
|||
.label { |
|||
font-size: 14px; |
|||
font-weight: 500; |
|||
} |
|||
|
|||
.textbox { |
|||
margin: 0px 40px 0px 40px; |
|||
font-size: 14px; |
|||
font-weight: 500; |
|||
} |
|||
|
|||
.new-field { |
|||
font-size: 16px; |
|||
font-weight: bold; |
|||
color: var(--blue); |
|||
} |
|||
|
|||
.fields-table { |
|||
margin: 8px 40px 0px 40px; |
|||
border-collapse: collapse; |
|||
width: 88%; |
|||
} |
|||
|
|||
tbody > tr:hover { |
|||
background-color: var(--grey-1); |
|||
} |
|||
|
|||
.table-controls { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
margin: 0px 40px; |
|||
} |
|||
|
|||
.ri-more-line:hover { |
|||
cursor: pointer; |
|||
} |
|||
|
|||
.heading { |
|||
padding: 40px 40px 0 40px; |
|||
display: flex; |
|||
align-items: center; |
|||
} |
|||
|
|||
h3 { |
|||
margin: 0 0 0 10px; |
|||
color: var(--ink); |
|||
} |
|||
|
|||
footer { |
|||
background-color: var(--grey-1); |
|||
margin-top: 40px; |
|||
padding: 20px 40px 20px 40px; |
|||
display: flex; |
|||
justify-content: flex-end; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,64 @@ |
|||
<script> |
|||
import ActionButton from "components/common/ActionButton.svelte" |
|||
import { notifier } from "builderStore/store/notifications" |
|||
import { store, backendUiStore } from "builderStore" |
|||
import * as api from "../api" |
|||
|
|||
export let table |
|||
export let onClosed |
|||
|
|||
function deleteTable() { |
|||
backendUiStore.actions.models.delete(table) |
|||
} |
|||
</script> |
|||
|
|||
<section> |
|||
<div class="content"> |
|||
<heading> |
|||
<i class="ri-information-line alert" /> |
|||
<h4 class="budibase__title--4">Delete Table</h4> |
|||
</heading> |
|||
<p> |
|||
Are you sure you want to delete this table? All of your data will be |
|||
permanently removed. This action cannot be undone. |
|||
</p> |
|||
</div> |
|||
<div class="modal-actions"> |
|||
<ActionButton on:click={onClosed}>Cancel</ActionButton> |
|||
<ActionButton |
|||
alert |
|||
on:click={async () => { |
|||
await backendUiStore.actions.models.delete(table) |
|||
notifier.danger('Table deleted') |
|||
onClosed() |
|||
}}> |
|||
Delete |
|||
</ActionButton> |
|||
</div> |
|||
</section> |
|||
|
|||
<style> |
|||
.alert { |
|||
color: rgba(255, 0, 31, 1); |
|||
background: var(--grey-1); |
|||
padding: 5px; |
|||
} |
|||
|
|||
.modal-actions { |
|||
padding: 10px; |
|||
background: var(--grey-1); |
|||
border-top: 1px solid #ccc; |
|||
} |
|||
|
|||
heading { |
|||
display: flex; |
|||
align-items: center; |
|||
} |
|||
.content { |
|||
padding: 30px; |
|||
} |
|||
|
|||
h4 { |
|||
margin: 0 0 0 10px; |
|||
} |
|||
</style> |
|||
@ -1,5 +1,4 @@ |
|||
export { default as DeleteRecordModal } from "./DeleteRecord.svelte" |
|||
export { default as CreateEditRecordModal } from "./CreateEditRecord.svelte" |
|||
export { default as CreateEditViewModal } from "./CreateEditView.svelte" |
|||
export { default as CreateDatabaseModal } from "./CreateDatabase.svelte" |
|||
export { default as CreateUserModal } from "./CreateUser.svelte" |
|||
|
|||
@ -0,0 +1,31 @@ |
|||
<script> |
|||
import { backendUiStore } from "builderStore" |
|||
import { DropdownMenu, Button, Icon, Input, Select } from "@budibase/bbui" |
|||
import { FIELDS } from "constants/backend" |
|||
import { ModelSetupNav } from "components/nav/ModelSetupNav" |
|||
import ModelFieldEditor from "components/nav/ModelSetupNav/ModelFieldEditor.svelte" |
|||
import CreateEditColumn from "../modals/CreateEditColumn.svelte" |
|||
|
|||
let anchor |
|||
let dropdown |
|||
let fieldName |
|||
</script> |
|||
|
|||
<div bind:this={anchor}> |
|||
<Button text small on:click={dropdown.show}> |
|||
<Icon name="addcolumn" /> |
|||
Create New Column |
|||
</Button> |
|||
</div> |
|||
<DropdownMenu bind:this={dropdown} {anchor} align="left"> |
|||
<h5>Create Column</h5> |
|||
<CreateEditColumn onClosed={dropdown.hide} /> |
|||
</DropdownMenu> |
|||
|
|||
<style> |
|||
h5 { |
|||
padding: var(--spacing-xl) 0 0 var(--spacing-xl); |
|||
margin: 0; |
|||
font-weight: 500; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,107 @@ |
|||
<script> |
|||
import { backendUiStore } from "builderStore" |
|||
import { DropdownMenu, Button, Icon, Input, Select } from "@budibase/bbui" |
|||
import { FIELDS } from "constants/backend" |
|||
import { ModelSetupNav } from "components/nav/ModelSetupNav" |
|||
import ModelFieldEditor from "components/nav/ModelSetupNav/ModelFieldEditor.svelte" |
|||
import CreateEditColumn from "../modals/CreateEditColumn.svelte" |
|||
|
|||
export let field |
|||
|
|||
let anchor |
|||
let dropdown |
|||
|
|||
let editing |
|||
|
|||
$: sortColumn = $backendUiStore.sort && $backendUiStore.sort.column |
|||
$: sortDirection = $backendUiStore.sort && $backendUiStore.sort.direction |
|||
|
|||
function showEditor() { |
|||
editing = true |
|||
} |
|||
|
|||
function hideEditor() { |
|||
dropdown.hide() |
|||
editing = false |
|||
} |
|||
|
|||
function deleteField() { |
|||
backendUiStore.actions.models.deleteField(field) |
|||
} |
|||
|
|||
function sort(direction, column) { |
|||
backendUiStore.update(state => { |
|||
state.sort = { direction, column } |
|||
return state |
|||
}) |
|||
hideEditor() |
|||
} |
|||
</script> |
|||
|
|||
<div bind:this={anchor} on:click={dropdown.show}> |
|||
{field.name} |
|||
<Icon name="arrowdown" /> |
|||
</div> |
|||
<DropdownMenu bind:this={dropdown} {anchor} align="left"> |
|||
{#if editing} |
|||
<h5>Edit Column</h5> |
|||
<CreateEditColumn onClosed={hideEditor} {field} /> |
|||
{:else} |
|||
<ul> |
|||
<li data-cy="edit-column-header" on:click={showEditor}> |
|||
<Icon name="edit" /> |
|||
Edit |
|||
</li> |
|||
<li data-cy="delete-column-header" on:click={deleteField}> |
|||
<Icon name="delete" /> |
|||
Delete |
|||
</li> |
|||
{#if sortDirection === 'desc' || sortColumn !== field.name} |
|||
<li on:click={() => sort('asc', field.name)}> |
|||
<Icon name="sortascending" /> |
|||
Sort A - Z |
|||
</li> |
|||
{/if} |
|||
{#if sortDirection === 'asc' || sortColumn !== field.name} |
|||
<li on:click={() => sort('desc', field.name)}> |
|||
<Icon name="sortdescending" /> |
|||
Sort Z - A |
|||
</li> |
|||
{/if} |
|||
</ul> |
|||
{/if} |
|||
</DropdownMenu> |
|||
|
|||
<style> |
|||
h5 { |
|||
padding: var(--spacing-xl) 0 0 var(--spacing-xl); |
|||
margin: 0; |
|||
font-weight: 500; |
|||
} |
|||
|
|||
ul { |
|||
list-style: none; |
|||
padding-left: 0; |
|||
margin: 0; |
|||
padding: var(--spacing-s) 0; |
|||
} |
|||
|
|||
li { |
|||
display: flex; |
|||
font-family: var(--font-sans); |
|||
font-size: var(--font-size-xs); |
|||
color: var(--ink); |
|||
padding: var(--spacing-s) var(--spacing-m); |
|||
margin: auto 0px; |
|||
align-items: center; |
|||
cursor: pointer; |
|||
} |
|||
|
|||
li:hover { |
|||
background-color: var(--grey-2); |
|||
} |
|||
|
|||
li:active { |
|||
color: var(--blue); |
|||
} |
|||
</style> |
|||
@ -0,0 +1,97 @@ |
|||
<script> |
|||
import { getContext } from "svelte" |
|||
import { backendUiStore } from "builderStore" |
|||
import { DropdownMenu, Button, Icon, Input, Select } from "@budibase/bbui" |
|||
import { FIELDS } from "constants/backend" |
|||
import CreateEditRecord from "../modals/CreateEditRecord.svelte" |
|||
import DeleteRecordModal from "../modals/DeleteRecord.svelte" |
|||
|
|||
const { open, close } = getContext("simple-modal") |
|||
|
|||
export let row |
|||
|
|||
let anchor |
|||
let dropdown |
|||
|
|||
let editing |
|||
|
|||
function showEditor() { |
|||
editing = true |
|||
} |
|||
|
|||
function hideEditor() { |
|||
dropdown.hide() |
|||
editing = false |
|||
close() |
|||
} |
|||
|
|||
const deleteRow = () => { |
|||
open( |
|||
DeleteRecordModal, |
|||
{ |
|||
onClosed: hideEditor, |
|||
record: row, |
|||
}, |
|||
{ styleContent: { padding: "0" } } |
|||
) |
|||
} |
|||
</script> |
|||
|
|||
<div bind:this={anchor} on:click={dropdown.show}> |
|||
<i class="ri-more-line" /> |
|||
</div> |
|||
<DropdownMenu bind:this={dropdown} {anchor} align="left"> |
|||
{#if editing} |
|||
<h5>Edit Row</h5> |
|||
<CreateEditRecord onClosed={hideEditor} record={row} /> |
|||
{:else} |
|||
<ul> |
|||
<li data-cy="edit-row" on:click={showEditor}> |
|||
<Icon name="edit" /> |
|||
Edit |
|||
</li> |
|||
<li data-cy="delete-row" on:click={deleteRow}> |
|||
<Icon name="delete" /> |
|||
Delete |
|||
</li> |
|||
</ul> |
|||
{/if} |
|||
</DropdownMenu> |
|||
|
|||
<style> |
|||
.ri-more-line:hover { |
|||
cursor: pointer; |
|||
} |
|||
|
|||
h5 { |
|||
padding: var(--spacing-xl) 0 0 var(--spacing-xl); |
|||
margin: 0; |
|||
font-weight: 500; |
|||
} |
|||
|
|||
ul { |
|||
list-style: none; |
|||
padding-left: 0; |
|||
margin: 0; |
|||
padding: var(--spacing-s) 0; |
|||
} |
|||
|
|||
li { |
|||
display: flex; |
|||
font-family: var(--font-sans); |
|||
font-size: var(--font-size-xs); |
|||
color: var(--ink); |
|||
padding: var(--spacing-s) var(--spacing-m); |
|||
margin: auto 0px; |
|||
align-items: center; |
|||
cursor: pointer; |
|||
} |
|||
|
|||
li:hover { |
|||
background-color: var(--grey-2); |
|||
} |
|||
|
|||
li:active { |
|||
color: var(--blue); |
|||
} |
|||
</style> |
|||
@ -0,0 +1,26 @@ |
|||
<script> |
|||
import { DropdownMenu, Button, Icon } from "@budibase/bbui" |
|||
import CreateEditRecord from "../modals/CreateEditRecord.svelte" |
|||
|
|||
let anchor |
|||
let dropdown |
|||
</script> |
|||
|
|||
<div bind:this={anchor}> |
|||
<Button text small on:click={dropdown.show}> |
|||
<Icon name="addrow" /> |
|||
Create New Row |
|||
</Button> |
|||
</div> |
|||
<DropdownMenu bind:this={dropdown} {anchor} align="left"> |
|||
<h5>Add New Row</h5> |
|||
<CreateEditRecord onClosed={dropdown.hide} /> |
|||
</DropdownMenu> |
|||
|
|||
<style> |
|||
h5 { |
|||
padding: var(--spacing-xl) 0 0 var(--spacing-xl); |
|||
margin: 0; |
|||
font-weight: 500; |
|||
} |
|||
</style> |
|||
@ -1,91 +0,0 @@ |
|||
<script> |
|||
import * as blockDefinitions from "constants/backend" |
|||
import { backendUiStore } from "builderStore" |
|||
import Block from "components/common/Block.svelte" |
|||
|
|||
const HEADINGS = [ |
|||
{ |
|||
title: "Fields", |
|||
key: "FIELDS", |
|||
}, |
|||
{ |
|||
title: "Blocks", |
|||
key: "BLOCKS", |
|||
}, |
|||
] |
|||
|
|||
let selectedTab = "FIELDS" |
|||
|
|||
function addField(blockDefinition) { |
|||
backendUiStore.actions.models.addField(blockDefinition) |
|||
backendUiStore.actions.models.fetch() |
|||
} |
|||
</script> |
|||
|
|||
<section> |
|||
<header> |
|||
{#each HEADINGS as tab} |
|||
<span |
|||
class:selected={selectedTab === tab.key} |
|||
on:click={() => (selectedTab = tab.key)}> |
|||
{tab.title} |
|||
</span> |
|||
{/each} |
|||
</header> |
|||
|
|||
<div class="block-grid"> |
|||
{#each Object.values(blockDefinitions[selectedTab]) as blockDefinition} |
|||
<Block |
|||
on:click={() => addField(blockDefinition)} |
|||
title={blockDefinition.name} |
|||
icon={blockDefinition.icon} /> |
|||
{/each} |
|||
</div> |
|||
</section> |
|||
|
|||
<style> |
|||
header { |
|||
margin-top: 20px; |
|||
margin-bottom: 20px; |
|||
display: grid; |
|||
grid-template-columns: repeat(3, 1fr); |
|||
} |
|||
|
|||
span { |
|||
cursor: pointer; |
|||
display: grid; |
|||
justify-content: center; |
|||
align-content: center; |
|||
padding: 0px 16px; |
|||
height: 36px; |
|||
text-align: center; |
|||
background: #ffffff; |
|||
color: var(--grey-7); |
|||
border-radius: 5px; |
|||
font-family: inter; |
|||
font-size: 14px; |
|||
font-weight: 400; |
|||
transition: all 0.3s; |
|||
text-rendering: optimizeLegibility; |
|||
border: none !important; |
|||
transition: 0.2s; |
|||
outline: none; |
|||
} |
|||
|
|||
span:hover { |
|||
color: var(--ink); |
|||
cursor: pointer; |
|||
} |
|||
|
|||
.selected { |
|||
background: var(--grey-3); |
|||
color: var(--ink); |
|||
} |
|||
|
|||
.block-grid { |
|||
margin-top: 20px; |
|||
display: grid; |
|||
grid-template-columns: 1fr 1fr; |
|||
grid-gap: 20px; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,77 @@ |
|||
<script> |
|||
import { goto } from "@sveltech/routify" |
|||
import { backendUiStore } from "builderStore" |
|||
import { notifier } from "builderStore/store/notifications" |
|||
import { DropdownMenu, Button, Icon, Input, Select } from "@budibase/bbui" |
|||
|
|||
export let table |
|||
|
|||
let anchor |
|||
let dropdown |
|||
let name |
|||
|
|||
async function saveTable() { |
|||
const model = await backendUiStore.actions.models.save({ |
|||
name, |
|||
schema: {}, |
|||
}) |
|||
notifier.success(`Table ${name} created successfully.`) |
|||
$goto(`./model/${model._id}`) |
|||
dropdown.hide() |
|||
} |
|||
</script> |
|||
|
|||
<div bind:this={anchor}> |
|||
<Button primary wide on:click={dropdown.show}>Create New Table</Button> |
|||
</div> |
|||
<DropdownMenu bind:this={dropdown} {anchor} align="left"> |
|||
<div class="container"> |
|||
<h5>Create Table</h5> |
|||
<Input |
|||
data-cy="table-name-input" |
|||
placeholder="Table Name" |
|||
thin |
|||
bind:value={name} /> |
|||
</div> |
|||
<footer> |
|||
<div class="button-margin-3"> |
|||
<Button secondary on:click={dropdown.hide}>Cancel</Button> |
|||
</div> |
|||
<div class="button-margin-4"> |
|||
<Button primary on:click={saveTable}>Save</Button> |
|||
</div> |
|||
</footer> |
|||
</DropdownMenu> |
|||
|
|||
<style> |
|||
h5 { |
|||
padding: var(--spacing-xl) 0 0 var(--spacing-xl); |
|||
margin: 0; |
|||
font-weight: 500; |
|||
} |
|||
|
|||
.container { |
|||
padding: var(--spacing-l); |
|||
margin: 0; |
|||
} |
|||
|
|||
footer { |
|||
padding: 20px; |
|||
display: grid; |
|||
grid-template-columns: 1fr 1fr 1fr 1fr; |
|||
gap: 20px; |
|||
background: var(--grey-1); |
|||
border-bottom-left-radius: 0.5rem; |
|||
border-bottom-left-radius: 0.5rem; |
|||
} |
|||
|
|||
.button-margin-3 { |
|||
grid-column-start: 3; |
|||
display: grid; |
|||
} |
|||
|
|||
.button-margin-4 { |
|||
grid-column-start: 4; |
|||
display: grid; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,137 @@ |
|||
<script> |
|||
import { getContext } from "svelte" |
|||
import { backendUiStore } from "builderStore" |
|||
import { DropdownMenu, Button, Icon, Input, Select } from "@budibase/bbui" |
|||
import { FIELDS } from "constants/backend" |
|||
import DeleteTableModal from "components/database/ModelDataTable/modals/DeleteTable.svelte" |
|||
|
|||
const { open, close } = getContext("simple-modal") |
|||
|
|||
export let table |
|||
|
|||
let anchor |
|||
let dropdown |
|||
|
|||
let editing |
|||
|
|||
function showEditor() { |
|||
editing = true |
|||
} |
|||
|
|||
function hideEditor() { |
|||
dropdown.hide() |
|||
editing = false |
|||
close() |
|||
} |
|||
|
|||
const deleteTable = () => { |
|||
open( |
|||
DeleteTableModal, |
|||
{ |
|||
onClosed: close, |
|||
table, |
|||
}, |
|||
{ styleContent: { padding: "0" } } |
|||
) |
|||
} |
|||
|
|||
function save() { |
|||
backendUiStore.actions.models.save(table) |
|||
hideEditor() |
|||
} |
|||
</script> |
|||
|
|||
<div bind:this={anchor} on:click={dropdown.show}> |
|||
<i class="ri-more-line" /> |
|||
</div> |
|||
<DropdownMenu bind:this={dropdown} {anchor} align="left"> |
|||
{#if editing} |
|||
<h5>Edit Table</h5> |
|||
<div class="container"> |
|||
<Input placeholder="Table Name" thin bind:value={table.name} /> |
|||
</div> |
|||
<footer> |
|||
<div class="button-margin-3"> |
|||
<Button secondary on:click={hideEditor}>Cancel</Button> |
|||
</div> |
|||
<div class="button-margin-4"> |
|||
<Button primary on:click={save}>Save</Button> |
|||
</div> |
|||
</footer> |
|||
{:else} |
|||
<ul> |
|||
<li on:click={showEditor}> |
|||
<Icon name="edit" /> |
|||
Edit |
|||
</li> |
|||
<li data-cy="delete-table" on:click={deleteTable}> |
|||
<Icon name="delete" /> |
|||
Delete |
|||
</li> |
|||
</ul> |
|||
{/if} |
|||
</DropdownMenu> |
|||
|
|||
<style> |
|||
h5 { |
|||
padding: var(--spacing-xl) 0 0 var(--spacing-xl); |
|||
margin: 0; |
|||
font-weight: 500; |
|||
} |
|||
|
|||
.container { |
|||
padding: var(--spacing-xl); |
|||
} |
|||
|
|||
ul { |
|||
padding: var(--spacing-xl) 0 0 var(--spacing-xl); |
|||
list-style: none; |
|||
padding-left: 0; |
|||
margin: 0; |
|||
padding: var(--spacing-s) 0; |
|||
} |
|||
|
|||
li { |
|||
display: flex; |
|||
font-family: var(--font-sans); |
|||
font-size: var(--font-size-xs); |
|||
color: var(--ink); |
|||
padding: var(--spacing-s) var(--spacing-m); |
|||
margin: auto 0px; |
|||
align-items: center; |
|||
cursor: pointer; |
|||
} |
|||
|
|||
li:hover { |
|||
background-color: var(--grey-2); |
|||
} |
|||
|
|||
li:active { |
|||
color: var(--blue); |
|||
} |
|||
|
|||
footer { |
|||
padding: 20px; |
|||
display: grid; |
|||
grid-template-columns: 1fr 1fr 1fr 1fr; |
|||
gap: 20px; |
|||
background: var(--grey-1); |
|||
border-bottom-left-radius: 0.5rem; |
|||
border-bottom-left-radius: 0.5rem; |
|||
} |
|||
|
|||
.button-margin-1 { |
|||
grid-column-start: 1; |
|||
display: grid; |
|||
} |
|||
|
|||
.button-margin-3 { |
|||
grid-column-start: 3; |
|||
display: grid; |
|||
} |
|||
|
|||
.button-margin-4 { |
|||
grid-column-start: 4; |
|||
display: grid; |
|||
} |
|||
</style> |
|||
@ -1,107 +0,0 @@ |
|||
<script> |
|||
import { backendUiStore } from "builderStore" |
|||
import { uuid } from "builderStore/uuid" |
|||
import { fade } from "svelte/transition" |
|||
import { notifier } from "builderStore/store/notifications" |
|||
import { FIELDS, BLOCKS, MODELS } from "constants/backend" |
|||
import Block from "components/common/Block.svelte" |
|||
|
|||
function addNewField(field) { |
|||
backendUiStore.actions.models.addField(field) |
|||
} |
|||
|
|||
function createModel(model) { |
|||
const { schema, ...rest } = $backendUiStore.selectedModel |
|||
|
|||
backendUiStore.actions.models.save({ |
|||
model: { |
|||
...model, |
|||
...rest, |
|||
}, |
|||
}) |
|||
notifier.success(`${model.name} table created.`) |
|||
} |
|||
</script> |
|||
|
|||
<section transition:fade> |
|||
<header> |
|||
<h2>Create New Table</h2> |
|||
<p>Before you can view your table, you need to set it up.</p> |
|||
</header> |
|||
|
|||
<div class="block-row"> |
|||
<span class="block-row-title">Fields</span> |
|||
<p>Blocks are pre-made fields and help you build your table quicker.</p> |
|||
<div class="blocks"> |
|||
{#each Object.values(FIELDS) as field} |
|||
<Block |
|||
primary |
|||
title={field.name} |
|||
icon={field.icon} |
|||
on:click={() => addNewField(field)} /> |
|||
{/each} |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="block-row"> |
|||
<span class="block-row-title">Blocks</span> |
|||
<p>Blocks are pre-made fields and help you build your table quicker.</p> |
|||
<div class="blocks"> |
|||
{#each Object.values(BLOCKS) as field} |
|||
<Block |
|||
secondary |
|||
title={field.name} |
|||
icon={field.icon} |
|||
on:click={() => addNewField(field)} /> |
|||
{/each} |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="block-row"> |
|||
<span class="block-row-title">Tables</span> |
|||
<p>Blocks are pre-made fields and help you build your table quicker.</p> |
|||
<div class="blocks"> |
|||
{#each Object.values(MODELS) as model} |
|||
<Block |
|||
tertiary |
|||
title={model.name} |
|||
icon={model.icon} |
|||
on:click={() => createModel(model)} /> |
|||
{/each} |
|||
</div> |
|||
</div> |
|||
</section> |
|||
|
|||
<style> |
|||
section { |
|||
height: 100vh; |
|||
} |
|||
|
|||
h2 { |
|||
font-size: 20px; |
|||
font-weight: bold; |
|||
margin: 0; |
|||
} |
|||
|
|||
.block-row-title { |
|||
font-weight: 500; |
|||
font-size: 16px; |
|||
} |
|||
|
|||
p { |
|||
margin-top: 8px; |
|||
margin-bottom: 20px; |
|||
font-size: 14px; |
|||
} |
|||
|
|||
.block-row { |
|||
margin-top: 40px; |
|||
} |
|||
|
|||
.block-row .blocks { |
|||
display: grid; |
|||
grid-auto-flow: column; |
|||
grid-auto-columns: 110px; |
|||
grid-gap: 20px; |
|||
} |
|||
</style> |
|||
File diff suppressed because it is too large
Loading…
Reference in new issue