forked from tsai/budibase
19 changed files with 580 additions and 36 deletions
@ -0,0 +1,47 @@ |
|||||
|
<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> |
||||
@ -0,0 +1,79 @@ |
|||||
|
<script> |
||||
|
import * as blockDefinitions from "constants/backend" |
||||
|
import Block from "./Block.svelte" |
||||
|
|
||||
|
console.log(blockDefinitions) |
||||
|
|
||||
|
const HEADINGS = [ |
||||
|
{ |
||||
|
title: "Fields", |
||||
|
key: "FIELDS", |
||||
|
}, |
||||
|
{ |
||||
|
title: "Blocks", |
||||
|
key: "BLOCKS", |
||||
|
}, |
||||
|
{ |
||||
|
title: "Table", |
||||
|
key: "TABLES", |
||||
|
}, |
||||
|
] |
||||
|
|
||||
|
let selectedTab = "FIELDS" |
||||
|
</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 |
||||
|
primary={true} |
||||
|
secondary={false} |
||||
|
tertiary={false} |
||||
|
title={blockDefinition.name} |
||||
|
icon={blockDefinition.icon} /> |
||||
|
{/each} |
||||
|
</div> |
||||
|
</section> |
||||
|
|
||||
|
<style> |
||||
|
header { |
||||
|
margin-top: 20px; |
||||
|
margin-bottom: 20px; |
||||
|
} |
||||
|
|
||||
|
span { |
||||
|
margin-right: 20px; |
||||
|
padding: 10px; |
||||
|
border-radius: 3px; |
||||
|
color: var(--ink-lighter); |
||||
|
font-size: 14px; |
||||
|
background: var(--light-grey); |
||||
|
} |
||||
|
|
||||
|
span:hover { |
||||
|
background: var(--secondary); |
||||
|
cursor: pointer; |
||||
|
} |
||||
|
|
||||
|
.selected { |
||||
|
background: var(--secondary); |
||||
|
color: var(--ink); |
||||
|
} |
||||
|
|
||||
|
.block-grid { |
||||
|
margin-top: 20px; |
||||
|
display: grid; |
||||
|
grid-template-columns: 1fr 1fr; |
||||
|
grid-gap: 20px; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,46 @@ |
|||||
|
<script> |
||||
|
export let icon |
||||
|
export let className |
||||
|
export let title |
||||
|
export let selected |
||||
|
export let indented |
||||
|
</script> |
||||
|
|
||||
|
<div class:selected on:click class={className}> |
||||
|
<i class:indented class={icon} /> |
||||
|
<span>{title}</span> |
||||
|
</div> |
||||
|
|
||||
|
<style> |
||||
|
.indented { |
||||
|
margin-left: 20px; |
||||
|
} |
||||
|
|
||||
|
div { |
||||
|
padding: 0 10px 0 10px; |
||||
|
width: 260px; |
||||
|
height: 40px; |
||||
|
border-radius: 3px; |
||||
|
font-weight: bold; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
transition: 0.3s background-color; |
||||
|
color: var(--ink); |
||||
|
font-size: 16px; |
||||
|
} |
||||
|
|
||||
|
.selected { |
||||
|
background-color: var(--secondary); |
||||
|
} |
||||
|
|
||||
|
div:hover { |
||||
|
background-color: var(--secondary); |
||||
|
cursor: pointer; |
||||
|
} |
||||
|
|
||||
|
i { |
||||
|
color: var(--dark-grey); |
||||
|
font-size: 20px; |
||||
|
margin-right: 8px; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,90 @@ |
|||||
|
<script> |
||||
|
import { getContext } from "svelte" |
||||
|
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" |
||||
|
|
||||
|
const { open, close } = getContext("simple-modal") |
||||
|
|
||||
|
const HEADINGS = [ |
||||
|
{ |
||||
|
title: "Navigate", |
||||
|
key: "NAVIGATE", |
||||
|
}, |
||||
|
{ |
||||
|
title: "Add", |
||||
|
key: "ADD", |
||||
|
}, |
||||
|
] |
||||
|
|
||||
|
let selectedTab = "NAVIGATE" |
||||
|
|
||||
|
function selectModel(model) { |
||||
|
backendUiStore.actions.models.select(model) |
||||
|
} |
||||
|
|
||||
|
function selectField() {} |
||||
|
</script> |
||||
|
|
||||
|
<div class="items-root"> |
||||
|
{#if $backendUiStore.selectedDatabase._id} |
||||
|
<div class="hierarchy"> |
||||
|
<div class="components-list-container"> |
||||
|
<Switcher headings={HEADINGS} bind:value={selectedTab}> |
||||
|
{#if selectedTab === 'NAVIGATE'} |
||||
|
<Button |
||||
|
secondary |
||||
|
wide |
||||
|
on:click={() => $goto(`./database/${$backendUiStore.selectedDatabase._id}/newmodel`)}> |
||||
|
Create New Model |
||||
|
</Button> |
||||
|
<div class="hierarchy-items-container"> |
||||
|
{#each $backendUiStore.models as model} |
||||
|
<ListItem |
||||
|
selected={model._id === $backendUiStore.selectedModel._id} |
||||
|
title={model.name} |
||||
|
icon="ri-table-fill" |
||||
|
on:click={() => selectModel(model)} /> |
||||
|
{#each Object.keys(model.schema) as field} |
||||
|
<ListItem |
||||
|
indented |
||||
|
icon="ri-layout-column-fill" |
||||
|
title={field} |
||||
|
on:click={() => selectField(model.schema[field])} /> |
||||
|
{/each} |
||||
|
{/each} |
||||
|
</div> |
||||
|
{:else if selectedTab === 'ADD'} |
||||
|
<BlockNavigator /> |
||||
|
{/if} |
||||
|
</Switcher> |
||||
|
</div> |
||||
|
</div> |
||||
|
{/if} |
||||
|
</div> |
||||
|
|
||||
|
<style> |
||||
|
.items-root { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
max-height: 100%; |
||||
|
height: 100%; |
||||
|
background: var(--white); |
||||
|
padding: 20px; |
||||
|
} |
||||
|
|
||||
|
.hierarchy { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
} |
||||
|
|
||||
|
.hierarchy-items-container { |
||||
|
margin-top: 20px; |
||||
|
flex: 1 1 auto; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,111 @@ |
|||||
|
<script> |
||||
|
import { getContext, onMount } from "svelte" |
||||
|
import { Button, Switcher } from "@budibase/bbui" |
||||
|
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") |
||||
|
|
||||
|
const ITEMS = [ |
||||
|
{ |
||||
|
title: "Setup", |
||||
|
key: "SETUP", |
||||
|
}, |
||||
|
{ |
||||
|
title: "Filter", |
||||
|
key: "FILTER", |
||||
|
}, |
||||
|
{ |
||||
|
title: "Delete", |
||||
|
key: "DELETE", |
||||
|
}, |
||||
|
] |
||||
|
|
||||
|
let selectedTab = "SETUP" |
||||
|
let modelName = $backendUiStore.selectedModel.name |
||||
|
let edited |
||||
|
|
||||
|
$: if (modelName) { |
||||
|
edited = modelName !== $backendUiStore.selectedModel.name |
||||
|
} |
||||
|
|
||||
|
async function deleteModel() { |
||||
|
const modelToDelete = $backendUiStore.selectedModel |
||||
|
const DELETE_MODEL_URL = `/api/${instanceId}/models/${modelToDelete._id}/${modelToDelete._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"> |
||||
|
<Switcher headings={ITEMS} bind:value={selectedTab}> |
||||
|
{#if selectedTab === 'SETUP'} |
||||
|
<div class="titled-input"> |
||||
|
<header>Name</header> |
||||
|
<input type="text" class="budibase__input" bind:value={modelName} /> |
||||
|
</div> |
||||
|
<div class="titled-input"> |
||||
|
<header>Import Data</header> |
||||
|
<Button wide secondary>Import CSV</Button> |
||||
|
</div> |
||||
|
|
||||
|
<Button |
||||
|
disabled={!edited} |
||||
|
attention={edited} |
||||
|
wide |
||||
|
on:click={() => alert('Clicked!')}> |
||||
|
Save |
||||
|
</Button> |
||||
|
{:else if selectedTab === 'DELETE'} |
||||
|
<div class="titled-input"> |
||||
|
<header>Danger Zone</header> |
||||
|
<Button error wide on:click={deleteModel}> |
||||
|
Delete |
||||
|
</Button> |
||||
|
</div> |
||||
|
{/if} |
||||
|
</Switcher> |
||||
|
</div> |
||||
|
|
||||
|
<style> |
||||
|
.items-root { |
||||
|
padding: 20px; |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
max-height: 100%; |
||||
|
height: 100%; |
||||
|
background-color: var(--white); |
||||
|
} |
||||
|
|
||||
|
.titled-input { |
||||
|
padding: 20px; |
||||
|
background: var(--light-grey); |
||||
|
margin-top: 20px; |
||||
|
} |
||||
|
|
||||
|
.titled-input header { |
||||
|
display: block; |
||||
|
font-size: 14px; |
||||
|
margin-bottom: 16px; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,101 @@ |
|||||
|
const FIELD_TYPES = ["string", "number", "boolean"] |
||||
|
|
||||
|
export const FIELDS = { |
||||
|
PLAIN_TEXT: { |
||||
|
name: "Plain Text", |
||||
|
icon: "ri-text", |
||||
|
definition: { |
||||
|
type: "string", |
||||
|
constraints: { |
||||
|
type: "string", |
||||
|
length: {}, |
||||
|
presence: false, |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
NUMBER: { |
||||
|
name: "Number", |
||||
|
icon: "ri-number-1", |
||||
|
definition: { |
||||
|
type: "number", |
||||
|
constraints: { |
||||
|
type: "number", |
||||
|
presence: false, |
||||
|
numericality: {}, |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
BOOLEAN: { |
||||
|
name: "True/False", |
||||
|
icon: "ri-toggle-line", |
||||
|
definition: { |
||||
|
type: "boolean", |
||||
|
constraints: { |
||||
|
type: "boolean", |
||||
|
presence: false, |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
OPTIONS: { |
||||
|
name: "Options", |
||||
|
icon: "ri-list-check-2", |
||||
|
definition: { |
||||
|
type: "options", |
||||
|
constraints: { |
||||
|
type: "string", |
||||
|
presence: false, |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
DATETIME: { |
||||
|
name: "Date/Time", |
||||
|
icon: "ri-calendar-event-fill", |
||||
|
definition: { |
||||
|
type: "date", |
||||
|
constraints: { |
||||
|
type: "date", |
||||
|
datetime: {}, |
||||
|
presence: false, |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
IMAGE: { |
||||
|
name: "Image", |
||||
|
icon: "ri-image-line", |
||||
|
definition: { |
||||
|
type: "image", |
||||
|
constraints: { |
||||
|
type: "string", |
||||
|
presence: false, |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
FILE: { |
||||
|
name: "Image", |
||||
|
icon: "ri-file-line", |
||||
|
definition: { |
||||
|
type: "file", |
||||
|
constraints: { |
||||
|
type: "string", |
||||
|
presence: false, |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
DATA_LINK: { |
||||
|
name: "Data Links", |
||||
|
icon: "ri-link", |
||||
|
definition: { |
||||
|
type: "link", |
||||
|
modelId: null, |
||||
|
constraints: { |
||||
|
type: "array", |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
} |
||||
|
|
||||
|
export const BLOCKS = { |
||||
|
|
||||
|
} |
||||
|
|
||||
|
export const TABLES = {} |
||||
@ -0,0 +1,49 @@ |
|||||
|
<script> |
||||
|
export let title |
||||
|
export let icon |
||||
|
|
||||
|
export let primary |
||||
|
export let secondary |
||||
|
export let tertiary |
||||
|
</script> |
||||
|
|
||||
|
<!-- TODO: Move to bbui --> |
||||
|
<div on:click class:primary class:secondary class:tertiary> |
||||
|
<i class={icon} /> |
||||
|
<span>{title}</span> |
||||
|
</div> |
||||
|
|
||||
|
<style> |
||||
|
div { |
||||
|
width: 120px; |
||||
|
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> |
||||
@ -0,0 +1,40 @@ |
|||||
|
<script> |
||||
|
import { FIELDS } from "constants/backend"; |
||||
|
import Block from "./Block.svelte"; |
||||
|
</script> |
||||
|
|
||||
|
<section> |
||||
|
<header> |
||||
|
<h2>Create New Table</h2> |
||||
|
<span>Before you can view your table, you need to set it up.</span> |
||||
|
</header> |
||||
|
|
||||
|
<div class="block-row"> |
||||
|
<h4>Fields</h4> |
||||
|
<div class="blocks"> |
||||
|
{#each Object.values(FIELDS) as field} |
||||
|
<Block primary title={field.name} icon={field.icon} /> |
||||
|
{/each} |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<!-- TODO: More block rows --> |
||||
|
|
||||
|
</section> |
||||
|
|
||||
|
<style> |
||||
|
h2 { |
||||
|
font-size: 20px; |
||||
|
font-weight: bold; |
||||
|
} |
||||
|
|
||||
|
.block-row { |
||||
|
margin-top: 40px; |
||||
|
} |
||||
|
|
||||
|
.block-row .blocks { |
||||
|
display: grid; |
||||
|
grid-auto-flow: column; |
||||
|
grid-gap: 10px; |
||||
|
} |
||||
|
</style> |
||||
Loading…
Reference in new issue