Merge branch 'couchdb-api' of github.com:Budibase/budibase into couchdb-api

This commit is contained in:
Michael Shanks 2020-04-25 08:35:50 +01:00
commit 34c6cfc53b
43 changed files with 8951 additions and 469 deletions

7563
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -2,6 +2,7 @@
"name": "root",
"private": true,
"devDependencies": {
"@rollup/plugin-json": "^4.0.2",
"babel-eslint": "^10.0.3",
"eslint": "^6.8.0",
"eslint-plugin-prettier": "^3.1.2",
@ -17,7 +18,7 @@
"build": "lerna run build",
"initialise": "lerna run initialise",
"publishdev": "lerna run publishdev",
"publishnpm":"yarn build && lerna publish",
"publishnpm": "yarn build && lerna publish",
"clean": "lerna clean",
"dev": "lerna run --parallel --stream dev:builder",
"test": "lerna run test",

View File

@ -9,6 +9,8 @@ import builtins from "rollup-plugin-node-builtins"
import nodeglobals from "rollup-plugin-node-globals"
import copy from "rollup-plugin-copy"
import replace from "rollup-plugin-replace"
import json from '@rollup/plugin-json';
import path from "path"
@ -133,10 +135,16 @@ export default {
plugins: [
alias({
entries: [
{ find: "components", replacement: path.resolve(projectRootDir, 'src/components') },
{ find: "builderStore", replacement: path.resolve(projectRootDir, 'src/builderStore') }
{
find: "components",
replacement: path.resolve(projectRootDir, "src/components"),
},
{
find: "builderStore",
replacement: path.resolve(projectRootDir, "src/builderStore"),
},
],
customResolver
customResolver,
}),
copy({
targets: [
@ -206,5 +214,6 @@ export default {
// If we're building for production (npm run build
// instead of npm run dev), minify
production && terser(),
json(),
],
}

View File

@ -27,8 +27,8 @@ export async function loadRecord(key, { appname, instanceId }) {
return await response.json()
}
export async function saveRecord({ record, instanceId, modelId }) {
const SAVE_RECORDS_URL = `/api/${instanceId}/${modelId}/records`
export async function saveRecord(record, instanceId) {
const SAVE_RECORDS_URL = `/api/${instanceId}/records`
const response = await api.post(SAVE_RECORDS_URL, record)
return await response.json()
}

View File

@ -43,19 +43,22 @@
}
async function saveRecord() {
const recordResponse = await api.saveRecord({
record,
instanceId,
modelId: $backendUiStore.selectedModel._id
})
const recordResponse = await api.saveRecord(
{
...record,
modelId: $backendUiStore.selectedModel._id,
},
instanceId
)
if (recordResponse.errors) {
errors = recordResponse.errors;
return;
errors = recordResponse.errors
return
}
backendUiStore.update(state => {
state.selectedView = state.selectedView
return state
onClosed();
})
}
</script>

View File

@ -14,7 +14,6 @@
async function createUser() {
const user = { name: username, username, password }
const response = await api.createUser(user, instanceId);
console.log(response);
backendUiStore.actions.users.create(response)
onClosed()
}
@ -22,11 +21,14 @@
<form on:submit|preventDefault class="uk-form-stacked">
<div>
<div class="uk-margin">
<label class="uk-form-label" for="form-stacked-text">Username</label>
<input class="uk-input" type="text" bind:value={username} />
</div>
<div class="uk-margin">
<label class="uk-form-label" for="form-stacked-text">Password</label>
<input class="uk-input" type="password" bind:value={password} />
<label class="uk-form-label" for="form-stacked-text">Access Levels</label>
</div>
</div>
<footer>
<ActionButton alert on:click={onClosed}>Cancel</ActionButton>

View File

@ -146,6 +146,7 @@
.root {
height: 100%;
padding: 20px;
display: flex;
flex-direction: column;
}

View File

@ -1,6 +1,7 @@
<script>
import { splitName } from "./pagesParsing/splitRootComponentName.js"
import { store } from "builderStore"
import components from "./temporaryPanelStructure.js"
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
import {
find,
sortBy,
@ -11,10 +12,7 @@
uniqBy,
flatten,
} from "lodash/fp"
import { ImageIcon, InputIcon, LayoutIcon } from "components/common/Icons/"
import Select from "components/common/Select.svelte"
import Button from "components/common/PlusButton.svelte"
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
import {
getRecordNodes,
getIndexNodes,
@ -22,15 +20,54 @@
pipe,
} from "components/common/core"
import Tab from "./ItemTab/Tab.svelte"
import { store } from "builderStore"
export let toggleTab
let componentLibraries = []
let current_view = "text"
let selectedComponent = null
let selectedLib
let selectTemplateDialog
let templateInstances = []
let selectedTemplateInstance
let templateInstances = []
let selectedComponent = null
const categories = components.categories
let selectedCategory = categories[0]
const onTemplateChosen = template => {
selectedComponent = null
const { componentName, libName } = splitName(template.name)
const templateOptions = {
records: getRecordNodes(hierarchy),
indexes: getIndexNodes(hierarchy),
helpers: {
indexSchema: getIndexSchema(hierarchy),
},
}
templateInstances = libraryModules[libName][componentName](templateOptions)
if (!templateInstances || templateInstances.length === 0) return
selectedTemplateInstance = templateInstances[0].name
selectTemplateDialog.show()
}
const onComponentChosen = component => {
if (component.template) {
onTemplateChosen(component.template)
} else {
store.addChildComponent(component._component)
toggleTab()
}
}
const onTemplateInstanceChosen = () => {
selectedComponent = null
const instance = templateInstances.find(
i => i.name === selectedTemplateInstance
)
store.addTemplatedComponent(instance.props)
toggleTab()
}
$: templatesByComponent = groupBy(t => t.component)($store.templates)
$: hierarchy = $store.hierarchy
@ -45,157 +82,25 @@
uniqBy(t => t.name),
]
)
const addRootComponent = (component, allComponents) => {
const { libName } = splitName(component.name)
let group = find(r => r.libName === libName)(allComponents)
if (!group) {
group = {
libName,
components: [],
}
allComponents.push(group)
}
group.components.push(component)
}
const onComponentChosen = component => {
if (component.template) {
onTemplateChosen(component.template)
} else {
store.addChildComponent(component.name)
toggleTab()
}
}
const onTemplateChosen = template => {
selectedComponent = null
const { componentName, libName } = splitName(template.name)
const templateOptions = {
records: getRecordNodes(hierarchy),
indexes: getIndexNodes(hierarchy),
helpers: {
indexSchema: getIndexSchema(hierarchy),
},
}
templateInstances = libraryModules[libName][componentName](templateOptions)
if (!templateInstances || templateInstances.length === 0) return
selectedTemplateInstance = templateInstances[0].name
selectTemplateDialog.show()
}
const onTemplateInstanceChosen = () => {
selectedComponent = null
const instance = templateInstances.find(
i => i.name === selectedTemplateInstance
)
store.addTemplatedComponent(instance.props)
toggleTab()
}
function generate_components_list(components) {
return ($store.currentFrontEndType === "page"
? $store.builtins.concat(components)
: components
).concat(standaloneTemplates)
}
$: {
const newComponentLibraries = []
for (let comp of sortBy(["name"])($store.components)) {
addRootComponent(comp, newComponentLibraries)
}
componentLibraries = newComponentLibraries
if (!selectedLib) selectedLib = newComponentLibraries[0].libName
}
$: componentLibrary = componentLibraries.find(l => l.libName === selectedLib)
</script>
<div class="root">
<Select on:change={e => (selectedLib = e.target.value)}>
{#each componentLibraries as lib}
<option value={lib.libName}>{lib.libName}</option>
{/each}
</Select>
<div class="library-container">
<!-- <ul>
<li>
<button
class:selected={current_view === 'text'}
on:click={() => (current_view = 'text')}>
<InputIcon />
</button>
</li>
<li>
<button
class:selected={current_view === 'layout'}
on:click={() => (current_view = 'layout')}>
<LayoutIcon />
</button>
</li>
<li>
<button
class:selected={current_view === 'media'}
on:click={() => (current_view = 'media')}>
<ImageIcon />
</button>
</li>
</ul> -->
{#if componentLibrary}
{#each generate_components_list(componentLibrary.components) as component}
<div class="component-container">
<div class="component" on:click={() => onComponentChosen(component)}>
<div class="name">{splitName(component.name).componentName}</div>
{#if (component.presets || templatesByComponent[component.name]) && component.name === selectedComponent}
<ul class="preset-menu">
{#if component.presets}
<span>{splitName(component.name).componentName} Presets</span>
{#each Object.keys(component.presets) as preset}
<ul class="tabs">
{#each categories as category}
<li
on:click|stopPropagation={() => onComponentChosen(component, preset)}>
{preset}
on:click={() => (selectedCategory = category)}
class:active={selectedCategory === category}>
{category.name}
</li>
{/each}
{/if}
{#if templatesByComponent[component.name]}
<span>
{splitName(component.name).componentName} Templates
</span>
{#each templatesByComponent[component.name] as template}
<li
on:click|stopPropagation={() => onTemplateChosen(template)}>
{template.description}
</li>
{/each}
{/if}
</ul>
{/if}
<div class="panel">
<Tab
list={selectedCategory}
on:selectItem={e => onComponentChosen(e.detail)}
{onTemplateChosen}
{toggleTab} />
</div>
{#if component.presets || templatesByComponent[component.name]}
<Button
on:click={() => {
selectedComponent = selectedComponent ? null : component.name
}}>
<span
class="open-presets"
class:open={selectedComponent === component.name}>
...
</span>
</Button>
{/if}
</div>
{/each}
{/if}
</div>
</div>
<ConfirmDialog
@ -218,111 +123,32 @@
</ConfirmDialog>
<style>
.root {
.tabs {
display: flex;
flex-direction: column;
}
.library-container {
padding: 0 0 10px 0;
flex: 1 1 auto;
min-height: 0px;
margin-top: 20px;
}
.component-container {
display: flex;
align-items: center;
}
.component {
position: relative;
padding: 0 15px;
cursor: pointer;
border: 1px solid #d8d8d8;
border-radius: 2px;
margin: 5px 0;
height: 40px;
box-sizing: border-box;
color: #000333;
display: flex;
align-items: center;
flex: 1;
margin-right: 5px;
}
.component:hover {
background-color: var(--lightslate);
}
.component > .name {
color: #000333;
display: inline-block;
font-size: 13px;
opacity: 0.8;
}
ul {
justify-content: center;
list-style: none;
display: flex;
padding: 0;
}
margin: 0 auto;
padding: 0 30px;
border-bottom: 1px solid #d8d8d8;
.preset-menu {
flex-direction: column;
position: absolute;
top: 25px;
left: 0;
right: 0;
z-index: 1;
background: #fafafa;
padding: 10px;
border-radius: 2px;
color: var(--secondary80);
}
.preset-menu > span {
font-size: 13px;
text-transform: uppercase;
margin-top: 5px;
}
.preset-menu li {
font-size: 14px;
margin-top: 13px;
}
.preset-menu li:hover {
font-weight: bold;
font-weight: 500;
letter-spacing: 0.14px;
}
li {
margin-right: 20px;
background: none;
border-radius: 5px;
}
/* li button {
width: 100%;
height: 100%;
background: none;
border: none;
border-radius: 5px;
padding: 13px;
outline: none;
color: #808192;
margin: 0 5px;
padding: 0 8px;
cursor: pointer;
} */
/* .selected {
color: var(--button-text);
background: var(--background-button) !important;
} */
.open {
color: rgba(0, 85, 255, 1);
}
.template-instance-label {
margin-left: 20px;
.panel {
padding: 20px;
}
.active {
border-bottom: solid 3px #0055ff;
color: #393c44;
}
</style>

View File

@ -60,9 +60,7 @@
.switcher {
display: flex;
justify-content: space-between;
margin-bottom: 20px;
padding: 0 20px 20px;
border-bottom: 1px solid #d8d8d8;
margin: 20px;
}
.switcher > button {
@ -85,11 +83,4 @@
color: var(--secondary100);
font-weight: 600;
}
.panel {
flex: 1 1 auto;
height: 0px;
overflow-y: auto;
padding: 0 20px 40px 20px;
}
</style>

View File

@ -133,15 +133,13 @@
grid-column-start: middle;
position: relative;
overflow: hidden;
padding-top: 56.25%;
margin: auto;
height: 100%;
}
.component-container iframe {
border: 0;
height: 100%;
left: 0;
position: absolute;
top: 0;
width: 100%;
}

View File

@ -0,0 +1,66 @@
<script>
export let item
</script>
<div class="item-item" on:click>
<div class="item-icon">
<i class={item.icon} />
</div>
<div class="item-text">
<div class="item-name">{item.name}</div>
<div class="item-description">
<p>{item.description}</p>
</div>
</div>
</div>
<style>
.item-item {
display: flex;
flex-direction: row;
padding: 10px 0px 8px 10px;
align-items: center;
cursor: pointer;
}
.item-item:hover {
background: #fbfbfb;
border-radius: 5px;
}
.item-icon {
flex: 0 0 40px;
background: #f1f4fc;
height: 40px;
border-radius: 5px;
display: flex;
justify-content: center;
align-items: center;
}
.item-text {
display: flex;
padding-left: 16px;
padding-top: 8px;
flex-direction: column;
}
.item-name {
font-size: 14px;
font-weight: 500;
}
.item-description {
font-size: 12px;
color: #808192;
}
p {
line-height: 15px;
}
i {
font-size: 24px;
color: #808192;
}
</style>

View File

@ -0,0 +1,44 @@
<script>
import { createEventDispatcher } from "svelte"
const dispatch = createEventDispatcher()
import Item from "./Item.svelte"
import { store } from "builderStore"
export let list
let category = list
const handleClick = item => {
if (item.children && item.children.length > 0) {
list = item
} else {
dispatch("selectItem", item)
}
}
const goBack = () => {
list = category
}
</script>
{#if !list.isCategory}
<button class="back-button" on:click={() => (list = category)}>Back</button>
{/if}
{#each list.children as item}
<Item {item} on:click={() => handleClick(item)} />
{/each}
<style>
.back-button {
font-size: 16px;
width: 100%;
text-align: center;
height: 40px;
border-radius: 3px;
border: solid 1px #e8e8ef;
background: white;
margin-bottom: 20px;
cursor: pointer;
}
</style>

View File

@ -0,0 +1,335 @@
<script>
import { splitName } from "./pagesParsing/splitRootComponentName.js"
import { store } from "builderStore"
import {
find,
sortBy,
groupBy,
values,
filter,
map,
uniqBy,
flatten,
} from "lodash/fp"
import { ImageIcon, InputIcon, LayoutIcon } from "components/common/Icons/"
import Select from "components/common/Select.svelte"
import Button from "components/common/PlusButton.svelte"
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
import {
getRecordNodes,
getIndexNodes,
getIndexSchema,
pipe,
} from "components/common/core"
export let toggleTab
let componentLibraries = []
let current_view = "text"
let selectedComponent = null
let selectedLib
let selectTemplateDialog
let templateInstances = []
let selectedTemplateInstance
//Info: Components seem to be generated from individual templates. Will this be the same going forward
$: templatesByComponent = groupBy(t => t.component)($store.templates)
$: hierarchy = $store.hierarchy
$: libraryModules = $store.libraries
$: standaloneTemplates = pipe(
templatesByComponent,
[
values,
flatten,
filter(t => !$store.components.some(c => c.name === t.component)),
map(t => ({ name: splitName(t.component).componentName, template: t })),
uniqBy(t => t.name),
]
)
const addRootComponent = (component, allComponents) => {
const { libName } = splitName(component.name)
let group = find(r => r.libName === libName)(allComponents)
if (!group) {
group = {
libName,
components: [],
}
allComponents.push(group)
}
group.components.push(component)
}
const onComponentChosen = component => {
if (component.template) {
onTemplateChosen(component.template)
} else {
store.addChildComponent(component.name)
toggleTab()
}
}
//Info: Called from menu beside components with presets and templates
const onTemplateChosen = template => {
selectedComponent = null
const { componentName, libName } = splitName(template.name)
//Info: how will DB changes effect this?
const templateOptions = {
records: getRecordNodes(hierarchy),
indexes: getIndexNodes(hierarchy),
helpers: {
indexSchema: getIndexSchema(hierarchy),
},
}
//Info: go off and get template instances by library and component name
//libraryModules and hierarchies (used above) come from builderStore
templateInstances = libraryModules[libName][componentName](templateOptions)
if (!templateInstances || templateInstances.length === 0) return
selectedTemplateInstance = templateInstances[0].name
selectTemplateDialog.show()
}
const onTemplateInstanceChosen = () => {
selectedComponent = null
const instance = templateInstances.find(
i => i.name === selectedTemplateInstance
)
debugger
store.addTemplatedComponent(instance.props)
toggleTab()
}
function generate_components_list(components) {
debugger
return ($store.currentFrontEndType === "page"
? $store.builtins.concat(components)
: components
).concat(standaloneTemplates)
}
$: {
const newComponentLibraries = []
for (let comp of sortBy(["name"])($store.components)) {
addRootComponent(comp, newComponentLibraries)
}
componentLibraries = newComponentLibraries
if (!selectedLib) selectedLib = newComponentLibraries[0].libName
}
$: componentLibrary = componentLibraries.find(l => l.libName === selectedLib)
</script>
<div class="root">
<Select on:change={e => (selectedLib = e.target.value)}>
{#each componentLibraries as lib}
<option value={lib.libName}>{lib.libName}</option>
{/each}
</Select>
<div class="library-container">
<!-- <ul>
<li>
<button
class:selected={current_view === 'text'}
on:click={() => (current_view = 'text')}>
<InputIcon />
</button>
</li>
<li>
<button
class:selected={current_view === 'layout'}
on:click={() => (current_view = 'layout')}>
<LayoutIcon />
</button>
</li>
<li>
<button
class:selected={current_view === 'media'}
on:click={() => (current_view = 'media')}>
<ImageIcon />
</button>
</li>
</ul> -->
{#if componentLibrary}
{#each generate_components_list(componentLibrary.components) as component}
<div class="component-container">
<div class="component" on:click={() => onComponentChosen(component)}>
<div class="name">{splitName(component.name).componentName}</div>
{#if (component.presets || templatesByComponent[component.name]) && component.name === selectedComponent}
<ul class="preset-menu">
{#if component.presets}
<span>{splitName(component.name).componentName} Presets</span>
{#each Object.keys(component.presets) as preset}
<li
on:click|stopPropagation={() => onComponentChosen(component, preset)}>
{preset}
</li>
{/each}
{/if}
{#if templatesByComponent[component.name]}
<span>
{splitName(component.name).componentName} Templates
</span>
{#each templatesByComponent[component.name] as template}
<li
on:click|stopPropagation={() => onTemplateChosen(template)}>
{template.description}
</li>
{/each}
{/if}
</ul>
{/if}
</div>
{#if component.presets || templatesByComponent[component.name]}
<Button
on:click={() => {
selectedComponent = selectedComponent ? null : component.name
}}>
<span
class="open-presets"
class:open={selectedComponent === component.name}>
...
</span>
</Button>
{/if}
</div>
{/each}
{/if}
</div>
</div>
<ConfirmDialog
bind:this={selectTemplateDialog}
title="Choose Template"
onCancel={() => (selectedComponent = null)}
onOk={onTemplateInstanceChosen}>
{#each templateInstances.map(i => i.name) as instance}
<div class="uk-margin uk-grid-small uk-child-width-auto uk-grid">
<label>
<input
class="uk-radio"
type="radio"
bind:group={selectedTemplateInstance}
value={instance} />
<span class="template-instance-label">{instance}</span>
</label>
</div>
{/each}
</ConfirmDialog>
<style>
.root {
display: flex;
flex-direction: column;
}
.library-container {
padding: 0 0 10px 0;
flex: 1 1 auto;
min-height: 0px;
margin-top: 20px;
}
.component-container {
display: flex;
align-items: center;
}
.component {
position: relative;
padding: 0 15px;
cursor: pointer;
border: 1px solid #d8d8d8;
border-radius: 2px;
margin: 5px 0;
height: 40px;
box-sizing: border-box;
color: #000333;
display: flex;
align-items: center;
flex: 1;
margin-right: 5px;
}
.component:hover {
background-color: var(--lightslate);
}
.component > .name {
color: #000333;
display: inline-block;
font-size: 13px;
opacity: 0.8;
}
ul {
list-style: none;
display: flex;
padding: 0;
}
.preset-menu {
flex-direction: column;
position: absolute;
top: 25px;
left: 0;
right: 0;
z-index: 1;
background: #fafafa;
padding: 10px;
border-radius: 2px;
color: var(--secondary80);
}
.preset-menu > span {
font-size: 13px;
text-transform: uppercase;
margin-top: 5px;
}
.preset-menu li {
font-size: 14px;
margin-top: 13px;
}
.preset-menu li:hover {
font-weight: bold;
}
li {
margin-right: 20px;
background: none;
border-radius: 5px;
}
/* li button {
width: 100%;
height: 100%;
background: none;
border: none;
border-radius: 5px;
padding: 13px;
outline: none;
cursor: pointer;
} */
/* .selected {
color: var(--button-text);
background: var(--background-button) !important;
} */
.open {
color: rgba(0, 85, 255, 1);
}
.template-instance-label {
margin-left: 20px;
}
</style>

View File

@ -135,7 +135,7 @@
background-color: var(--white);
height: calc(100vh - 49px);
padding: 0;
overflow: hidden;
overflow: scroll;
display: flex;
flex-direction: column;
}
@ -152,7 +152,7 @@
grid-column: 3;
background-color: var(--white);
min-height: 0px;
overflow-y: hidden;
overflow-y: scroll;
}
.components-nav-page {
@ -235,7 +235,6 @@
}
.components-list-container {
overflow: auto;
padding: 20px 0px 0 0;
}
</style>

View File

@ -0,0 +1,162 @@
export default {
categories: [
{
name: 'Basic',
isCategory: true,
children: [
{
_component: "@budibase/standard-components/container",
name: 'Container',
description: 'This component contains things within itself',
icon: 'ri-layout-row-fill',
commonProps: {},
children: []
},
{
name: 'Text',
description: 'This is a simple text component',
icon: 'ri-t-box-fill',
commonProps: {},
children: [
{
_component: '@budibase/standard-components/heading',
name: 'Headline',
description: "A component for displaying heading text",
icon: "ri-heading",
props: {
type: {
type: "options",
options: ["h1", "h2", "h3", "h4", "h5", "h6"],
default: "h1",
},
text: "string",
},
},
{
_component: '@budibase/standard-components/text',
name: 'Paragraph',
description: "A component for displaying paragraph text.",
icon: 'ri-paragraph',
props: {}
}
]
},
{
name: 'Input',
description: "These components handle user input.",
icon: 'ri-edit-box-line',
commonProps: {},
children: [
{
_component: "@budibase/standard-components/textfield",
name: "Textfield",
description: "A textfield component that allows the user to input text.",
icon: 'ri-edit-box-line',
props: {}
},
{
_component: "@budibase/standard-components/checkbox",
name: "Checkbox",
description: "A selectable checkbox component",
icon: 'ri-checkbox-line',
props: {}
},
{
_component: "@budibase/standard-components/radiobutton",
name: "Radiobutton",
description: "A selectable radiobutton component",
icon: 'ri-radio-button-line',
props: {}
},
{
_component: "@budibase/standard-components/select",
name: "Select",
description: "A select component for choosing from different options",
icon: 'ri-file-list-line',
props: {}
}
]
},
{
_component: "@budibase/standard-components/button",
name: 'Button',
description: 'A basic html button that is ready for styling',
icon: 'ri-radio-button-fill',
commonProps: {},
children: []
},
{
_component: "@budibase/standard-components/icon",
name: 'Icon',
description: 'A basic component for displaying icons',
icon: 'ri-sun-fill',
commonProps: {},
children: []
},
{
_component: "@budibase/standard-components/link",
name: 'Link',
description: 'A basic link component for internal and external links',
icon: 'ri-link',
commonProps: {},
children: []
}
]
},
{
name: 'Blocks',
isCategory: true,
children: [
{
_component: "@budibase/materialdesign-components/BasicCard",
name: 'Card',
description: 'A basic card component that can contain content and actions.',
icon: 'ri-layout-bottom-line',
commonProps: {},
children: []
},
{
name: 'Login',
description: 'A component that automatically generates a login screen for your app.',
icon: 'ri-login-box-fill',
commonProps: {},
children: []
},
{
name: "Navigation Bar",
_component: "@budibase/standard-components/Navigation",
description: "A component for handling the navigation within your app.",
icon: "ri-navigation-fill",
commonProps: {},
children: []
}
]
},
{
name: 'Data',
isCategory: true,
children: [
{
name: 'Table',
description: 'A component that generates a table from your data.',
icon: 'ri-archive-drawer-fill',
commonProps: {},
children: []
},
{
name: 'Form',
description: 'A component that generates a form from your data.',
icon: 'ri-file-edit-fill',
commonProps: {},
component: "@budibase/materialdesign-components/Form",
template: {
component: "@budibase/materialdesign-components/Form",
description: "Form for saving a record",
name: "@budibase/materialdesign-components/recordForm",
},
children: []
}
]
},
]
}

View File

@ -0,0 +1,75 @@
{
"categories": [
{
"name": "Basic",
"components": [
{
"component": "Container",
"description": "This component contains things within itself",
"icon": "ri-layout-row-fill",
"commonProps": {},
"type": []
},
{
"component": "Text",
"description": "This is a simple text component",
"icon": "ri-t-box-fill",
"commonProps": {
},
"type": [
{
"_component": "@budibase/standard-components/header",
"name": "Headline",
"icon": "headline",
"props": {
"type": {
"type": "options",
"options": [
"h1",
"h2"
],
"default": "h1"
}
}
},
{
"_component": "@budibase/standard-components/text",
"name": "Paragraph",
"icon": "paragraph",
"props": {
}
}
]
},
{
"component": "Button",
"description": "A basic html button that is ready for styling",
"icon": "ri-radio-button-fill",
"commonProps": {},
"type": []
},
{
"component": "Icon",
"description": "A basic component for displaying icons",
"icon": "ri-sun-fill",
"commonProps": {},
"type": []
},
{
"component": "Avatar",
"description": "A basic component for rendering an avatar",
"icon": "ri-user-smile-fill",
"commonProps": {},
"type": []
},
{
"component": "Link",
"description": "A basic link component for internal and external links",
"icon": "ri-link",
"commonProps": {},
"type": []
}
]
}
]
}

View File

@ -5,7 +5,8 @@ var getNewRecord = function getNewRecord(schema, modelName) {
var record = {
_id: (0, _shortid.generate)(),
modelId: model._id };
modelId: model._id
};
for (var field in model.schema.properties) {

View File

@ -115,6 +115,21 @@
},
"tags": []
},
"BasicCard": {
"name": "BasicCard",
"description": "This is a basic card",
"props": {
"heading": "string",
"subheading": "string",
"content": "string",
"imageUrl": "string",
"button1Text": "string",
"button2Text": "string",
"cardClick": "event",
"button1Click": "event",
"button2Click": "event"
}
},
"Card": {
"name": "Card",
"description": "A Material Card container. Accepts CardHeader, CardBody and CardFooter as possible children",

View File

@ -0,0 +1,67 @@
<script>
export let heading = ""
export let subheading = ""
export let content = ""
export let imageUrl = ""
export let button1Text = ""
export let button2Text = ""
export let cardClick = () => {}
export let button1Click = () => {}
export let button2Click = () => {}
$: showImage = !!imageUrl
$: showButton1 = !!button1Text
$: showButton2 = !!button2Text
$: showButtons = !!showButton1 && !!showButton2
</script>
<div class="mdc-card" on:click={cardClick}>
<div class="mdc-card__primary-action demo-card__primary-action" tabindex="0">
{#if showImage}
<div
class="mdc-card__media mdc-card__media--16-9 demo-card__media"
style="background-image: url(&quot;{imageUrl}&quot;);" />
{/if}
<div class="pad">
<div class="demo-card__primary">
<h2 class="demo-card__title mdc-typography mdc-typography--headline6">
{heading}
</h2>
<h3
class="demo-card__subtitle mdc-typography mdc-typography--subtitle2">
{subheading}
</h3>
</div>
<div class="demo-card__secondary mdc-typography mdc-typography--body2">
{content}
</div>
</div>
</div>
{#if showButtons}
<div class="mdc-card__actions">
<div class="mdc-card__action-buttons">
{#if showButton1}
<button class="mdc-button mdc-card__action mdc-card__action--button">
<span class="mdc-button__ripple" on:click={button1Click} />
{button1Text}
</button>
{/if}
{#if showButton2}
<button
class="mdc-button mdc-card__action mdc-card__action--button"
on:click={button2Click}>
<span class="mdc-button__ripple" />
{button2Text}
</button>
{/if}
</div>
</div>
{/if}
</div>
<style>
.pad {
padding: 10px;
}
</style>

View File

@ -1,5 +1,6 @@
import "./_styles.scss"
export { default as Card } from "./Card.svelte"
export { default as BasicCard } from "./BasicCard.svelte"
export { default as CardBody } from "./CardBody.svelte"
export { default as CardFooter } from "./CardFooter.svelte"
export { default as CardHeader } from "./CardHeader.svelte"

View File

@ -22,7 +22,7 @@ export { Menu } from "./Menu"
export { Select } from "./Select"
export { DatePicker } from "./DatePicker"
export { IconButton } from "./IconButton"
export { Card, CardHeader, CardImage, CardBody, CardFooter } from "./Card"
export { Card, CardHeader, CardImage, CardBody, CardFooter, BasicCard } from "./Card"
export { Dialog, DialogHeader, DialogContent, DialogActions } from "./Dialog"
export { Switch } from "./Switch"
export { Slider } from "./Slider"

View File

@ -10,7 +10,7 @@ exports.create = async function(ctx) {
by_type: {
map: function(doc) {
emit([doc.type], doc._id);
}
}.toString()
}
}
});

View File

@ -5,7 +5,7 @@ exports.create = async function(ctx) {
// await couchdb.db.create(instanceName);
const { clientId, applicationId } = ctx.params;
const db = new CouchDB(ctx.config)(instanceName);
const db = new CouchDB(instanceName);
await db.put({
_id: "_design/database",
metadata: {
@ -46,7 +46,7 @@ exports.destroy = async function(ctx) {
const clientDb = new CouchDB(metadata.clientId);
const budibaseApp = await clientDb.get(metadata.applicationId);
budibaseApp.instances = budibaseApp.instances.filter(instance => instance !== ctx.params.instanceId);
await clientDb.put(budibaseApp);
const updatedApp = await clientDb.put(budibaseApp);
ctx.body = {
message: `Instance Database ${ctx.params.instanceId} successfully destroyed.`,

View File

@ -24,9 +24,12 @@ exports.save = async function(ctx) {
if (existingRecord) {
const response = await db.put({ ...record, _id: existingRecord._id });
ctx.body = {
message: "Record updated successfully.",
message: `${model.name} updated successfully.`,
status: 200,
record: response
record: {
...record,
...response
}
}
return
}
@ -40,7 +43,7 @@ exports.save = async function(ctx) {
ctx.body = record
ctx.status = 200
ctx.message = `${model.name} ${record._rev ? "updated" : "created"} successfully`
ctx.message = `${model.name} created successfully`
}
exports.fetch = async function(ctx) {
@ -62,5 +65,5 @@ exports.find = async function(ctx) {
exports.destroy = async function(ctx) {
const databaseId = ctx.params.instanceId;
const db = new CouchDB(databaseId)
ctx.body = await db.destroy(ctx.params.recordId, ctx.params.revId);
ctx.body = await db.remove(ctx.params.recordId, ctx.params.revId);
};

View File

@ -20,8 +20,8 @@ describe("/applications", () => {
});
afterAll(async () => {
await destroyDatabase(CLIENT_DB_ID)
server.close();
await destroyDatabase(CLIENT_DB_ID)
})
describe("create", () => {

View File

@ -1,6 +1,6 @@
const supertest = require("supertest");
const app = require("../../../../app");
const { createClientDatabase, destroyDatabase } = require("./couchTestUtils")
const { createClientDatabase, destroyClientDatabase } = require("./couchTestUtils")
const CLIENT_DB_ID = "client-testing";
@ -8,6 +8,7 @@ const CLIENT_DB_ID = "client-testing";
describe("/clients", () => {
let request;
let server;
let db;
beforeAll(async () => {
server = await app({
@ -24,7 +25,7 @@ describe("/clients", () => {
describe("create", () => {
afterEach(async () => {
await destroyDatabase(CLIENT_DB_ID);
await destroyClientDatabase();
});
it("returns a success message when the client database is successfully created", done => {
@ -43,7 +44,7 @@ describe("/clients", () => {
describe("destroy", () => {
beforeEach(async () => {
await createClientDatabase();
db = await createClientDatabase();
});
it("returns a success message when the client database is successfully destroyed", async done => {

View File

@ -1,11 +1,7 @@
const couchdb = require("../../../../db")
const createClientDb = require("../../../../db/initialiseClientDb")
const CLIENT_DB_ID = "client-testing"
const CouchDB = require("../../../../db");
const CLIENT_DB_ID = "client-testing";
const TEST_APP_ID = "test-app";
exports.destroyDatabase = couchdb.db.destroy;
exports.createModel = async (instanceId, model) => {
model = model || {
"name": "TestModel",
@ -15,8 +11,8 @@ exports.createModel = async (instanceId, model) => {
"name": { "type": "string" }
}
}
const db = couchdb.db.use(instanceId);
const response = await db.insert(model);
const db = new CouchDB(instanceId);
const response = await db.post(model);
const designDoc = await db.get("_design/database");
designDoc.views = {
@ -29,7 +25,7 @@ exports.createModel = async (instanceId, model) => {
}`
}
};
await db.insert(designDoc, designDoc._id);
await db.put(designDoc);
return {
...response,
@ -37,20 +33,36 @@ exports.createModel = async (instanceId, model) => {
};
}
exports.createClientDatabase = async () =>
await createClientDb({
database: "couch",
clientId: CLIENT_DB_ID,
})
exports.createClientDatabase = async () => {
const db = new CouchDB(CLIENT_DB_ID);
exports.destroyClientDatabase = async () => await couchdb.db.destroy(CLIENT_DB_ID);
await db.put({
_id: "_design/client",
views: {
by_type: {
map: function(doc) {
emit([doc.type], doc._id);
}
}.toString()
}
});
await db.put({
_id: TEST_APP_ID,
type: "app",
instances: []
});
return db;
}
exports.destroyClientDatabase = async () => new CouchDB(CLIENT_DB_ID).destroy();
exports.createInstanceDatabase = async instanceId => {
await couchdb.db.create(instanceId);
const db = new CouchDB(instanceId);
const db = couchdb.db.use(instanceId);
await db.insert({
await db.put({
_id: "_design/database",
metadata: {
clientId: CLIENT_DB_ID,
applicationId: TEST_APP_ID
@ -59,17 +71,17 @@ exports.createInstanceDatabase = async instanceId => {
by_type: {
map: function(doc) {
emit([doc.type], doc._id);
}.toString()
}
}
}
}, '_design/database');
});
return instanceId;
return db;
}
exports.insertDocument = async (databaseId, document) => {
const { id, ...documentFields } = document;
await couchdb.db.use(databaseId).insert(documentFields, id);
await new CouchDB(databaseId).put({ _id: id, ...documentFields });
}
exports.createSchema = async (request, instanceId, schema) => {

View File

@ -2,7 +2,6 @@ const supertest = require("supertest");
const app = require("../../../../app");
const {
createInstanceDatabase,
destroyDatabase,
createClientDatabase,
destroyClientDatabase
} = require("./couchTestUtils");
@ -29,13 +28,13 @@ describe("/instances", () => {
})
describe("create", () => {
beforeEach(async () => {
await createClientDatabase();
clientDb = await createClientDatabase();
});
afterEach(async () => {
await destroyClientDatabase();
await destroyDatabase(TEST_INSTANCE_ID);
});
it("returns a success message when the instance database is successfully created", done => {
@ -55,7 +54,7 @@ describe("/instances", () => {
describe("destroy", () => {
beforeEach(async () => {
await createClientDatabase();
await createInstanceDatabase(TEST_INSTANCE_ID);
instanceDb = await createInstanceDatabase(TEST_INSTANCE_ID);
});
afterEach(async () => {

View File

@ -1,6 +1,6 @@
const supertest = require("supertest");
const app = require("../../../../app");
const { createInstanceDatabase, createModel, destroyDatabase } = require("./couchTestUtils");
const { createInstanceDatabase, createModel } = require("./couchTestUtils");
const TEST_INSTANCE_ID = "testing-123";
@ -23,12 +23,14 @@ describe("/models", () => {
})
describe("create", () => {
let db;
beforeEach(async () => {
await createInstanceDatabase(TEST_INSTANCE_ID);
db = await createInstanceDatabase(TEST_INSTANCE_ID);
});
afterEach(async () => {
await destroyDatabase(TEST_INSTANCE_ID);
await db.destroy();
});
it("returns a success message when the model is successfully created", done => {
@ -54,14 +56,15 @@ describe("/models", () => {
describe("fetch", () => {
let testModel;
let db;
beforeEach(async () => {
await createInstanceDatabase(TEST_INSTANCE_ID);
db = await createInstanceDatabase(TEST_INSTANCE_ID);
testModel = await createModel(TEST_INSTANCE_ID);
});
afterEach(async () => {
await destroyDatabase(TEST_INSTANCE_ID);
await db.destroy();
});
it("returns all the models for that instance in the response body", done => {
@ -81,14 +84,15 @@ describe("/models", () => {
describe("destroy", () => {
let testModel;
let db;
beforeEach(async () => {
await createInstanceDatabase(TEST_INSTANCE_ID);
db = await createInstanceDatabase(TEST_INSTANCE_ID);
testModel = await createModel(TEST_INSTANCE_ID);
});
afterEach(async () => {
await destroyDatabase(TEST_INSTANCE_ID);
await db.destroy();
});
it("returns a success response when a model is deleted.", done => {

View File

@ -1,14 +1,23 @@
const supertest = require("supertest");
const app = require("../../../../app");
const { createInstanceDatabase, createModel, destroyDatabase } = require("./couchTestUtils");
const { getNewRecord } = require("../../../../../common/lib/records/getNewRecord")
const { testSchema } = require("../../../../../common/lib/testUtils/testSchema")
const { createInstanceDatabase, createModel } = require("./couchTestUtils");
const TEST_INSTANCE_ID = "testing-123";
const CONTACT_MODEL = {
"name": "Contact",
"type": "model",
"key": "name",
"schema": {
"name": { "type": "string" },
"age": { "type": "number" }
}
};
describe("/records", () => {
let request;
let server;
let db;
beforeAll(async () => {
server = await app({
@ -24,21 +33,23 @@ describe("/records", () => {
})
describe("save, load, update, delete", () => {
const schema = testSchema()
let record;
let model;
beforeAll(async () => {
await createInstanceDatabase(TEST_INSTANCE_ID);
await createModel(TEST_INSTANCE_ID, schema.findModel("Contact"))
beforeEach(async () => {
db = await createInstanceDatabase(TEST_INSTANCE_ID);
model = await createModel(TEST_INSTANCE_ID, CONTACT_MODEL)
record = {
name: "Test Contact",
status: "new",
modelId: model.id
}
});
afterAll(async () => {
await destroyDatabase(TEST_INSTANCE_ID);
afterEach(async () => {
await db.destroy();
});
let record = getNewRecord(schema, "Contact")
record.name = "Test Contact"
record.Status = "new"
it("returns a success message when the record is created", done => {
request
.post(`/api/${TEST_INSTANCE_ID}/records`)
@ -47,69 +58,65 @@ describe("/records", () => {
.expect('Content-Type', /json/)
.expect(200)
.end(async (err, res) => {
expect(res.res.statusMessage.toLowerCase()).toEqual("contact created successfully")
expect(res.res.statusMessage).toEqual("Contact created successfully")
expect(res.body.name).toEqual("Test Contact")
expect(res.body._rev).toBeDefined()
record = res.body
done();
});
})
it("updates a record successfully", async () => {
record.name = "Updated Name"
const existing = await db.post(record);
const res = await request
.post(`/api/${TEST_INSTANCE_ID}/records`)
.send(record)
.send({
_id: existing.id,
_rev: existing.rev,
modelId: model.id,
name: "Updated Name",
})
.set("Accept", "application/json")
.expect('Content-Type', /json/)
.expect(200)
expect(res.res.statusMessage.toLowerCase()).toEqual("contact updated successfully")
expect(res.body.name).toEqual("Updated Name")
record = res.body
expect(res.body.message).toEqual("Contact updated successfully.")
expect(res.body.record.name).toEqual("Updated Name")
})
it("should load a record", async () => {
const existing = await db.post(record);
const res = await request
.get(`/api/${TEST_INSTANCE_ID}/records/${record._id}`)
.get(`/api/${TEST_INSTANCE_ID}/records/${existing.id}`)
.set("Accept", "application/json")
.expect('Content-Type', /json/)
.expect(200)
expect(res.body).toEqual(record)
expect(res.body).toEqual({
...record,
_id: existing.id,
_rev: existing.rev
})
})
it("should list all records for given modelId", async () => {
const secondRecord = getNewRecord(schema, "Contact")
secondRecord.name = "Second Contact"
secondRecord.Status = "new"
const newRecord = {
modelId: model.id,
name: "Second Contact",
status: "new"
}
await request
.post(`/api/${TEST_INSTANCE_ID}/records`)
.send(secondRecord)
.set("Accept", "application/json")
await db.post(newRecord);
const res = await request
.get(`/api/${TEST_INSTANCE_ID}/${record.modelId}/records`)
.get(`/api/${TEST_INSTANCE_ID}/all_${newRecord.modelId}/records`)
.set("Accept", "application/json")
.expect('Content-Type', /json/)
.expect(200)
expect(res.body.length).toBe(2)
console.log(JSON.stringify(res.body, undefined, 2))
expect(res.body.find(c => c.name === record.name)).toBeDefined()
expect(res.body.find(c => c.name === secondRecord.name)).toBeDefined()
})
it("should return 404 when load, after a delete", async () => {
await request
.delete(`/api/${TEST_INSTANCE_ID}/records/${record._id}/${record._rev}`)
.expect(200)
await request
.get(`/api/${TEST_INSTANCE_ID}/records/${record._id}`)
.set("Accept", "application/json")
.expect('Content-Type', /json/)
.expect(404)
expect(res.body.length).toBe(1)
expect(res.body[0].name).toEqual(newRecord.name);
})
it("load should return 404 when record does not exist", async () => {
@ -119,6 +126,5 @@ describe("/records", () => {
.expect('Content-Type', /json/)
.expect(404)
})
})
})

View File

@ -1,8 +1,7 @@
const supertest = require("supertest");
const app = require("../../../../app");
const {
createInstanceDatabase,
destroyDatabase
createInstanceDatabase
} = require("./couchTestUtils");
@ -29,12 +28,14 @@ describe("/users", () => {
})
describe("fetch", () => {
let db;
beforeEach(async () => {
await createInstanceDatabase(TEST_INSTANCE_ID);
db = await createInstanceDatabase(TEST_INSTANCE_ID);
});
afterEach(async () => {
await destroyDatabase(TEST_INSTANCE_ID);
await db.destroy();
});
it("returns a list of users from an instance db", done => {
@ -52,18 +53,20 @@ describe("/users", () => {
});
describe("create", () => {
let db;
beforeEach(async () => {
await createInstanceDatabase(TEST_INSTANCE_ID);
db = await createInstanceDatabase(TEST_INSTANCE_ID);
});
afterEach(async () => {
await destroyDatabase(TEST_INSTANCE_ID);
await db.destroy();
});
it("returns a success message when a user is successfully created", done => {
request
.post(`/api/${TEST_INSTANCE_ID}/users`)
.send({ name: "John" })
.send({ name: "Bill", username: "bill1", password: "password" })
.set("Accept", "application/json")
.expect('Content-Type', /json/)
.expect(200)

View File

@ -8,6 +8,7 @@ const TEST_INSTANCE_ID = "testing-123";
describe("/views", () => {
let request;
let server;
let db;
beforeAll(async () => {
server = await app({
@ -24,11 +25,11 @@ describe("/views", () => {
describe("create", () => {
beforeEach(async () => {
await createInstanceDatabase(TEST_INSTANCE_ID);
db = await createInstanceDatabase(TEST_INSTANCE_ID);
});
afterEach(async () => {
await destroyDatabase(TEST_INSTANCE_ID);
await db.destroy();
});
it("returns a success message when the view is successfully created", done => {
@ -55,13 +56,15 @@ describe("/views", () => {
});
describe("fetch", () => {
let db;
beforeEach(async () => {
await createInstanceDatabase(TEST_INSTANCE_ID);
db = await createInstanceDatabase(TEST_INSTANCE_ID);
await createModel(TEST_INSTANCE_ID);
});
afterEach(async () => {
await destroyDatabase(TEST_INSTANCE_ID);
await db.destroy();
});
it("returns a list of all the views that exist in the instance database", done => {

View File

@ -1,16 +1,32 @@
{
"_lib": "./dist/index.js",
"_templates" : {
"saveRecordButton" : {
"_templates": {
"saveRecordButton": {
"description": "Save record button",
"component": "button"
}
},
"button" : {
"Navigation": {
"name": "Navigation",
"description": "A basic header navigation component",
"props": {
"logoUrl": "string",
"title": "string",
"backgroundColor": "string",
"color": "string",
"borderWidth": "string",
"borderColor": "string",
"borderStyle": "string"
}
},
"button": {
"name": "Button",
"description": "an html <button />",
"props": {
"contentText": { "type": "string", "default": "Button" },
"contentText": {
"type": "string",
"default": "Button"
},
"className": "string",
"disabled": "bool",
"onClick": "event",
@ -22,7 +38,9 @@
"hoverBackground": "string",
"hoverBorder": "string"
},
"tags": ["layout"],
"tags": [
"layout"
],
"presets": {
"primary": {
"contentText": "Primary Button Preset",
@ -45,53 +63,85 @@
}
}
},
"login" : {
"login": {
"name": "Login Control",
"description": "A control that accepts username, password an also handles password resets",
"props" : {
"props": {
"logo": "asset",
"loginRedirect": "string",
"usernameLabel": {"type":"string", "default": "Username"},
"passwordLabel": {"type":"string", "default": "Password"},
"loginButtonLabel": {"type":"string", "default": "Login"},
"usernameLabel": {
"type": "string",
"default": "Username"
},
"passwordLabel": {
"type": "string",
"default": "Password"
},
"loginButtonLabel": {
"type": "string",
"default": "Login"
},
"buttonClass": "string",
"inputClass": "string"
},
"tags": ["login", "credentials", "password", "logon"]
"tags": [
"login",
"credentials",
"password",
"logon"
]
},
"input" : {
"input": {
"name": "Input",
"description": "An HTML input",
"props" : {
"props": {
"value": "string",
"type": {
"type":"options",
"options":[
"text", "password", "checkbox", "color",
"date", "datetime-local", "email",
"file", "hidden", "image", "month", "number",
"radio", "range", "reset", "search", "submit",
"tel", "time", "week"],
"default":"text"
"type": "options",
"options": [
"text",
"password",
"checkbox",
"color",
"date",
"datetime-local",
"email",
"file",
"hidden",
"image",
"month",
"number",
"radio",
"range",
"reset",
"search",
"submit",
"tel",
"time",
"week"
],
"default": "text"
},
"onChange": "event",
"className": "string"
},
"tags": ["form"]
"tags": [
"form"
]
},
"select" : {
"select": {
"name": "Select",
"description": "An HTML <select> (dropdown)",
"props" : {
"props": {
"value": "string",
"className": "string"
}
},
"option" : {
"option": {
"name": "Option",
"description": "An HTML <option>, to be used with <select>",
"children": false,
"props" : {
"props": {
"value": "string",
"text": "string"
}
@ -100,27 +150,32 @@
"name": "Text",
"description": "stylable block of text",
"children": false,
"props" : {
"props": {
"text": "string",
"font": "string",
"fontSize": "string",
"color": "string",
"textAlign": {
"type": "options",
"default":"inline",
"default": "inline",
"options": [
"left", "center", "right"
"left",
"center",
"right"
]
},
"verticalAlign": {
"type": "options",
"default":"inline",
"default": "inline",
"options": [
"top", "middle", "bottom"
"top",
"middle",
"bottom"
]
},
"formattingTag": {
"type": "options",
"default":"none",
"default": "none",
"options": [
"none",
"<b> - bold",
@ -136,7 +191,46 @@
]
}
},
"tags": ["div", "container"]
"tags": [
"div",
"container"
]
},
"textfield": {
"name": "Textfield",
"description": "A component that allows the user to input text.",
"props": {
"label": "string",
"value": "string",
"onchange": "event"
}
},
"checkbox": {
"name": "Checkbox",
"description": "A selectable checkbox component",
"props": {
"label": "string",
"checked": "bool",
"value": "string",
"onchange": "event"
}
},
"radiobutton": {
"name": "Radiobutton",
"description": "A selectable radiobutton component",
"props": {
"label": "string",
"checked": "bool",
"value": "string",
"onchange": "event"
}
},
"icon": {
"description": "A HTML icon tag",
"props": {
"icon": "string",
"fontSize": "string"
}
},
"link": {
"description": "an HTML anchor <a> tag",
@ -163,8 +257,8 @@
"container": {
"name": "Container",
"description": "An element that contains and lays out other elements. e.g. <div>, <header> etc",
"props" : {
"className":"string",
"props": {
"className": "string",
"onLoad": "event",
"type": {
"type": "options",
@ -190,7 +284,7 @@
"borderWidth": "string",
"borderColor": "string",
"borderStyle": {
"type":"options",
"type": "options",
"options": [
"none",
"solid",
@ -204,20 +298,31 @@
],
"default": "none"
}
},
"container": true,
"tags": ["div", "container", "layout"]
"tags": [
"div",
"container",
"layout"
]
},
"heading": {
"name": "Heading",
"description": "An HTML H1 - H6 tag",
"props" : {
"className":"string",
"props": {
"className": "string",
"text": "string",
"type": {
"type": "options",
"default": "h1",
"options": ["h1","h2","h3","h4","h5","h6"]
"options": [
"h1",
"h2",
"h3",
"h4",
"h5",
"h6"
]
}
},
"tags": []
@ -225,15 +330,15 @@
"thead": {
"name": "TableHead",
"description": "an HTML <thead> tab",
"props" : {
"className":"string"
"props": {
"className": "string"
}
},
"tbody": {
"name": "TableBody",
"description": "an HTML <tbody> tab",
"props" : {
"className":"string"
"props": {
"className": "string"
}
}
}

View File

@ -0,0 +1,11 @@
<script>
import Input from "./Input.svelte"
export let _bb
export let label = ""
export let checked = false
export let value = ""
export let onchange = () => {}
</script>
<Input type="checkbox" {_bb} {checked} {label} {value} {onchange} />

View File

@ -2,22 +2,22 @@
export let className = ""
export let type
export let _bb
export let text = ""
let containerElement
$: containerElement && _bb.attachChildren(containerElement)
$: containerElement && !text && _bb.attachChildren(containerElement)
</script>
{#if type === 'h1'}
<h1 class={className} bind:this={containerElement} />
<h1 class={className} bind:this={containerElement}>{text}</h1>
{:else if type === 'h2'}
<h2 class={className} bind:this={containerElement} />
<h2 class={className} bind:this={containerElement}>{text}</h2>
{:else if type === 'h3'}
<h3 class={className} bind:this={containerElement} />
<h3 class={className} bind:this={containerElement}>{text}</h3>
{:else if type === 'h4'}
<h4 class={className} bind:this={containerElement} />
<h4 class={className} bind:this={containerElement}>{text}</h4>
{:else if type === 'h5'}
<h5 class={className} bind:this={containerElement} />
<h5 class={className} bind:this={containerElement}>{text}</h5>
{:else if type === 'h6'}
<h6 class={className} bind:this={containerElement} />
<h6 class={className} bind:this={containerElement}>{text}</h6>
{/if}

View File

@ -0,0 +1,9 @@
<script>
export let icon = ""
export let fontSize = "1em"
export let _bb
$: style = { fontSize }
</script>
<i class={icon} {style} />

View File

@ -1,7 +1,10 @@
<script>
export let id = ""
export let value = ""
export let className = ""
export let type = "text"
export let label = ""
export let checked = false
export let _bb
@ -14,4 +17,5 @@
}
</script>
<input class={className} {type} {value} on:change={onchange} />
<label for={id}>{label}</label>
<input {id} class={className} {type} {value} {checked} on:change={onchange} />

View File

@ -0,0 +1,75 @@
<script>
import { cssVars, createClasses } from "./cssVars"
export let className = ""
export let onLoad
export let backgroundColor
export let color
export let borderWidth
export let borderColor
export let borderStyle
export let logoUrl
export let title
export let _bb
let itemContainer
let hasLoaded
let currentChildren
$: cssVariables = {
backgroundColor,
color,
borderWidth,
borderColor,
borderStyle,
}
$: {
if (itemContainer) {
_bb.attachChildren(itemContainer)
if (!hasLoaded) {
_bb.call(onLoad)
hasLoaded = true
}
}
}
</script>
<nav use:cssVars={cssVariables}>
<a href="/">
<img class="logo" alt="logo" src={logoUrl} height="30" />
<span>{title}</span>
</a>
<div class="menu-items" bind:this={itemContainer} />
</nav>
<style>
nav {
color: var(--color);
background-color: var(--backgroundColor);
align-items: center;
display: flex;
font-weight: bold;
justify-content: space-between;
padding: 20px 0 20px 40px;
}
nav > a {
display: flex;
align-items: center;
font-size: 1.5em;
color: var(--color);
text-decoration: none;
}
nav a img {
border-radius: 15px;
margin-right: 15px;
}
.menu-items {
display: flex;
}
.menu-items > :global(*) {
margin: 0 10px;
}
</style>

View File

@ -0,0 +1,11 @@
<script>
import Input from "./Input.svelte"
export let _bb
export let label = ""
export let checked = false
export let value = ""
export let onchange = () => {}
</script>
<Input type="radio" {_bb} {checked} {label} {value} {onchange} />

View File

@ -7,29 +7,26 @@
export let formattingTag = ""
export let font = ""
export let fontSize = "1em"
export let textAlign = ""
export let verticalAlign = ""
export let color = ""
export let _bb
let style = ""
const isTag = tag => (formattingTag || "").indexOf(tag) > -1
$: {
style = buildStyle({
font,
$: style = buildStyle({
font: `${fontSize} ${font}`,
verticalAlign,
color,
"text-align": textAlign,
"vertical-align": verticalAlign,
})
}
</script>
{#if isTag('none')}
<span>{text}</span>
<span {style}>{text}</span>
{:else if isTag('<b>')}
<b class={className} {style}>{text}</b>
{:else if isTag('<strong>')}

View File

@ -0,0 +1,10 @@
<script>
import Input from "./Input.svelte"
export let _bb
export let label = ""
export let value = ""
export let onchange = () => {}
</script>
<Input type="text" {_bb} {label} {value} {onchange} />

View File

@ -3,9 +3,14 @@ export { default as text } from "./Text.svelte"
export { default as heading } from "./Heading.svelte"
export { default as input } from "./Input.svelte"
export { default as select } from "./Select.svelte"
export { default as textfield } from "./Textfield.svelte"
export { default as checkbox } from "./Checkbox.svelte"
export { default as radiobutton } from "./Radiobutton.svelte"
export { default as option } from "./Option.svelte"
export { default as button } from "./Button.svelte"
export { default as login } from "./Login.svelte"
export { default as saveRecordButton } from "./Templates/saveRecordButton"
export { default as link } from "./Link.svelte"
export { default as image } from "./Image.svelte"
export { default as icon } from "./Icon.svelte"
export { default as Navigation } from "./Navigation.svelte"

View File

@ -918,6 +918,27 @@
dependencies:
"@types/node" ">= 8"
"@rollup/plugin-json@^4.0.2":
version "4.0.2"
resolved "https://registry.yarnpkg.com/@rollup/plugin-json/-/plugin-json-4.0.2.tgz#482185ee36ac7dd21c346e2dbcc22ffed0c6f2d6"
integrity sha512-t4zJMc98BdH42mBuzjhQA7dKh0t4vMJlUka6Fz0c+iO5IVnWaEMiYBy1uBj9ruHZzXBW23IPDGL9oCzBkQ9Udg==
dependencies:
"@rollup/pluginutils" "^3.0.4"
"@rollup/pluginutils@^3.0.4":
version "3.0.9"
resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-3.0.9.tgz#aa6adca2c45e5a1b950103a999e3cddfe49fd775"
integrity sha512-TLZavlfPAZYI7v33wQh4mTP6zojne14yok3DNSLcjoG/Hirxfkonn6icP5rrNWRn8nZsirJBFFpijVOJzkUHDg==
dependencies:
"@types/estree" "0.0.39"
estree-walker "^1.0.1"
micromatch "^4.0.2"
"@types/estree@0.0.39":
version "0.0.39"
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f"
integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==
"@types/node@>= 8":
version "13.5.3"
resolved "https://registry.yarnpkg.com/@types/node/-/node-13.5.3.tgz#37f1f539b7535b9fb4ef77d59db1847a837b7f17"
@ -1216,6 +1237,13 @@ braces@^2.3.1:
split-string "^3.0.2"
to-regex "^3.0.1"
braces@^3.0.1:
version "3.0.2"
resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==
dependencies:
fill-range "^7.0.1"
btoa-lite@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/btoa-lite/-/btoa-lite-1.0.0.tgz#337766da15801210fdd956c22e9c6891ab9d0337"
@ -2065,6 +2093,11 @@ estree-walker@^0.6.1:
resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-0.6.1.tgz#53049143f40c6eb918b23671d1fe3219f3a1b362"
integrity sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==
estree-walker@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-1.0.1.tgz#31bc5d612c96b704106b477e6dd5d8aa138cb700"
integrity sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==
esutils@^2.0.2:
version "2.0.3"
resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64"
@ -2222,6 +2255,13 @@ fill-range@^4.0.0:
repeat-string "^1.6.1"
to-regex-range "^2.1.0"
fill-range@^7.0.1:
version "7.0.1"
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40"
integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==
dependencies:
to-regex-range "^5.0.1"
find-up@^1.0.0:
version "1.1.2"
resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f"
@ -2930,6 +2970,11 @@ is-number@^3.0.0:
dependencies:
kind-of "^3.0.2"
is-number@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
is-obj@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f"
@ -3464,6 +3509,14 @@ micromatch@^3.1.10:
snapdragon "^0.8.1"
to-regex "^3.0.2"
micromatch@^4.0.2:
version "4.0.2"
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.2.tgz#4fcb0999bf9fbc2fcbdd212f6d629b9a56c39259"
integrity sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==
dependencies:
braces "^3.0.1"
picomatch "^2.0.5"
mime-db@1.43.0:
version "1.43.0"
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.43.0.tgz#0a12e0502650e473d735535050e7c8f4eb4fae58"
@ -4174,6 +4227,11 @@ performance-now@^2.1.0:
resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=
picomatch@^2.0.5:
version "2.2.2"
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad"
integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==
pify@^2.0.0, pify@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
@ -5141,6 +5199,13 @@ to-regex-range@^2.1.0:
is-number "^3.0.0"
repeat-string "^1.6.1"
to-regex-range@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"
integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==
dependencies:
is-number "^7.0.0"
to-regex@^3.0.1, to-regex@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce"