1
0
Fork 0
mirror of synced 2024-06-27 02:20:35 +12:00

backend v1 - styling to do

This commit is contained in:
Martin McKeaveney 2020-06-18 17:17:18 +01:00
parent 6e187943c4
commit 7c4f24c1b4
27 changed files with 251 additions and 1375 deletions

View file

@ -39,7 +39,7 @@
},
"dependencies": {
"@beyonk/svelte-notifications": "^2.0.3",
"@budibase/bbui": "^1.4.1",
"@budibase/bbui": "^1.5.0",
"@budibase/client": "^0.0.32",
"@nx-js/compiler-util": "^2.0.0",
"codemirror": "^5.51.0",

View file

@ -84,7 +84,6 @@
text-align: left;
color: var(--ink);
font-size: 14px;
padding-left: 12px;
}
.uk-text-right {

View file

@ -4,13 +4,16 @@ import api from "../api"
export const getBackendUiStore = () => {
const INITIAL_BACKEND_UI_STATE = {
breadcrumbs: [],
models: [],
views: [],
users: [],
selectedDatabase: {},
selectedModel: {},
draftModel: {}
draftModel: {},
tabs: {
SETUP_PANEL: "SETUP",
NAVIGATION_PANEL: "NAVIGATE"
}
}
const store = writable(INITIAL_BACKEND_UI_STATE)
@ -27,7 +30,6 @@ export const getBackendUiStore = () => {
if (models && models.length > 0) {
store.actions.models.select(models[0]);
}
state.breadcrumbs = [db.name]
state.models = models
state.views = views
return state
@ -40,11 +42,6 @@ export const getBackendUiStore = () => {
state.selectedView = state.selectedView
return state
}),
view: record =>
store.update(state => {
state.breadcrumbs = [state.selectedDatabase.name, record._id]
return state
}),
select: record =>
store.update(state => {
state.selectedRecord = record
@ -57,6 +54,7 @@ export const getBackendUiStore = () => {
state.draftModel = cloneDeep(model);
state.selectedField = ""
state.selectedView = `all_${model._id}`
state.tabs.SETUP_PANEL = "SETUP"
return state;
}),
save: async ({ instanceId, model }) => {
@ -102,6 +100,8 @@ export const getBackendUiStore = () => {
state.selectedField = field.name
state.tabs.NAVIGATION_PANEL = "NAVIGATE"
return state
});
},

View file

@ -7,7 +7,6 @@
export let tertiary
</script>
<!-- TODO: Move to bbui -->
<div on:click class:primary class:secondary class:tertiary>
<i class={icon} />
<span>{title}</span>
@ -15,8 +14,7 @@
<style>
div {
width: 120px;
height: 90px;
height: 80px;
border-radius: 3px;
color: var(--ink);
font-weight: 500;
@ -26,6 +24,7 @@
align-items: center;
flex-direction: column;
transition: 0.3s transform;
background: var(--light-grey);
}
i {
@ -33,7 +32,8 @@
}
span {
font-size: 16px;
font-size: 14px;
text-align: center;
}
div:hover {
@ -53,4 +53,5 @@
.tertiary {
background: var(--white);
}
</style>

View file

@ -3,8 +3,8 @@
export let label = ""
</script>
<input class="uk-checkbox" type="checkbox" bind:checked on:change />
{label}
<input class="uk-checkbox" type="checkbox" bind:checked on:change />
<style>
input {

View file

@ -13,13 +13,25 @@
let numberText = value === null || value === undefined ? "" : value.toString()
</script>
<div class="uk-margin">
<label class="uk-form-label">{label}</label>
<div class="uk-form-controls">
<input
class="budibase__input"
type="number"
{value}
on:change={inputChanged} />
</div>
<div class="numberbox">
<label>{label}</label>
<input
class="budibase__input"
type="number"
{value}
on:change={inputChanged} />
</div>
<style>
.numberbox {
display: grid;
align-items: center;
grid-template-columns: 40% 1fr;
margin-top: 8px;
margin-bottom: 8px;
}
label {
font-size: 12px;
}
</style>

View file

@ -24,7 +24,7 @@
<style>
textarea {
width: 300px;
width: 100%;
height: 100px;
}
</style>

View file

@ -1,18 +1,7 @@
<script>
import { onMount, getContext } from "svelte"
import { store, backendUiStore } from "builderStore"
import {
tap,
get,
find,
last,
compose,
flatten,
map,
remove,
keys,
takeRight,
} from "lodash/fp"
import { Button } from "@budibase/bbui"
import Select from "components/common/Select.svelte"
import ActionButton from "components/common/ActionButton.svelte"
import TablePagination from "./TablePagination.svelte"
@ -68,10 +57,22 @@
}
}
$: paginatedData = data ? data.slice(
currentPage * ITEMS_PER_PAGE,
currentPage * ITEMS_PER_PAGE + ITEMS_PER_PAGE
) : []
$: paginatedData = data
? data.slice(
currentPage * ITEMS_PER_PAGE,
currentPage * ITEMS_PER_PAGE + ITEMS_PER_PAGE
)
: []
const createNewRecord = () => {
open(
CreateEditRecordModal,
{
onClosed: close,
},
{ styleContent: { padding: "0" } }
)
}
onMount(() => {
if (views.length) {
@ -83,6 +84,12 @@
<section>
<div class="table-controls">
<h2 class="title">{$backendUiStore.selectedModel.name}</h2>
<Button primary on:click={createNewRecord}>
<span class="button-inner">
<i class="ri-add-circle-fill" />
Create New Record
</span>
</Button>
</div>
<table class="uk-table">
<thead>

View file

@ -1,156 +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) : []
$: instanceId = $backendUiStore.selectedDatabase._id
async function saveModel() {
const SAVE_MODEL_URL = `/api/${instanceId}/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 Model</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 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}
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-light);
}
.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-light);
margin-top: 40px;
padding: 20px 40px 20px 40px;
display: flex;
justify-content: flex-end;
}
</style>

View file

@ -1,104 +0,0 @@
<script>
import Dropdown from "components/common/Dropdown.svelte"
import Textbox from "components/common/Textbox.svelte"
import Button from "components/common/Button.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 { keys, cloneDeep } from "lodash/fp"
const FIELD_TYPES = ["string", "number", "boolean"]
export let field = {
type: "string",
constraints: { type: "string", presence: false },
}
export let schema
export let goBack
let errors = []
let draftField = cloneDeep(field)
let type = field.type
let constraints = field.constraints
let required =
field.constraints.presence && !field.constraints.presence.allowEmpty
const save = () => {
constraints.presence = required ? { allowEmpty: false } : false
schema[field.name] = { type, constraints }
goBack()
}
$: constraints =
type === "string"
? { type: "string", length: {}, presence: false }
: type === "number"
? { type: "number", presence: false, numericality: {} }
: type === "boolean"
? { type: "boolean", presence: false }
: type === "datetime"
? { type: "date", datetime: {}, presence: false }
: type.startsWith("array")
? { type: "array", presence: false }
: { type: "string", presence: false }
</script>
<div class="root">
<ErrorsBox {errors} />
<form on:submit|preventDefault class="uk-form-stacked">
<Textbox label="Name" bind:text={field.name} />
<Dropdown label="Type" bind:selected={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'}
<NumberBox
label="Min Value"
bind:value={constraints.numericality.greaterThanOrEqualTo} />
<NumberBox
label="Max Value"
bind:value={constraints.numericality.lessThanOrEqualTo} />
{/if}
</form>
</div>
<footer>
<div class="button">
<ActionButton secondary on:click={goBack}>Cancel</ActionButton>
</div>
<ActionButton primary on:click={save}>Save</ActionButton>
</footer>
<style>
.root {
margin: 40px;
}
footer {
padding: 20px 40px;
border-radius: 0 0 5px 5px;
bottom: 0;
left: 0;
background: var(--grey-light);
display: flex;
align-items: center;
justify-content: flex-end;
}
.button {
margin-right: 20px;
}
</style>

View file

@ -1,6 +1,7 @@
<script>
import { onMount } from "svelte"
import { store, backendUiStore } from "builderStore"
import { notifier } from "@beyonk/svelte-notifications";
import { compose, map, get, flatten } from "lodash/fp"
import ActionButton from "components/common/ActionButton.svelte"
import LinkedRecordSelector from "components/common/LinkedRecordSelector.svelte"
@ -63,6 +64,7 @@
backendUiStore.update(state => {
state.selectedView = state.selectedView
onClosed()
notifier.success("Record created successfully.");
return state
})
}

View file

@ -1,6 +1,5 @@
export { default as DeleteRecordModal } from "./DeleteRecord.svelte"
export { default as CreateEditRecordModal } from "./CreateEditRecord.svelte"
export { default as CreateEditModelModal } from "./CreateEditModel/CreateEditModel.svelte"
export { default as CreateEditViewModal } from "./CreateEditView.svelte"
export { default as CreateDatabaseModal } from "./CreateDatabase.svelte"
export { default as CreateUserModal } from "./CreateUser.svelte"

View file

@ -1,87 +0,0 @@
<script>
import { getContext } from "svelte"
import { store, backendUiStore } from "builderStore"
import HierarchyRow from "./HierarchyRow.svelte"
import DatabasesList from "./DatabasesList.svelte"
import UsersList from "./UsersList.svelte"
import NavItem from "./NavItem.svelte"
import getIcon from "components/common/icon"
import {
CreateDatabaseModal,
CreateUserModal,
} from "components/database/ModelDataTable/modals"
const { open, close } = getContext("simple-modal")
const openDatabaseCreator = () => {
open(
CreateDatabaseModal,
{
onClosed: close,
},
{ styleContent: { padding: "0" } }
)
}
const openUserCreator = () => {
open(
CreateUserModal,
{
onClosed: close,
},
{ styleContent: { padding: "0" } }
)
}
</script>
<div class="items-root">
<div class="hierarchy" />
{#if $backendUiStore.selectedDatabase._id}
<div class="hierarchy">
<div class="components-list-container">
<div class="nav-group-header">
<div class="hierarchy-title">Users</div>
<i class="ri-add-line hoverable" on:click={openUserCreator} />
</div>
</div>
<div class="hierarchy-items-container">
<UsersList />
</div>
</div>
{/if}
</div>
<style>
.items-root {
display: flex;
flex-direction: column;
max-height: 100%;
height: 100%;
background: var(--white);
}
.nav-group-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px 20px 10px 20px;
}
.hierarchy-title {
align-items: center;
font-size: 18px;
font-weight: 700;
text-rendering: optimizeLegibility;
color: var(--ink);
}
.hierarchy {
display: flex;
flex-direction: column;
}
.hierarchy-items-container {
flex: 1 1 auto;
overflow-y: auto;
}
</style>

View file

@ -4,7 +4,6 @@
import { cloneDeep } from "lodash/fp"
import getIcon from "../common/icon"
import {
CreateEditModelModal,
CreateEditViewModal,
} from "components/database/ModelDataTable/modals"
import api from "builderStore/api"

View file

@ -1,47 +0,0 @@
<script>
export let title
export let icon
export let primary
export let secondary
export let tertiary
</script>
<div on:click class:primary class:secondary class:tertiary>
<i class={icon} />
<span>{title}</span>
</div>
<style>
div {
height: 90px;
border-radius: 3px;
color: var(--ink);
padding: 20px;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
span {
font-size: 16px;
}
div:hover {
cursor: pointer;
}
.primary {
background: var(--ink);
color: var(--white);
}
.secondary {
background: var(--secondary);
}
.tertiary {
background: var(--white);
}
</style>

View file

@ -1,7 +1,7 @@
<script>
import * as blockDefinitions from "constants/backend"
import { backendUiStore } from "builderStore";
import Block from "./Block.svelte"
import Block from "components/common/Block.svelte"
const HEADINGS = [
{
@ -13,8 +13,8 @@
key: "BLOCKS",
},
{
title: "Table",
key: "TABLES",
title: "Model",
key: "MODELS",
},
]
@ -40,9 +40,6 @@
{#each Object.values(blockDefinitions[selectedTab]) as blockDefinition}
<Block
on:click={() => addField(blockDefinition)}
primary={true}
secondary={false}
tertiary={false}
title={blockDefinition.name}
icon={blockDefinition.icon} />
{/each}
@ -53,11 +50,15 @@
header {
margin-top: 20px;
margin-bottom: 20px;
display: grid;
grid-gap: 20px;
grid-template-columns: repeat(3, 1fr);
}
span {
margin-right: 20px;
text-align: center;
padding: 10px;
font-weight: 500;
border-radius: 3px;
color: var(--ink-lighter);
font-size: 14px;

View file

@ -1,22 +1,23 @@
<script>
import { backendUiStore } from "builderStore";
import { fade } from 'svelte/transition';
import { FIELDS, BLOCKS, MODELS } from "constants/backend";
import Block from "./Block.svelte";
import Block from "components/common/Block.svelte";
function addNewField(field) {
backendUiStore.actions.models.addField(field);
}
</script>
<section>
<section transition:fade>
<header>
<h2>Create New Table</h2>
<p>Before you can view your table, you need to set it up.</p>
<h2>Create New Model</h2>
<p>Before you can view your model, 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>
<p>Blocks are pre-made fields and help you build your model quicker.</p>
<div class="blocks">
{#each Object.values(FIELDS) as field}
<Block primary title={field.name} icon={field.icon} on:click={() => addNewField(field)} />
@ -26,17 +27,17 @@
<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>
<p>Blocks are pre-made fields and help you build your model quicker.</p>
<div class="blocks">
{#each Object.values(BLOCKS) as field}
<Block secondary title={field.name} icon={field.icon} />
<Block secondary title={field.name} icon={field.icon} on:click={() => addNewField(field)} />
{/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>
<p>Blocks are pre-made fields and help you build your model quicker.</p>
<div class="blocks">
{#each Object.values(MODELS) as model}
<Block tertiary title={model.name} icon={model.icon} />
@ -74,6 +75,7 @@
.block-row .blocks {
display: grid;
grid-auto-flow: column;
grid-auto-columns: 110px;
grid-gap: 20px;
}
</style>

View file

@ -18,7 +18,7 @@
div {
padding: 0 10px 0 10px;
width: 260px;
width: 90%;
height: 40px;
border-radius: 3px;
display: flex;
@ -27,6 +27,8 @@
color: var(--ink);
font-weight: 500;
font-size: 16px;
margin-top: 4px;
margin-bottom: 4px;
}
.selected {

View file

@ -1,11 +1,10 @@
<script>
import { getContext } from "svelte"
import { slide } from 'svelte/transition';
import { Switcher } from "@budibase/bbui"
import { goto } from "@sveltech/routify"
import { store, backendUiStore } from "builderStore"
import BlockNavigator from "./BlockNavigator.svelte"
import SchemaManagementDrawer from "../SchemaManagementDrawer.svelte"
import HierarchyRow from "../HierarchyRow.svelte"
import ListItem from "./ListItem.svelte"
import { Button } from "@budibase/bbui"
@ -22,17 +21,17 @@
},
]
let selectedTab = "NAVIGATE"
$: selectedTab = $backendUiStore.tabs.NAVIGATION_PANEL
function selectModel(model) {
function selectModel(model, fieldName) {
backendUiStore.actions.models.select(model)
}
function selectField(fieldName) {
backendUiStore.update(state => {
state.selectedField = fieldName
return state
});
if (fieldName) {
backendUiStore.update(state => {
state.selectedField = fieldName
return state
});
}
}
function setupForNewModel() {
@ -48,24 +47,28 @@
{#if $backendUiStore.selectedDatabase && $backendUiStore.selectedDatabase._id}
<div class="hierarchy">
<div class="components-list-container">
<Switcher headings={HEADINGS} bind:value={selectedTab}>
<Switcher headings={HEADINGS} bind:value={$backendUiStore.tabs.NAVIGATION_PANEL}>
{#if selectedTab === 'NAVIGATE'}
<Button secondary wide on:click={setupForNewModel}>Create New Model</Button>
<div class="hierarchy-items-container">
{#each $backendUiStore.models as model}
<ListItem
selected={model._id === $backendUiStore.selectedModel._id}
selected={!$backendUiStore.selectedField && model._id === $backendUiStore.selectedModel._id}
title={model.name}
icon="ri-table-fill"
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(field)} />
{/each}
{#if model._id === $backendUiStore.selectedModel._id}
<div in:slide>
{#each Object.keys(model.schema) as field}
<ListItem
selected={model._id === $backendUiStore.selectedModel._id && field === $backendUiStore.selectedField}
indented
icon="ri-layout-column-fill"
title={field}
on:click={() => selectModel(model, field)} />
{/each}
</div>
{/if}
{/each}
</div>
{:else if selectedTab === 'ADD'}

View file

@ -1,63 +0,0 @@
<script>
import { backendUiStore } from "builderStore"
import { Button } from "@budibase/bbui"
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 { keys, cloneDeep } from "lodash/fp"
const FIELD_TYPES = ["string", "number", "boolean", "link"]
let field = {}
$: field = $backendUiStore.draftModel.schema[$backendUiStore.selectedField] || {}
$: required =
field.constraints && field.constraints.presence && !constraints.presence.allowEmpty
const save = () => {
backendUiStore.actions.models.save({
instanceId: $backendUiStore.selectedDatabase._id,
model: $backendUiStore.draftModel
})
}
</script>
<form on:submit|preventDefault class="uk-form-stacked">
<Textbox label="Name" bind:text={field.name} />
<Dropdown label="Type" bind:selected={field.type} options={FIELD_TYPES} />
<Checkbox label="Required" bind:checked={required} />
{#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'}
<select class="budibase__input" bind:value={field.modelId}>
<option value={''} />
{#each $backendUiStore.models as model}
<option value={model._id}>{model.name}</option>
{/each}
</select>
{/if}
</form>
<Button attention wide on:click={save}>Save</Button>
<style>
</style>

View file

@ -0,0 +1,101 @@
<script>
import { backendUiStore } from "builderStore"
import { Button } from "@budibase/bbui"
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 { keys, cloneDeep } from "lodash/fp"
const FIELD_TYPES = ["string", "number", "boolean", "link"]
let field = {}
$: field =
$backendUiStore.draftModel.schema[$backendUiStore.selectedField] || {}
$: required =
field.constraints &&
field.constraints.presence &&
!constraints.presence.allowEmpty
</script>
<div class="info">
<div class="field-box">
<header>Name</header>
<input class="budibase__input" type="text" bind:value={field.name} />
</div>
</div>
<div class="info">
<div class="field-box">
<header>Type</header>
<span>{field.type}</span>
</div>
</div>
<div class="info">
<div class="required-field">
<label>Required</label>
<input type="checkbox" />
</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'}
<select class="budibase__input" bind:value={field.modelId}>
<option value={''} />
{#each $backendUiStore.models as model}
<option value={model._id}>{model.name}</option>
{/each}
</select>
{/if}
</div>
<style>
.info {
background: var(--light-grey);
padding: 12px;
margin-bottom: 5px;
border-radius: 3px;
}
label {
font-size: 12px;
}
.required-field {
display: grid;
align-items: center;
grid-template-columns: 40% 1fr;
}
.field-box header {
font-size: 14px;
font-weight: 500;
margin-bottom: 16px;
}
</style>

View file

@ -4,7 +4,7 @@
import { notifier } from "@beyonk/svelte-notifications"
import { store, backendUiStore } from "builderStore"
import api from "builderStore/api"
import FieldView from "./FieldView.svelte";
import ModelFieldEditor from "./ModelFieldEditor.svelte"
const { open, close } = getContext("simple-modal")
@ -13,52 +13,57 @@
title: "Setup",
key: "SETUP",
},
{
title: "Filter",
key: "FILTER",
},
{
title: "Delete",
key: "DELETE",
},
]
let selectedTab = "SETUP"
$: selectedTab = $backendUiStore.tabs.SETUP_PANEL
$: edited = $backendUiStore.draftModel.name !== $backendUiStore.selectedModel.name
$: edited =
$backendUiStore.draftModel.name !== $backendUiStore.selectedModel.name
async function deleteModel() {
const modelToDelete = $backendUiStore.selectedModel
if ($backendUiStore.selectedField) {
delete modelToDelete[$backendUiStore.selectedField]
const model = $backendUiStore.selectedModel
const instanceId = $backendUiStore.selectedDatabase._id
const field = $backendUiStore.selectedField
if (field) {
delete model.schema[field]
backendUiStore.actions.models.save({ model, instanceId });
notifier.danger(`Field ${field} deleted.`);
return;
}
const DELETE_MODEL_URL = `/api/${$backendUiStore.selectedDatabase._id}/models/${modelToDelete._id}/${modelToDelete._rev}`
const DELETE_MODEL_URL = `/api/${instanceId}/models/${model._id}/${model._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
({ _id }) => _id !== model._id
)
notifier.danger(`${modelToDelete.name} deleted successfully.`)
notifier.danger(`${model.name} deleted successfully.`)
return state
})
}
async function saveModel() {
await backendUiStore.actions.models.save({
await backendUiStore.actions.models.save({
instanceId: $backendUiStore.selectedDatabase._id,
model: $backendUiStore.draftModel
model: $backendUiStore.draftModel,
})
notifier.success("Success! Your changes have been saved. Please continue on with your greatness.");
notifier.success(
"Success! Your changes have been saved. Please continue on with your greatness."
)
}
</script>
<div class="items-root">
<Switcher headings={ITEMS} bind:value={selectedTab}>
<Switcher headings={ITEMS} bind:value={$backendUiStore.tabs.SETUP_PANEL}>
{#if selectedTab === 'SETUP'}
{#if $backendUiStore.selectedField}
<FieldView />
<ModelFieldEditor />
{:else}
<div class="titled-input">
<header>Name</header>
@ -71,13 +76,10 @@
<header>Import Data</header>
<Button wide secondary>Import CSV</Button>
</div>
<Button
attention
wide
on:click={saveModel}>
Save
</Button>
{/if}
<footer>
<Button attention wide on:click={saveModel}>Save</Button>
</footer>
{:else if selectedTab === 'DELETE'}
<div class="titled-input">
<header>Danger Zone</header>
@ -92,6 +94,10 @@
font-weight: 500;
}
footer {
width: 100%;
}
.items-root {
padding: 20px;
display: flex;
@ -102,9 +108,8 @@
}
.titled-input {
padding: 20px;
padding: 12px;
background: var(--light-grey);
margin-top: 20px;
}
.titled-input header {

View file

@ -1,143 +0,0 @@
<script>
import { getContext, onMount } from "svelte"
import { store, backendUiStore } from "builderStore"
import HierarchyRow from "./HierarchyRow.svelte"
import NavItem from "./NavItem.svelte"
import getIcon from "components/common/icon"
import api from "builderStore/api"
import {
CreateEditModelModal,
CreateEditViewModal,
} from "components/database/ModelDataTable/modals"
const { open, close } = getContext("simple-modal")
function editModel() {
open(
CreateEditModelModal,
{
model: node,
onClosed: close,
},
{ styleContent: { padding: "0" } }
)
}
function newModel() {
open(
CreateEditModelModal,
{
onClosed: close,
},
{ styleContent: { padding: "0" } }
)
}
function newView() {
open(
CreateEditViewModal,
{
onClosed: close,
},
{ styleContent: { padding: "0" } }
)
}
function selectModel(model) {
backendUiStore.update(state => {
state.selectedModel = model
state.selectedView = `all_${model._id}`
return state
})
}
async function deleteModel(modelToDelete) {
const DELETE_MODEL_URL = `/api/${instanceId}/models/${node._id}/${node._rev}`
const response = await api.delete(DELETE_MODEL_URL)
backendUiStore.update(state => {
state.models = state.models.filter(
model => model._id !== modelToDelete._id
)
state.selectedView = {}
return state
})
}
function selectView(view) {
backendUiStore.update(state => {
state.selectedView = view.name
return state
})
}
</script>
<div class="items-root">
<div class="hierarchy">
<div class="components-list-container">
<div class="nav-group-header">
<div class="hierarchy-title">Models</div>
<div class="uk-inline">
<i class="ri-add-line hoverable" on:click={newModel} />
</div>
</div>
</div>
<div class="hierarchy-items-container">
{#each $backendUiStore.models as model}
<HierarchyRow onSelect={selectModel} node={model} type="model" />
{/each}
</div>
</div>
<div class="hierarchy">
<div class="components-list-container">
<div class="nav-group-header">
<div class="hierarchy-title">Views</div>
<div class="uk-inline">
<i class="ri-add-line hoverable" on:click={newView} />
</div>
</div>
</div>
<div class="hierarchy-items-container">
{#each $backendUiStore.views as view}
<HierarchyRow onSelect={selectView} node={view} type="view" />
{/each}
</div>
</div>
</div>
<style>
.items-root {
display: flex;
flex-direction: column;
max-height: 100%;
height: 100%;
background-color: var(--white);
}
.nav-group-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px 20px 10px 20px;
}
.hierarchy-title {
align-items: center;
font-size: 18px;
font-weight: 700;
text-rendering: optimizeLegibility;
color: var(--ink);
}
.hierarchy {
display: flex;
flex-direction: column;
}
.hierarchy-items-container {
flex: 1 1 auto;
overflow-y: auto;
}
</style>

View file

@ -50,9 +50,9 @@ export const FIELDS = {
},
},
IMAGE: {
name: "Image",
name: "File",
icon: "ri-image-line",
type: "image",
type: "file",
constraints: {
type: "string",
presence: false,

View file

@ -1,647 +0,0 @@
body, html {
margin: 0px;
padding: 0px;
overflow: hidden;
background-repeat: repeat;
background-size: 30px 30px;
background-color: #FBFBFB;
height: 100%;
}
#navigation {
height: 71px;
background-color: #FFF;
border: 1px solid #E8E8EF;
width: 100%;
display: table;
box-sizing: border-box;
position: fixed;
top: 0;
z-index: 9
}
#back {
width: 40px;
height: 40px;
border-radius: 100px;
background-color: #F1F4FC;
text-align: center;
display: inline-block;
vertical-align: top;
margin-top: 12px;
margin-right: 10px
}
#back img {
margin-top: 13px;
}
#names {
display: inline-block;
vertical-align: top;
}
#title {
font-family: Roboto;
font-weight: 500;
font-size: 16px;
color: #393C44;
margin-bottom: 0px;
}
#subtitle {
font-family: Roboto;
color: #808292;
font-size: 14px;
margin-top: 5px;
}
#leftside {
display: inline-block;
vertical-align: middle;
margin-left: 20px;
}
#centerswitch {
position: absolute;
width: 222px;
left: 50%;
margin-left: -111px;
top: 15px;
}
#leftswitch {
border: 1px solid #E8E8EF;
background-color: #FBFBFB;
width: 111px;
height: 39px;
line-height: 39px;
border-radius: 5px 0px 0px 5px;
font-family: Roboto;
color: #393C44;
display: inline-block;
font-size: 14px;
text-align: center;
}
#rightswitch {
font-family: Roboto;
color: #808292;
border-radius: 0px 5px 5px 0px;
border: 1px solid #E8E8EF;
height: 39px;
width: 102px;
display: inline-block;
font-size: 14px;
line-height: 39px;
text-align: center;
margin-left: -5px;
}
#discard {
font-family: Roboto;
font-weight: 500;
font-size: 14px;
color: #A6A6B3;
width: 95px;
height: 38px;
border: 1px solid #E8E8EF;
border-radius: 5px;
text-align: center;
line-height: 38px;
display: inline-block;
vertical-align: top;
transition: all .2s cubic-bezier(.05,.03,.35,1);
}
#discard:hover {
cursor: pointer;
opacity: .7;
}
#publish {
font-family: Roboto;
font-weight: 500;
font-size: 14px;
color: #FFF;
background-color: #217CE8;
border-radius: 5px;
width: 143px;
height: 38px;
margin-left: 10px;
display: inline-block;
vertical-align: top;
text-align: center;
line-height: 38px;
margin-right: 20px;
transition: all .2s cubic-bezier(.05,.03,.35,1);
}
#publish:hover {
cursor: pointer;
opacity: .7;
}
#buttonsright {
float: right;
margin-top: 15px;
}
#leftcard {
width: 363px;
background-color: #FFF;
border: 1px solid #E8E8EF;
box-sizing: border-box;
padding-top: 85px;
padding-left: 20px;
height: 100%;
position: absolute;
z-index: 2;
}
#search input {
width: 318px;
height: 40px;
background-color: #FFF;
border: 1px solid #E8E8EF;
box-sizing: border-box;
box-shadow: 0px 2px 8px rgba(34,34,87,0.05);
border-radius: 5px;
text-indent: 35px;
font-family: Roboto;
font-size: 16px;
}
::-webkit-input-placeholder { /* Edge */
color: #C9C9D5;
}
:-ms-input-placeholder { /* Internet Explorer 10-11 */
color: #C9C9D5
}
::placeholder {
color: #C9C9D5;
}
#search img {
position: absolute;
margin-top: 10px;
width: 18px;
margin-left: 12px;
}
#header {
font-size: 20px;
font-family: Roboto;
font-weight: bold;
color: #393C44;
}
#subnav {
border-bottom: 1px solid #E8E8EF;
width: calc(100% + 20px);
margin-left: -20px;
margin-top: 10px;
}
.navdisabled {
transition: all .3s cubic-bezier(.05,.03,.35,1);
}
.navdisabled:hover {
cursor: pointer;
opacity: .5;
}
.navactive {
color: #393C44!important;
}
#triggers {
margin-left: 20px;
font-family: Roboto;
font-weight: 500;
font-size: 14px;
text-align: center;
color: #808292;
width: calc(88% / 3);
height: 48px;
line-height: 48px;
display: inline-block;
float: left;
}
.navactive:after {
display: block;
content: "";
width: 100%;
height: 4px;
background-color: #217CE8;
margin-top: -4px;
}
#actions {
display: inline-block;
font-family: Roboto;
font-weight: 500;
color: #808292;
font-size: 14px;
height: 48px;
line-height: 48px;
width: calc(88% / 3);
text-align: center;
float: left;
}
#loggers {
width: calc(88% / 3);
display: inline-block;
font-family: Roboto;
font-weight: 500;
color: #808292;
font-size: 14px;
height: 48px;
line-height: 48px;
text-align: center;
}
#footer {
position: absolute;
left: 0;
padding-left: 20px;
line-height: 40px;
bottom: 0;
width: 362px;
border: 1px solid #E8E8EF;
height: 67px;
box-sizing: border-box;
background-color: #FFF;
font-family: Roboto;
font-size: 14px;
}
#footer a {
text-decoration: none;
color: #393C44;
transition: all .2s cubic-bezier(.05,.03,.35,1);
}
#footer a:hover {
opacity: .5;
}
#footer span {
color: #808292;
}
#footer p {
display: inline-block;
color: #808292;
}
#footer img {
margin-left: 5px;
margin-right: 5px;
}
.blockelem:first-child {
margin-top: 20px
}
.blockelem {
padding-top: 10px;
width: 318px;
border: 1px solid transparent;
transition-property: box-shadow, height;
transition-duration: .2s;
transition-timing-function: cubic-bezier(.05,.03,.35,1);
border-radius: 5px;
box-shadow: 0px 0px 30px rgba(22, 33, 74, 0);
box-sizing: border-box;
}
.blockelem:hover {
box-shadow: 0px 4px 30px rgba(22, 33, 74, 0.08);
border-radius: 5px;
background-color: #FFF;
cursor: pointer;
}
.grabme, .blockico {
display: inline-block;
}
.grabme {
margin-top: 10px;
margin-left: 10px;
margin-bottom: -14px;
width: 15px;
}
#blocklist {
height: calc(100% - 220px);
overflow: auto;
}
#proplist {
height: calc(100% - 305px);
overflow: auto;
margin-top: -30px;
padding-top: 30px;
}
.blockin {
display: inline-block;
vertical-align: top;
margin-left: 12px;
}
.blockico {
width: 36px;
height: 36px;
background-color: #F1F4FC;
border-radius: 5px;
text-align: center;
white-space: nowrap;
}
.blockico i {
font-size: 24px;
color: var(--dark-grey);
}
.blockico span {
height: 100%;
width: 0px;
display: inline-block;
vertical-align: middle;
}
.blockico img {
vertical-align: middle;
margin-left: auto;
margin-right: auto;
display: inline-block;
}
.blocktext {
display: inline-block;
width: 220px;
vertical-align: top;
margin-left: 12px
}
.blocktitle {
margin: 0px!important;
padding: 0px!important;
font-family: Roboto;
font-weight: 500;
font-size: 16px;
color: #393C44;
}
.blockdesc {
margin-top: 5px;
font-family: Roboto;
color: #808292;
font-size: 14px;
line-height: 21px;
}
.blockdisabled {
background-color: #F0F2F9;
opacity: .5;
}
#closecard {
position: absolute;
margin-left: 340px;
background-color: #FFF;
border-radius: 0px 5px 5px 0px;
border-bottom: 1px solid #E8E8EF;
border-right: 1px solid #E8E8EF;
border-top: 1px solid #E8E8EF;
width: 53px;
height: 53px;
text-align: center;
z-index: 10;
}
#closecard img {
margin-top: 15px
}
#canvas {
border: 1px solid green;
position: absolute;
width: calc(100% - 361px);
height: calc(100% - 71px);
top: 71px;
left: 361px;
z-index: 0;
overflow: auto;
}
#propwrap {
position: absolute;
right: 0;
top: 0;
width: 311px;
height: 100%;
padding-left: 20px;
overflow: hidden;
z-index: -2;
}
#properties {
position: absolute;
height: 100%;
width: 311px;
background-color: #FFF;
right: -150px;
opacity: 0;
z-index: 2;
top: 0px;
box-shadow: -4px 0px 40px rgba(26, 26, 73, 0);
padding-left: 20px;
transition: all .25s cubic-bezier(.05,.03,.35,1);
}
.itson {
z-index: 2!important;
}
.expanded {
right: 0!important;
opacity: 1!important;
box-shadow: -4px 0px 40px rgba(26, 26, 73, 0.05);
z-index: 2;
}
#header2 {
font-size: 20px;
font-family: Roboto;
font-weight: bold;
color: #393C44;
margin-top: 101px;
}
#close {
margin-top: 100px;
position: absolute;
right: 20px;
z-index: 9999;
transition: all .25s cubic-bezier(.05,.03,.35,1);
}
#close:hover {
cursor: pointer;
opacity: .7;
}
#propswitch {
border-bottom: 1px solid #E8E8EF;
width: 331px;
margin-top: 10px;
margin-left: -20px;
margin-bottom: 30px;
}
#dataprop {
font-family: Roboto;
font-weight: 500;
font-size: 14px;
text-align: center;
color: #393C44;
width: calc(88% / 3);
height: 48px;
line-height: 48px;
display: inline-block;
float: left;
margin-left: 20px;
}
#dataprop:after {
display: block;
content: "";
width: 100%;
height: 4px;
background-color: #217CE8;
margin-top: -4px;
}
#alertprop {
display: inline-block;
font-family: Roboto;
font-weight: 500;
color: #808292;
font-size: 14px;
height: 48px;
line-height: 48px;
width: calc(88% / 3);
text-align: center;
float: left;
}
#logsprop {
width: calc(88% / 3);
display: inline-block;
font-family: Roboto;
font-weight: 500;
color: #808292;
font-size: 14px;
height: 48px;
line-height: 48px;
text-align: center;
}
.inputlabel {
font-family: Roboto;
font-size: 14px;
color: #253134;
}
.dropme {
background-color: #FFF;
border-radius: 5px;
border: 1px solid #E8E8EF;
box-shadow: 0px 2px 8px rgba(34, 34, 87, 0.05);
font-family: Roboto;
font-size: 14px;
color: #253134;
text-indent: 20px;
height: 40px;
line-height: 40px;
width: 287px;
margin-bottom: 25px;
}
.dropme img {
margin-top: 17px;
float: right;
margin-right: 15px;
}
.checkus {
margin-bottom: 10px;
}
.checkus img {
display: inline-block;
vertical-align: middle;
}
.checkus p {
display: inline-block;
font-family: Roboto;
font-size: 14px;
vertical-align: middle;
margin-left: 10px;
}
#divisionthing {
height: 1px;
width: 100%;
background-color: #E8E8EF;
position: absolute;
right: 0px;
bottom: 80;
}
#removeblock {
border-radius: 5px;
position: absolute;
bottom: 20px;
font-family: Roboto;
font-size: 14px;
text-align: center;
width: 287px;
height: 38px;
line-height: 38px;
color: #253134;
border: 1px solid #E8E8EF;
transition: all .3s cubic-bezier(.05,.03,.35,1);
}
#removeblock:hover {
cursor: pointer;
opacity: .5;
}
.noselect {
-webkit-touch-callout: none; /* iOS Safari */
-webkit-user-select: none; /* Safari */
-khtml-user-select: none; /* Konqueror HTML */
-moz-user-select: none; /* Old versions of Firefox */
-ms-user-select: none; /* Internet Explorer/Edge */
user-select: none; /* Non-prefixed version, currently
supported by Chrome, Opera and Firefox */
}
.blockyname {
font-family: Roboto;
font-weight: 500;
color: #253134;
display: inline-block;
vertical-align: middle;
margin-left: 8px;
font-size: 16px;
}
.blockyleft img {
display: inline-block;
vertical-align: middle;
}
.blockyright {
display: inline-block;
float: right;
vertical-align: middle;
margin-right: 20px;
margin-top: 10px;
width: 28px;
height: 28px;
border-radius: 5px;
text-align: center;
background-color: #FFF;
transition: all .3s cubic-bezier(.05,.03,.35,1);
z-index: 10;
}
.blockyright:hover {
background-color: #F1F4FC;
cursor: pointer;
}
.blockyright img {
margin-top: 12px;
}
.blockyleft {
display: inline-block;
margin-left: 20px;
}
.blockydiv {
width: 100%;
height: 1px;
background-color: #E9E9EF;
}
.blockyinfo {
font-family: Roboto;
font-size: 14px;
color: #808292;
margin-top: 15px;
text-indent: 20px;
margin-bottom: 20px;
}
.blockyinfo span {
color: #253134;
font-weight: 500;
display: inline-block;
border-bottom: 1px solid #D3DCEA;
line-height: 20px;
text-indent: 0px;
}
.block {
background-color: #FFF;
margin-top: 0px!important;
box-shadow: 0px 4px 30px rgba(22, 33, 74, 0.05);
}
.selectedblock {
border: 2px solid #217CE8;
box-shadow: 0px 4px 30px rgba(22, 33, 74, 0.08);
}
@media only screen and (max-width: 832px) {
#centerswitch {
display: none;
}
}
@media only screen and (max-width: 560px) {
#names {
display: none;
}
}

