add new models and fields, delete models

This commit is contained in:
Martin McKeaveney 2020-06-17 16:51:10 +01:00
parent bda580bc3f
commit 949052c41c
14 changed files with 295 additions and 60 deletions

View File

@ -77,7 +77,8 @@
.budibase__input {
height: 35px;
width: 220px;
width: 100%;
max-width: 220px;
border-radius: 3px;
border: 1px solid var(--grey-dark);
text-align: left;

View File

@ -25,9 +25,7 @@ export const getBackendUiStore = () => {
store.update(state => {
state.selectedDatabase = db
if (models && models.length > 0) {
state.selectedModel = models[0]
state.draftModel = models[0]
state.selectedView = `all_${models[0]._id}`
store.actions.models.select(models[0]);
}
state.breadcrumbs = [db.name]
state.models = models
@ -56,14 +54,25 @@ export const getBackendUiStore = () => {
models: {
select: model => store.update(state => {
state.selectedModel = model;
// TODO: prevent pointing to same obj
state.draftModel = cloneDeep(model);
state.selectedField = null
state.selectedField = ""
state.selectedView = `all_${model._id}`
return state;
}),
save: async ({ instanceId, model }) => {
const updatedModel = cloneDeep(model);
// TODO: refactor
for (let key in updatedModel.schema) {
const field = updatedModel.schema[key]
if (field.name && field.name !== key) {
updatedModel.schema[field.name] = field
delete updatedModel.schema[key];
}
}
const SAVE_MODEL_URL = `/api/${instanceId}/models`
const response = await api.post(SAVE_MODEL_URL, model)
const response = await api.post(SAVE_MODEL_URL, updatedModel)
const savedModel = await response.json()
store.update(state => {
@ -76,9 +85,7 @@ export const getBackendUiStore = () => {
state.models = state.models
}
state.selectedModel = savedModel
state.draftModel = savedModel
state.selectedView = `all_${savedModel._id}`
store.actions.models.select(savedModel)
return state
})
},
@ -93,11 +100,11 @@ export const getBackendUiStore = () => {
[field.name]: field
}
state.selectedField = field
state.selectedField = field.name
return state
});
}
},
},
views: {
select: view =>

View File

@ -0,0 +1,68 @@
<script>
import { onMount } from "svelte";
import { backendUiStore } from "builderStore"
import api from "builderStore/api"
export let modelId
export let linkedRecords
let records = []
async function fetchRecords() {
const FETCH_RECORDS_URL = `/api/${$backendUiStore.selectedDatabase._id}/${modelId}/records`
const response = await api.get(FETCH_RECORDS_URL)
records = await response.json()
}
onMount(() => {
fetchRecords()
})
function linkRecord(record) {
linkedRecords.push(record);
}
</script>
<section>
{#each records as record}
<div class="linked-record" on:click={linkRecord}>
<h3>{record.name}</h3>
<div class="fields">
{#each Object.keys(record) as key}
<div class="field">
<span>{key}</span>
<p>{record[key]}</p>
</div>
{/each}
</div>
</div>
{/each}
</section>
<style>
section {
background: var(--grey);
padding: 20px;
}
.fields {
padding: 20px;
display: grid;
grid-template-columns: 1fr 1fr 1fr;
grid-gap: 20px;
background: var(--white);
border: 1px solid var(--grey);
border-radius: 5px;
}
.field span {
color: var(--ink-lighter);
font-size: 12px;
}
.field p {
color: var(--ink);
font-size: 14px;
word-break: break-word;
}
</style>

View File

@ -68,10 +68,10 @@
}
}
$: paginatedData = data.slice(
$: paginatedData = data ? data.slice(
currentPage * ITEMS_PER_PAGE,
currentPage * ITEMS_PER_PAGE + ITEMS_PER_PAGE
)
) : []
onMount(() => {
if (views.length) {

View File

@ -3,6 +3,7 @@
import { store, backendUiStore } from "builderStore"
import { compose, map, get, flatten } from "lodash/fp"
import ActionButton from "components/common/ActionButton.svelte"
import LinkedRecordSelector from "components/common/LinkedRecordSelector.svelte"
import Select from "components/common/Select.svelte"
import RecordFieldControl from "./RecordFieldControl.svelte"
import * as api from "../api"
@ -73,11 +74,15 @@
<form on:submit|preventDefault class="uk-form-stacked">
{#each modelSchema as [key, meta]}
<div class="uk-margin">
<RecordFieldControl
type={determineInputType(meta)}
options={determineOptions(meta)}
label={key}
bind:value={record[key]} />
{#if meta.type === "link"}
<LinkedRecordSelector modelId={meta.modelId} />
{:else}
<RecordFieldControl
type={determineInputType(meta)}
options={determineOptions(meta)}
label={key}
bind:value={record[key]} />
{/if}
</div>
{/each}
</form>

View File

@ -13,7 +13,7 @@
<style>
.indented {
margin-left: 20px;
margin-left: 10px;
}
div {
@ -21,11 +21,11 @@
width: 260px;
height: 40px;
border-radius: 3px;
font-weight: bold;
display: flex;
align-items: center;
transition: 0.3s background-color;
color: var(--ink);
font-weight: 500;
font-size: 16px;
}

View File

@ -11,7 +11,7 @@
const { open, close } = getContext("simple-modal")
const HEADINGS = [
let HEADINGS = [
{
title: "Navigate",
key: "NAVIGATE",
@ -28,9 +28,9 @@
backendUiStore.actions.models.select(model)
}
function selectField(field) {
function selectField(fieldName) {
backendUiStore.update(state => {
state.selectedField = field
state.selectedField = fieldName
return state
});
}
@ -41,7 +41,6 @@
state.draftModel = { schema: {} }
return state
})
$goto(`./database/${$backendUiStore.selectedDatabase._id}/newmodel`)
}
</script>
@ -51,7 +50,7 @@
<div class="components-list-container">
<Switcher headings={HEADINGS} bind:value={selectedTab}>
{#if selectedTab === 'NAVIGATE'}
<Button secondary wide on:click={setupForNewModel}>Create New Model</Button>
<Button secondary wide on:click={setupForNewModel}>Create New Model</Button>
<div class="hierarchy-items-container">
{#each $backendUiStore.models as model}
<ListItem
@ -61,10 +60,11 @@
on:click={() => selectModel(model)} />
{#each Object.keys(model.schema) as field}
<ListItem
selected={field === $backendUiStore.selectedField}
indented
icon="ri-layout-column-fill"
title={field}
on:click={() => selectField({ name: field, ...model.schema[field] })} />
on:click={() => selectField(field)} />
{/each}
{/each}
</div>

View File

@ -14,12 +14,11 @@
const FIELD_TYPES = ["string", "number", "boolean", "link"]
export let field
let field = {}
$: type = field.type
$: constraints = field.constraints
$: field = $backendUiStore.draftModel.schema[$backendUiStore.selectedField] || {}
$: required =
constraints && constraints.presence && !constraints.presence.allowEmpty
field.constraints && field.constraints.presence && !constraints.presence.allowEmpty
const save = () => {
backendUiStore.actions.models.save({
@ -31,24 +30,24 @@
<form on:submit|preventDefault class="uk-form-stacked">
<Textbox label="Name" bind:text={field.name} />
<Dropdown label="Type" bind:selected={type} options={FIELD_TYPES} />
<Dropdown label="Type" bind:selected={field.type} options={FIELD_TYPES} />
<Checkbox label="Required" bind:checked={required} />
{#if type === 'string'}
<NumberBox label="Max Length" bind:value={constraints.length.maximum} />
<ValuesList label="Categories" bind:values={constraints.inclusion} />
{:else if type === 'datetime'}
<DatePicker label="Min Value" bind:value={constraints.datetime.earliest} />
<DatePicker label="Max Value" bind:value={constraints.datetime.latest} />
{:else if type === 'number'}
{#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={constraints.numericality.greaterThanOrEqualTo} />
bind:value={field.constraints.numericality.greaterThanOrEqualTo} />
<NumberBox
label="Max Value"
bind:value={constraints.numericality.lessThanOrEqualTo} />
{:else if type === 'link'}
bind:value={field.constraints.numericality.lessThanOrEqualTo} />
{:else if field.type === 'link'}
<select class="budibase__input" bind:value={field.modelId}>
<option value={''} />
{#each $backendUiStore.models as model}

View File

@ -1,6 +1,7 @@
<script>
import { getContext, onMount } from "svelte"
import { Button, Switcher } from "@budibase/bbui"
import { notifier } from "@beyonk/svelte-notifications"
import { store, backendUiStore } from "builderStore"
import api from "builderStore/api"
import FieldView from "./FieldView.svelte";
@ -27,14 +28,19 @@
$: edited = $backendUiStore.draftModel.name !== $backendUiStore.selectedModel.name
async function deleteModel() {
backendUiStore.update(async state => {
const modelToDelete = state.selectedModel
const DELETE_MODEL_URL = `/api/${state.selectedDatabase._id}/models/${modelToDelete._id}/${modelToDelete._rev}`
const response = await api.delete(DELETE_MODEL_URL)
const modelToDelete = $backendUiStore.selectedModel
if ($backendUiStore.selectedField) {
delete modelToDelete[$backendUiStore.selectedField]
}
const DELETE_MODEL_URL = `/api/${$backendUiStore.selectedDatabase._id}/models/${modelToDelete._id}/${modelToDelete._rev}`
const response = await api.delete(DELETE_MODEL_URL)
backendUiStore.update(state => {
state.selectedView = null
state.models = state.models.filter(
model => model._id !== modelToDelete._id
)
state.selectedView = {}
notifier.danger(`${modelToDelete.name} deleted successfully.`)
return state
})
}
@ -44,6 +50,7 @@
instanceId: $backendUiStore.selectedDatabase._id,
model: $backendUiStore.draftModel
})
notifier.success("Success! Your changes have been saved. Please continue on with your greatness.");
}
</script>
@ -51,7 +58,7 @@
<Switcher headings={ITEMS} bind:value={selectedTab}>
{#if selectedTab === 'SETUP'}
{#if $backendUiStore.selectedField}
<FieldView field={$backendUiStore.selectedField} />
<FieldView />
{:else}
<div class="titled-input">
<header>Name</header>
@ -64,7 +71,6 @@
<header>Import Data</header>
<Button wide secondary>Import CSV</Button>
</div>
<Button
attention
wide
@ -82,6 +88,10 @@
</div>
<style>
header {
font-weight: 500;
}
.items-root {
padding: 20px;
display: flex;

View File

@ -79,7 +79,104 @@ export const FIELDS = {
}
export const BLOCKS = {
NAME: {
name: "Name",
icon: "ri-text",
type: "string",
constraints: {
type: "string",
length: {},
presence: false,
},
},
PHONE_NUMBER: {
name: "Phone Number",
icon: "ri-number-1",
type: "number",
constraints: {
type: "number",
presence: false,
numericality: {},
},
},
ACTIVE: {
name: "Active",
icon: "ri-toggle-line",
type: "boolean",
constraints: {
type: "boolean",
presence: false,
},
},
PRIORITY: {
name: "Options",
icon: "ri-list-check-2",
type: "options",
constraints: {
type: "string",
presence: false,
inclusion: [
"low",
"medium",
"high"
]
},
},
END_DATE: {
name: "End Date",
icon: "ri-calendar-event-fill",
type: "datetime",
constraints: {
type: "date",
datetime: {},
presence: false,
},
},
AVATAR: {
name: "Avatar",
icon: "ri-image-line",
type: "image",
constraints: {
type: "string",
presence: false,
},
},
PDF: {
name: "PDF",
icon: "ri-file-line",
type: "file",
constraints: {
type: "string",
presence: false,
},
},
DATA_LINK: {
name: "Data Links",
icon: "ri-link",
type: "link",
modelId: null,
constraints: {
type: "array",
}
},
}
export const TABLES = {}
// TODO: Needs more thought, need to come up with the constraints etc for each one
export const MODELS = {
CONTACTS: {
icon: "ri-link",
name: "Contacts",
schema: {
Name: BLOCKS.NAME,
"Phone Number": BLOCKS.PHONE_NUMBER
}
},
RECIPES: {
icon: "ri-link",
name: "Recipes",
schema: {
Name: BLOCKS.NAME,
"Phone Number": BLOCKS.PHONE_NUMBER
}
}
}

View File

@ -92,7 +92,6 @@
}
html, body {
display: grid;
font-family: var(--fontnormal);
color: var(--secondary80);
padding: 0;

View File

@ -19,11 +19,17 @@
height: 90px;
border-radius: 3px;
color: var(--ink);
padding: 20px;
font-weight: 500;
padding: 15px;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
transition: 0.3s transform;
}
i {
font-size: 30px;
}
span {
@ -32,6 +38,7 @@
div:hover {
cursor: pointer;
transform: scale(1.1);
}
.primary {

View File

@ -1,5 +1,6 @@
<script>
import { getContext } from "svelte"
import NewModel from "./NewModel.svelte"
import ModelDataTable from "components/database/ModelDataTable"
import { store, backendUiStore } from "builderStore"
import ActionButton from "components/common/ActionButton.svelte"
@ -33,7 +34,9 @@
</ActionButton>
{/if}
</div>
{#if $backendUiStore.selectedDatabase._id && $backendUiStore.selectedModel.name}
{#if $backendUiStore.selectedModel.schema && Object.keys($backendUiStore.selectedModel.schema).length === 0}
<NewModel />
{:else if $backendUiStore.selectedDatabase._id && $backendUiStore.selectedModel.name}
<ModelDataTable />
{:else}
<i style="color: var(--grey-dark)">

View File

@ -1,31 +1,70 @@
<script>
import { FIELDS } from "constants/backend";
import { backendUiStore } from "builderStore";
import { FIELDS, BLOCKS, MODELS } from "constants/backend";
import Block from "./Block.svelte";
function addNewField(field) {
backendUiStore.actions.models.addField(field);
}
</script>
<section>
<header>
<h2>Create New Table</h2>
<span>Before you can view your table, you need to set it up.</span>
<p>Before you can view your table, you need to set it up.</p>
</header>
<div class="block-row">
<h4>Fields</h4>
<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} />
<Block primary title={field.name} icon={field.icon} on:click={() => addNewField(field)} />
{/each}
</div>
</div>
<!-- TODO: More block rows -->
<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} />
{/each}
</div>
</div>
<div class="block-row">
<span class="block-row-title">Models</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} />
{/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 {
@ -35,6 +74,6 @@
.block-row .blocks {
display: grid;
grid-auto-flow: column;
grid-gap: 10px;
grid-gap: 20px;
}
</style>