mirror of https://github.com/Budibase/budibase.git
16 changed files with 408 additions and 320 deletions
@ -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,143 @@ |
|||||
|
<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 = {} |
||||
|
export let columnName |
||||
|
|
||||
|
$: required = |
||||
|
field.constraints && |
||||
|
field.constraints.presence && |
||||
|
!field.constraints.presence.allowEmpty |
||||
|
$: if (field.type) { |
||||
|
field.constraints = FIELDS[field.type.toUpperCase()].constraints |
||||
|
} |
||||
|
|
||||
|
function closed() { |
||||
|
onClosed() |
||||
|
} |
||||
|
|
||||
|
async function saveColumn() { |
||||
|
backendUiStore.actions.models.addField({ |
||||
|
name: columnName, |
||||
|
field, |
||||
|
}) |
||||
|
|
||||
|
backendUiStore.actions.models.save({ |
||||
|
model: $backendUiStore.draftModel, |
||||
|
}) |
||||
|
onClosed() |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<div class="actions"> |
||||
|
<Input placeholder="Name" thin bind:value={columnName} /> |
||||
|
|
||||
|
<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: 20px; |
||||
|
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> |
||||
@ -0,0 +1,38 @@ |
|||||
|
import "@testing-library/jest-dom/extend-expect" |
||||
|
import { render, fireEvent } from "@testing-library/svelte" |
||||
|
import CreateEditTable from "../CreateEditTable.svelte"; |
||||
|
|
||||
|
const testField = { |
||||
|
field: {}, |
||||
|
name: "Yeet" |
||||
|
}; |
||||
|
|
||||
|
describe("<CreateEditTable />", () => { |
||||
|
|
||||
|
describe("New Column", () => { |
||||
|
it("shows proper heading when rendered", () => { |
||||
|
const { getByText } = render(CreateEditTable, { name: 'World' }) |
||||
|
|
||||
|
expect(getByText('Hello World!')).toBeInTheDocument() |
||||
|
}) |
||||
|
}) |
||||
|
|
||||
|
describe("Edit Existing Column", () => { |
||||
|
const { getByText } = render(CreateEditTable, testField) |
||||
|
|
||||
|
}) |
||||
|
|
||||
|
}) |
||||
|
|
||||
|
|
||||
|
// // Note: This is as an async test as we are using `fireEvent`
|
||||
|
// test('changes button text on click', async () => {
|
||||
|
// const { getByText } = render(Comp, { name: 'World' })
|
||||
|
// const button = getByText('Button')
|
||||
|
|
||||
|
// // Using await when firing events is unique to the svelte testing library because
|
||||
|
// // we have to wait for the next `tick` so that Svelte flushes all pending state changes.
|
||||
|
// await fireEvent.click(button)
|
||||
|
|
||||
|
// expect(button).toHaveTextContent('Button Clicked')
|
||||
|
// })
|
||||
@ -0,0 +1,25 @@ |
|||||
|
<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 CreateEditTable from "../modals/CreateEditTable.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"> |
||||
|
<CreateEditTable onClosed={dropdown.hide} /> |
||||
|
</DropdownMenu> |
||||
|
|
||||
|
<style> |
||||
|
</style> |
||||
@ -0,0 +1,114 @@ |
|||||
|
<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 CreateEditTable from "../modals/CreateEditTable.svelte" |
||||
|
|
||||
|
export let field |
||||
|
|
||||
|
let anchor |
||||
|
let dropdown |
||||
|
|
||||
|
let editing |
||||
|
|
||||
|
function showEditor() { |
||||
|
editing = true |
||||
|
} |
||||
|
|
||||
|
function hideEditor() { |
||||
|
dropdown.hide() |
||||
|
editing = false |
||||
|
} |
||||
|
|
||||
|
function deleteField() { |
||||
|
alert("Delete Field Not Implemented") |
||||
|
} |
||||
|
|
||||
|
function save() {} |
||||
|
</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> |
||||
|
<CreateEditTable |
||||
|
onClosed={dropdown.hide} |
||||
|
field={field.field} |
||||
|
columnName={field.name} /> |
||||
|
{:else} |
||||
|
<ul> |
||||
|
<li on:click={showEditor}> |
||||
|
<Icon name="edit" /> |
||||
|
Edit |
||||
|
</li> |
||||
|
<li on:click={deleteField}> |
||||
|
<Icon name="delete" /> |
||||
|
Delete |
||||
|
</li> |
||||
|
<li> |
||||
|
<Icon name="sortascending" /> |
||||
|
Sort A - Z |
||||
|
</li> |
||||
|
<li> |
||||
|
<Icon name="sortdescending" /> |
||||
|
Sort Z - A |
||||
|
</li> |
||||
|
</ul> |
||||
|
{/if} |
||||
|
</DropdownMenu> |
||||
|
|
||||
|
<style> |
||||
|
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); |
||||
|
} |
||||
|
|
||||
|
.editor { |
||||
|
padding: var(--spacing-xl); |
||||
|
} |
||||
|
|
||||
|
footer { |
||||
|
padding: var(--spacing-xl); |
||||
|
display: grid; |
||||
|
grid-template-columns: 1fr 1fr 1fr 1fr; |
||||
|
gap: 20px; |
||||
|
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,21 @@ |
|||||
|
<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={anchor} align="left"> |
||||
|
<CreateEditRecord onClosed={dropdown.hide} /> |
||||
|
</DropdownMenu> |
||||
|
|
||||
|
<style> |
||||
|
|
||||
|
</style> |
||||
Loading…
Reference in new issue