View file

@ -1,6 +1,7 @@
<script>
import { getContext } from "svelte"
import NewModel from "./NewModel.svelte"
import { Button } from "@budibase/bbui";
import EmptyModel from "components/nav/ModelNavigator/EmptyModel.svelte"
import ModelDataTable from "components/database/ModelDataTable"
import { store, backendUiStore } from "builderStore"
import ActionButton from "components/common/ActionButton.svelte"
@ -9,6 +10,8 @@
const { open, close } = getContext("simple-modal")
$: selectedModel = $backendUiStore.selectedModel
const createNewRecord = () => {
open(
CreateEditRecordModal,
@ -18,25 +21,11 @@
{ styleContent: { padding: "0" } }
)
}
export let selectedDatabase
let selectedRecord
$: breadcrumbs = $backendUiStore.breadcrumbs.join(" / ")
</script>
<div class="database-actions">
<div class="budibase__label--big">{breadcrumbs}</div>
{#if $backendUiStore.selectedModel._id}
<ActionButton primary on:click={createNewRecord}>
Create new record
</ActionButton>
{/if}
</div>
{#if $backendUiStore.selectedModel.schema && Object.keys($backendUiStore.selectedModel.schema).length === 0}
<NewModel />
{:else if $backendUiStore.selectedDatabase._id && $backendUiStore.selectedModel.name}
{#if selectedModel.schema && Object.keys(selectedModel.schema).length === 0}
<EmptyModel />
{:else if $backendUiStore.selectedDatabase._id && selectedModel.name}
<ModelDataTable />
{:else}
<i style="color: var(--grey-dark)">
@ -45,8 +34,8 @@
{/if}
<style>
.database-actions {
display: flex;
justify-content: space-between;
i {
font-size: 20px;
margin-right: 10px;
}
</style>