new table creation

This commit is contained in:
Martin McKeaveney 2020-08-07 18:31:40 +01:00
parent 160703863b
commit cfe262d602
19 changed files with 255 additions and 314 deletions

View File

@ -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')
})
})

View File

@ -0,0 +1,42 @@
xcontext('Create a Table', () => {
before(() => {
cy.visit('localhost:4001/_builder')
// https://on.cypress.io/type
cy.createApp('Table App', 'Table App Description')
})
// https://on.cypress.io/interacting-with-elements
it('should create a new Table', () => {
cy.createTable('dog', 'name', 'age')
// Check if Table exists
cy.get('.title').should('have.text', 'dog')
})
it('adds a new column to the table', () => {
cy.addRecord('bob', '15')
cy.contains('bob').should('have.text', 'bob')
})
it('updates a column on the table', () => {
cy.addRecord('bob', '15')
cy.contains('bob').should('have.text', 'bob')
})
it('edits a record', () => {
cy.addRecord('bob', '15')
cy.contains('bob').should('have.text', 'bob')
})
it('deletes a record', () => {
cy.addRecord('bob', '15')
cy.contains('bob').should('have.text', 'bob')
})
})

View File

@ -66,6 +66,7 @@
"codemirror": "^5.51.0", "codemirror": "^5.51.0",
"date-fns": "^1.29.0", "date-fns": "^1.29.0",
"deepmerge": "^4.2.2", "deepmerge": "^4.2.2",
"fast-sort": "^2.2.0",
"feather-icons": "^4.21.0", "feather-icons": "^4.21.0",
"flatpickr": "^4.5.7", "flatpickr": "^4.5.7",
"lodash": "^4.17.13", "lodash": "^4.17.13",

View File

@ -63,7 +63,7 @@ export const getBackendUiStore = () => {
state.selectedView = `all_${model._id}` state.selectedView = `all_${model._id}`
return state return state
}), }),
save: async ({ model }) => { save: async model => {
const updatedModel = cloneDeep(model) const updatedModel = cloneDeep(model)
// update any renamed schema keys to reflect their names // update any renamed schema keys to reflect their names
@ -97,14 +97,14 @@ export const getBackendUiStore = () => {
...state.draftModel.schema, ...state.draftModel.schema,
[field.name]: cloneDeep(field), [field.name]: cloneDeep(field),
} }
store.actions.models.save({ model: state.draftModel }) store.actions.models.save(state.draftModel)
return state return state
}) })
}, },
deleteField: field => { deleteField: field => {
store.update(state => { store.update(state => {
delete state.draftModel.schema[field.name] delete state.draftModel.schema[field.name]
store.actions.models.save({ model: state.draftModel }) store.actions.models.save(state.draftModel)
return state return state
}) })
} }

View File

@ -1,5 +1,6 @@
<script> <script>
import { onMount, getContext } from "svelte" import { onMount } from "svelte"
import fsort from "fast-sort";
import { store, backendUiStore } from "builderStore" import { store, backendUiStore } from "builderStore"
import { Button, Icon } from "@budibase/bbui" import { Button, Icon } from "@budibase/bbui"
import Select from "components/common/Select.svelte" import Select from "components/common/Select.svelte"
@ -10,32 +11,9 @@
import RowPopover from "./popovers/Row.svelte" import RowPopover from "./popovers/Row.svelte"
import ColumnPopover from "./popovers/Column.svelte" import ColumnPopover from "./popovers/Column.svelte"
import ColumnHeaderPopover from "./popovers/ColumnHeader.svelte" import ColumnHeaderPopover from "./popovers/ColumnHeader.svelte"
import EditRowPopover from "./popovers/EditRow.svelte"
import * as api from "./api" import * as api from "./api"
const { open, close } = getContext("simple-modal")
const editRecord = async row => {
open(
CreateEditRecordModal,
{
onClosed: close,
record: row,
},
{ styleContent: { padding: "0" } }
)
}
const deleteRecord = async row => {
open(
DeleteRecordModal,
{
onClosed: close,
record: row,
},
{ styleContent: { padding: "0" } }
)
}
const ITEMS_PER_PAGE = 10 const ITEMS_PER_PAGE = 10
// Internal headers we want to hide from the user // Internal headers we want to hide from the user
const INTERNAL_HEADERS = ["_id", "_rev", "modelId", "type"] const INTERNAL_HEADERS = ["_id", "_rev", "modelId", "type"]
@ -62,6 +40,8 @@
currentPage * ITEMS_PER_PAGE + ITEMS_PER_PAGE currentPage * ITEMS_PER_PAGE + ITEMS_PER_PAGE
) )
: [] : []
$: sort = $backendUiStore.sort
$: sorted = sort ? fsort(data)[sort.direction](sort.column) : data
$: headers = Object.keys($backendUiStore.selectedModel.schema).filter( $: headers = Object.keys($backendUiStore.selectedModel.schema).filter(
id => !INTERNAL_HEADERS.includes(id) id => !INTERNAL_HEADERS.includes(id)
@ -90,10 +70,6 @@
<div class="table-controls"> <div class="table-controls">
<h2 class="title">{$backendUiStore.selectedModel.name}</h2> <h2 class="title">{$backendUiStore.selectedModel.name}</h2>
<div class="popovers"> <div class="popovers">
<!-- <Button text small on:click={() => alert('Clicked!')}>
<Icon name="view" />
Create New View
</Button> -->
<ColumnPopover /> <ColumnPopover />
<RowPopover /> <RowPopover />
</div> </div>
@ -113,33 +89,13 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{#if paginatedData.length === 0} {#if sorted.length === 0}
<div class="no-data">No Data.</div> <div class="no-data">No Data.</div>
{/if} {/if}
{#each paginatedData as row} {#each sorted as row}
<tr> <tr>
<td> <td>
<div class="uk-inline"> <EditRowPopover {row} />
<i class="ri-more-line" />
<div uk-dropdown="mode: click">
<ul class="uk-nav uk-dropdown-nav">
<li
on:click={() => {
editRecord(row)
}}>
<i class="ri-edit-line" />
<div class="label">Edit</div>
</li>
<li
on:click={() => {
deleteRecord(row)
}}>
<i class="ri-delete-bin-2-line" />
<div class="label">Delete</div>
</li>
</ul>
</div>
</div>
</td> </td>
{#each headers as header} {#each headers as header}
<td class="hoverable"> <td class="hoverable">

View File

@ -30,10 +30,6 @@
field.constraints = FIELDS[field.type.toUpperCase()].constraints field.constraints = FIELDS[field.type.toUpperCase()].constraints
} }
function closed() {
onClosed()
}
async function saveColumn() { async function saveColumn() {
// if existing // if existing
// update the name and type // update the name and type

View File

@ -20,10 +20,6 @@
? Object.entries($backendUiStore.selectedModel.schema) ? Object.entries($backendUiStore.selectedModel.schema)
: [] : []
function closed() {
onClosed()
}
const isSelect = meta => const isSelect = meta =>
meta.type === "string" && meta.type === "string" &&
meta.constraints && meta.constraints &&
@ -68,7 +64,6 @@
</script> </script>
<div class="actions"> <div class="actions">
<h5>Add New Row</h5>
<ErrorsBox {errors} /> <ErrorsBox {errors} />
<form on:submit|preventDefault class="uk-form-stacked"> <form on:submit|preventDefault class="uk-form-stacked">
{#each modelSchema as [key, meta]} {#each modelSchema as [key, meta]}

View File

@ -1,24 +1,24 @@
import "@testing-library/jest-dom/extend-expect" import "@testing-library/jest-dom/extend-expect"
import { render, fireEvent } from "@testing-library/svelte" import { render, fireEvent } from "@testing-library/svelte"
import CreateEditTable from "../CreateEditTable.svelte"; import CreateEditColumn from "../CreateEditColumn.svelte";
const testField = { const testField = {
field: {}, field: {},
name: "Yeet" name: "Yeet"
}; };
describe("<CreateEditTable />", () => { describe("<CreateEditColumn />", () => {
describe("New Column", () => { describe("New Column", () => {
it("shows proper heading when rendered", () => { it("shows proper heading when rendered", () => {
const { getByText } = render(CreateEditTable, { name: 'World' }) const { getByText } = render(CreateEditColumn, { name: 'World' })
expect(getByText('Hello World!')).toBeInTheDocument() expect(getByText('Hello World!')).toBeInTheDocument()
}) })
}) })
describe("Edit Existing Column", () => { describe("Edit Existing Column", () => {
const { getByText } = render(CreateEditTable, testField) const { getByText } = render(CreateEditColumn, testField)
}) })

View File

@ -4,7 +4,7 @@
import { FIELDS } from "constants/backend" import { FIELDS } from "constants/backend"
import { ModelSetupNav } from "components/nav/ModelSetupNav" import { ModelSetupNav } from "components/nav/ModelSetupNav"
import ModelFieldEditor from "components/nav/ModelSetupNav/ModelFieldEditor.svelte" import ModelFieldEditor from "components/nav/ModelSetupNav/ModelFieldEditor.svelte"
import CreateEditTable from "../modals/CreateEditTable.svelte" import CreateEditColumn from "../modals/CreateEditColumn.svelte"
let anchor let anchor
let dropdown let dropdown
@ -18,10 +18,13 @@
</Button> </Button>
</div> </div>
<DropdownMenu bind:this={dropdown} {anchor} align="left"> <DropdownMenu bind:this={dropdown} {anchor} align="left">
<h5>Create Column</h5> <h4>Create Column</h4>
<CreateEditTable onClosed={dropdown.hide} /> <CreateEditColumn onClosed={dropdown.hide} />
</DropdownMenu> </DropdownMenu>
<style> <style>
h4 {
padding: var(--spacing-l);
margin: 0;
}
</style> </style>

View File

@ -4,7 +4,7 @@
import { FIELDS } from "constants/backend" import { FIELDS } from "constants/backend"
import { ModelSetupNav } from "components/nav/ModelSetupNav" import { ModelSetupNav } from "components/nav/ModelSetupNav"
import ModelFieldEditor from "components/nav/ModelSetupNav/ModelFieldEditor.svelte" import ModelFieldEditor from "components/nav/ModelSetupNav/ModelFieldEditor.svelte"
import CreateEditTable from "../modals/CreateEditTable.svelte" import CreateEditColumn from "../modals/CreateEditColumn.svelte"
export let field export let field
@ -26,7 +26,13 @@
backendUiStore.actions.models.deleteField(field) backendUiStore.actions.models.deleteField(field)
} }
function save() {} function sort(direction, column) {
backendUiStore.update(state => {
state.sort = { direction, column }
return state
})
hideEditor()
}
</script> </script>
<div bind:this={anchor} on:click={dropdown.show}> <div bind:this={anchor} on:click={dropdown.show}>
@ -35,9 +41,9 @@
</div> </div>
<DropdownMenu bind:this={dropdown} {anchor} align="left"> <DropdownMenu bind:this={dropdown} {anchor} align="left">
{#if editing} {#if editing}
<h5>Edit Column</h5> <h4>Edit Column</h4>
<CreateEditTable <CreateEditColumn
onClosed={dropdown.hide} onClosed={hideEditor}
field={field.field} field={field.field}
columnName={field.name} /> columnName={field.name} />
{:else} {:else}
@ -50,11 +56,11 @@
<Icon name="delete" /> <Icon name="delete" />
Delete Delete
</li> </li>
<li> <li on:click={() => sort('asc', field.name)}>
<Icon name="sortascending" /> <Icon name="sortascending" />
Sort A - Z Sort A - Z
</li> </li>
<li> <li on:click={() => sort('desc', field.name)}>
<Icon name="sortdescending" /> <Icon name="sortdescending" />
Sort Z - A Sort Z - A
</li> </li>
@ -63,6 +69,11 @@
</DropdownMenu> </DropdownMenu>
<style> <style>
h4 {
padding: var(--spacing-l);
margin: 0;
}
ul { ul {
list-style: none; list-style: none;
padding-left: 0; padding-left: 0;

View File

@ -0,0 +1,94 @@
<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" } }
)
}
function save() {}
</script>
<div bind:this={anchor} on:click={dropdown.show}>
<i class="ri-more-line" />
</div>
<DropdownMenu bind:this={dropdown} {anchor} align="left">
{#if editing}
<h4>Edit Row</h4>
<CreateEditRecord onClosed={hideEditor} record={row} />
{:else}
<ul>
<li on:click={showEditor}>
<Icon name="edit" />
Edit
</li>
<li on:click={deleteRow}>
<Icon name="delete" />
Delete
</li>
</ul>
{/if}
</DropdownMenu>
<style>
h4 {
padding: var(--spacing-l);
margin: 0;
}
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>

View File

@ -13,9 +13,13 @@
</Button> </Button>
</div> </div>
<DropdownMenu bind:this={dropdown} anchor={anchor} align="left"> <DropdownMenu bind:this={dropdown} anchor={anchor} align="left">
<h4>Add New Row</h4>
<CreateEditRecord onClosed={dropdown.hide} /> <CreateEditRecord onClosed={dropdown.hide} />
</DropdownMenu> </DropdownMenu>
<style> <style>
h4 {
padding: var(--spacing-l);
margin: 0;
}
</style> </style>

View File

@ -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>

View File

@ -0,0 +1,65 @@
<script>
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() {
await backendUiStore.actions.models.save({
name,
schema: {},
})
notifier.success(`Table ${name} created successfully.`)
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">
<h4>Create Table</h4>
<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>
.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>

View File

@ -1,93 +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 } 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>
</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>

View File

@ -9,6 +9,7 @@
<div class:selected on:click class={className}> <div class:selected on:click class={className}>
<i class:indented class={icon} /> <i class:indented class={icon} />
<span>{title}</span> <span>{title}</span>
<slot />
</div> </div>
<style> <style>

View File

@ -4,9 +4,9 @@
import { Switcher } from "@budibase/bbui" import { Switcher } from "@budibase/bbui"
import { goto } from "@sveltech/routify" import { goto } from "@sveltech/routify"
import { store, backendUiStore } from "builderStore" import { store, backendUiStore } from "builderStore"
import BlockNavigator from "./BlockNavigator.svelte"
import ListItem from "./ListItem.svelte" import ListItem from "./ListItem.svelte"
import { Button } from "@budibase/bbui" import { Button } from "@budibase/bbui"
import CreateTablePopover from "./CreateEditTable.svelte"
const { open, close } = getContext("simple-modal") const { open, close } = getContext("simple-modal")
@ -37,14 +37,6 @@
}) })
} }
} }
function setupForNewModel() {
backendUiStore.update(state => {
state.selectedModel = {}
state.draftModel = { schema: {} }
return state
})
}
</script> </script>
<div class="items-root"> <div class="items-root">
@ -52,16 +44,16 @@
<div class="hierarchy"> <div class="hierarchy">
<div class="components-list-container"> <div class="components-list-container">
<h3>Tables</h3> <h3>Tables</h3>
<Button primary wide on:click={setupForNewModel}> <CreateTablePopover />
Create New Table
</Button>
<div class="hierarchy-items-container"> <div class="hierarchy-items-container">
{#each $backendUiStore.models as model} {#each $backendUiStore.models as model}
<ListItem <ListItem
selected={!$backendUiStore.selectedField && model._id === $backendUiStore.selectedModel._id} selected={!$backendUiStore.selectedField && model._id === $backendUiStore.selectedModel._id}
title={model.name} title={model.name}
icon="ri-table-fill" icon="ri-table-fill"
on:click={() => selectModel(model)} /> on:click={() => selectModel(model)}>
</ListItem>
{/each} {/each}
</div> </div>
</div> </div>

View File

@ -35,7 +35,7 @@
if (field) { if (field) {
const name = model.schema[field].name const name = model.schema[field].name
delete model.schema[field] delete model.schema[field]
backendUiStore.actions.models.save({ model }) backendUiStore.actions.models.save(model)
notifier.danger(`Field ${name} deleted.`) notifier.danger(`Field ${name} deleted.`)
return return
} }
@ -81,9 +81,7 @@
return return
} }
await backendUiStore.actions.models.save({ await backendUiStore.actions.models.save($backendUiStore.draftModel)
model: $backendUiStore.draftModel,
})
notifier.success( notifier.success(
"Success! Your changes have been saved. Please continue on with your greatness." "Success! Your changes have been saved. Please continue on with your greatness."
) )

View File

@ -1,7 +1,6 @@
<script> <script>
import { getContext } from "svelte" import { getContext } from "svelte"
import { Button } from "@budibase/bbui" import { Button } from "@budibase/bbui"
import EmptyModel from "components/nav/ModelNavigator/EmptyModel.svelte"
import ModelDataTable from "components/database/ModelDataTable" import ModelDataTable from "components/database/ModelDataTable"
import { backendUiStore } from "builderStore" import { backendUiStore } from "builderStore"
import ActionButton from "components/common/ActionButton.svelte" import ActionButton from "components/common/ActionButton.svelte"
@ -11,16 +10,6 @@
const { open, close } = getContext("simple-modal") const { open, close } = getContext("simple-modal")
$: selectedModel = $backendUiStore.selectedModel $: selectedModel = $backendUiStore.selectedModel
const createNewRecord = () => {
open(
CreateEditRecordModal,
{
onClosed: close,
},
{ styleContent: { padding: "0" } }
)
}
</script> </script>
{#if $backendUiStore.selectedDatabase._id && selectedModel.name} {#if $backendUiStore.selectedDatabase._id && selectedModel.name}