Merge branch 'spectrum-bbui' of github.com:Budibase/budibase into spectrum-bbui
This commit is contained in:
commit
04d1fca3f2
|
@ -1,10 +1,16 @@
|
|||
<div class="drawer-contents">
|
||||
<div class="container" data-cy="binding-dropdown-modal">
|
||||
<div class="sidebar">
|
||||
<slot name="sidebar" />
|
||||
</div>
|
||||
<div
|
||||
class:no-sidebar={!$$slots.sidebar}
|
||||
class="container"
|
||||
data-cy="binding-dropdown-modal"
|
||||
>
|
||||
{#if $$slots.sidebar}
|
||||
<div class="sidebar">
|
||||
<slot name="sidebar" />
|
||||
</div>
|
||||
{/if}
|
||||
<div class="main">
|
||||
<slot name="main" />
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -19,6 +25,9 @@
|
|||
display: grid;
|
||||
grid-template-columns: 290px 1fr;
|
||||
}
|
||||
.no-sidebar {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.sidebar {
|
||||
border-right: var(--border-light);
|
||||
overflow: auto;
|
|
@ -1,49 +0,0 @@
|
|||
<script>
|
||||
export let extraSmall = false
|
||||
export let small = false
|
||||
export let medium = false
|
||||
export let large = false
|
||||
export let extraLarge = false
|
||||
</script>
|
||||
|
||||
<spacer
|
||||
class:extraSmall
|
||||
class:small
|
||||
class:medium
|
||||
class:large
|
||||
class:extraLarge />
|
||||
|
||||
<style>
|
||||
spacer {
|
||||
display: block;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.extraSmall {
|
||||
height: var(--spacing-xs);
|
||||
width: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.small {
|
||||
height: var(--spacing-s);
|
||||
width: var(--spacing-s);
|
||||
}
|
||||
|
||||
.medium {
|
||||
height: var(--spacing-m);
|
||||
width: var(--spacing-m);
|
||||
}
|
||||
|
||||
.large {
|
||||
height: var(--spacing-l);
|
||||
width: var(--spacing-l);
|
||||
}
|
||||
|
||||
.extraLarge {
|
||||
height: var(--spacing-xl);
|
||||
width: var(--spacing-xl);
|
||||
}
|
||||
</style>
|
|
@ -1,51 +0,0 @@
|
|||
<script>
|
||||
import { View } from "svench";
|
||||
import Spacer from "./Spacer.svelte";
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.block {
|
||||
background: black;
|
||||
height: 100px;
|
||||
width: 100px;
|
||||
border-radius: var(--border-radius-s);
|
||||
}
|
||||
|
||||
.horizontal {
|
||||
display: flex;
|
||||
}
|
||||
</style>
|
||||
|
||||
<View name="extraSmall">
|
||||
<div class="block"></div>
|
||||
<Spacer extraSmall />
|
||||
<div class="block"></div>
|
||||
</View>
|
||||
|
||||
<View name="Small">
|
||||
<div class="block"></div>
|
||||
<Spacer small />
|
||||
<div class="block"></div>
|
||||
</View>
|
||||
|
||||
<View name="Medium">
|
||||
<div class="block"></div>
|
||||
<Spacer medium />
|
||||
<div class="block"></div>
|
||||
</View>
|
||||
|
||||
<View name="Large">
|
||||
<div class="horizontal">
|
||||
<div class="block"></div>
|
||||
<Spacer large />
|
||||
<div class="block"></div>
|
||||
</div>
|
||||
</View>
|
||||
|
||||
<View name="extraLarge">
|
||||
<div class="horizontal">
|
||||
<div class="block"></div>
|
||||
<Spacer extraLarge />
|
||||
<div class="block"></div>
|
||||
</div>
|
||||
</View>
|
|
@ -10,7 +10,7 @@ export { default as Select } from "./Form/Select.svelte"
|
|||
export { default as Combobox } from "./Form/Combobox.svelte"
|
||||
export { default as Dropzone } from "./Form/Dropzone.svelte"
|
||||
export { default as Drawer } from "./Drawer/Drawer.svelte"
|
||||
export { default as DrawerContentWithSidebar } from "./Drawer/DrawerContentWithSidebar.svelte"
|
||||
export { default as DrawerContent } from "./Drawer/DrawerContent.svelte"
|
||||
export { default as Avatar } from "./Avatar/Avatar.svelte"
|
||||
export { default as ActionButton } from "./ActionButton/ActionButton.svelte"
|
||||
export { default as ActionGroup } from "./ActionGroup/ActionGroup.svelte"
|
||||
|
@ -36,7 +36,6 @@ export { default as MenuItem } from "./Menu/Item.svelte"
|
|||
export { default as Modal } from "./Modal/Modal.svelte"
|
||||
export { default as ModalContent } from "./Modal/ModalContent.svelte"
|
||||
export { default as NotificationDisplay } from "./Notification/NotificationDisplay.svelte"
|
||||
export { default as Spacer } from "./Spacer/Spacer.svelte"
|
||||
export { default as SideNavigation } from "./SideNavigation/Navigation.svelte"
|
||||
export { default as SideNavigationItem } from "./SideNavigation/Item.svelte"
|
||||
export { default as DatePicker } from "./Form/DatePicker.svelte"
|
||||
|
|
|
@ -252,7 +252,7 @@ export const getSchemaForDatasource = (datasource, isForm = false) => {
|
|||
if (datasource) {
|
||||
const { type } = datasource
|
||||
if (type === "query") {
|
||||
const queries = get(queriesStores).queries
|
||||
const queries = get(queriesStores).list
|
||||
table = queries.find(query => query._id === datasource._id)
|
||||
} else {
|
||||
const tables = get(tablesStore).list
|
||||
|
|
|
@ -1,6 +1,12 @@
|
|||
<script>
|
||||
import groupBy from "lodash/fp/groupBy"
|
||||
import { Input, TextArea, Heading, Layout } from "@budibase/bbui"
|
||||
import {
|
||||
Input,
|
||||
TextArea,
|
||||
Heading,
|
||||
Layout,
|
||||
DrawerContent,
|
||||
} from "@budibase/bbui"
|
||||
import { createEventDispatcher } from "svelte"
|
||||
import { isValid } from "@budibase/string-templates"
|
||||
import { handlebarsCompletions } from "constants/completions"
|
||||
|
@ -44,8 +50,8 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<div class="container">
|
||||
<div class="list">
|
||||
<DrawerContent>
|
||||
<div slot="sidebar" class="list">
|
||||
<Layout>
|
||||
<div class="section">
|
||||
<Heading s h3>Available bindings</Heading>
|
||||
|
@ -98,16 +104,9 @@
|
|||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</DrawerContent>
|
||||
|
||||
<style>
|
||||
.container {
|
||||
height: 40vh;
|
||||
overflow-y: auto;
|
||||
display: grid;
|
||||
grid-template-columns: 290px 1fr;
|
||||
}
|
||||
|
||||
.list {
|
||||
grid-gap: var(--spacing-s);
|
||||
border-right: var(--border-light);
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
Label,
|
||||
Input,
|
||||
Select,
|
||||
Spacer,
|
||||
notifications,
|
||||
Body,
|
||||
ModalContent,
|
||||
|
@ -39,10 +38,11 @@
|
|||
<Input value={capitalise(level)} disabled />
|
||||
<Select
|
||||
value={permissions[level]}
|
||||
on:change={e => changePermission(level, e.detail)}
|
||||
on:change={(e) => changePermission(level, e.detail)}
|
||||
options={$roles}
|
||||
getOptionLabel={x => x.name}
|
||||
getOptionValue={x => x._id} />
|
||||
getOptionLabel={(x) => x.name}
|
||||
getOptionValue={(x) => x._id}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
</ModalContent>
|
||||
|
|
|
@ -1,34 +1,33 @@
|
|||
<script>
|
||||
import { Label, Input, Spacer } from "@budibase/bbui"
|
||||
import { Label, Input, Layout } from "@budibase/bbui"
|
||||
import KeyValueBuilder from "components/integration/KeyValueBuilder.svelte"
|
||||
import { capitalise } from "../../../../helpers"
|
||||
|
||||
export let integration
|
||||
export let schema
|
||||
|
||||
let unsaved = false
|
||||
</script>
|
||||
|
||||
<form>
|
||||
{#each Object.keys(schema) as configKey}
|
||||
{#if schema[configKey].type === "object"}
|
||||
<Label>{capitalise(configKey)}</Label>
|
||||
<Spacer small />
|
||||
<KeyValueBuilder
|
||||
defaults={schema[configKey].default}
|
||||
bind:object={integration[configKey]}
|
||||
/>
|
||||
{:else}
|
||||
<div class="form-row">
|
||||
<Layout gap="S">
|
||||
{#each Object.keys(schema) as configKey}
|
||||
{#if schema[configKey].type === "object"}
|
||||
<Label>{capitalise(configKey)}</Label>
|
||||
<Input
|
||||
type={schema[configKey].type}
|
||||
on:change
|
||||
bind:value={integration[configKey]}
|
||||
<KeyValueBuilder
|
||||
defaults={schema[configKey].default}
|
||||
bind:object={integration[configKey]}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
{:else}
|
||||
<div class="form-row">
|
||||
<Label>{capitalise(configKey)}</Label>
|
||||
<Input
|
||||
type={schema[configKey].type}
|
||||
on:change
|
||||
bind:value={integration[configKey]}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
</Layout>
|
||||
</form>
|
||||
|
||||
<style>
|
||||
|
@ -37,6 +36,5 @@
|
|||
grid-template-columns: 20% 1fr;
|
||||
grid-gap: var(--spacing-l);
|
||||
align-items: center;
|
||||
margin-bottom: var(--spacing-m);
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
import groupBy from "lodash/fp/groupBy"
|
||||
import { Search, TextArea, Heading, Label, DrawerContentWithSidebar, Layout } from "@budibase/bbui"
|
||||
import { Search, TextArea, Heading, Label, DrawerContent, Layout } from "@budibase/bbui"
|
||||
import { createEventDispatcher } from "svelte"
|
||||
import { isValid } from "@budibase/string-templates"
|
||||
import {
|
||||
|
@ -57,7 +57,7 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<DrawerContentWithSidebar>
|
||||
<DrawerContent>
|
||||
<svelte:fragment slot="sidebar">
|
||||
<Layout>
|
||||
<Search placeholder="Search" bind:value={search} />
|
||||
|
@ -107,7 +107,7 @@
|
|||
</section>
|
||||
</Layout>
|
||||
</svelte:fragment>
|
||||
<div class="main" slot="main">
|
||||
<div class="main">
|
||||
<TextArea
|
||||
bind:getCaretPosition
|
||||
bind:value
|
||||
|
@ -121,7 +121,7 @@
|
|||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
</DrawerContentWithSidebar>
|
||||
</DrawerContent>
|
||||
|
||||
<style>
|
||||
.main {
|
||||
|
|
|
@ -2,13 +2,13 @@
|
|||
import { getBindableProperties } from "builderStore/dataBinding"
|
||||
import {
|
||||
Button,
|
||||
Icon,
|
||||
Popover,
|
||||
Divider,
|
||||
Select,
|
||||
Spacer,
|
||||
Layout,
|
||||
Heading,
|
||||
Drawer,
|
||||
DrawerContent,
|
||||
} from "@budibase/bbui"
|
||||
import { createEventDispatcher } from "svelte"
|
||||
import { store, currentAsset } from "builderStore"
|
||||
|
@ -30,7 +30,7 @@
|
|||
export let showAllQueries
|
||||
|
||||
$: text = value?.label ?? "Choose an option"
|
||||
$: tables = $tablesStore.list.map(m => ({
|
||||
$: tables = $tablesStore.list.map((m) => ({
|
||||
label: m.name,
|
||||
tableId: m._id,
|
||||
type: "table",
|
||||
|
@ -46,9 +46,9 @@
|
|||
}, [])
|
||||
$: queries = $queriesStore.list
|
||||
.filter(
|
||||
query => showAllQueries || query.queryVerb === "read" || query.readable
|
||||
(query) => showAllQueries || query.queryVerb === "read" || query.readable
|
||||
)
|
||||
.map(query => ({
|
||||
.map((query) => ({
|
||||
label: query.name,
|
||||
name: query.name,
|
||||
tableId: query._id,
|
||||
|
@ -61,15 +61,15 @@
|
|||
$currentAsset,
|
||||
$store.selectedComponentId
|
||||
)
|
||||
$: queryBindableProperties = bindableProperties.map(property => ({
|
||||
$: queryBindableProperties = bindableProperties.map((property) => ({
|
||||
...property,
|
||||
category: property.type === "instance" ? "Component" : "Table",
|
||||
label: property.readableBinding,
|
||||
path: property.readableBinding,
|
||||
}))
|
||||
$: links = bindableProperties
|
||||
.filter(x => x.fieldSchema?.type === "link")
|
||||
.map(property => {
|
||||
.filter((x) => x.fieldSchema?.type === "link")
|
||||
.map((property) => {
|
||||
return {
|
||||
providerId: property.providerId,
|
||||
label: property.readableBinding,
|
||||
|
@ -89,7 +89,7 @@
|
|||
}
|
||||
|
||||
function fetchQueryDefinition(query) {
|
||||
const source = $datasources.list.find(ds => ds._id === query.datasourceId)
|
||||
const source = $datasources.list.find((ds) => ds._id === query.datasourceId)
|
||||
.source
|
||||
return $integrations[source].query[query.queryVerb]
|
||||
}
|
||||
|
@ -100,38 +100,43 @@
|
|||
readonly
|
||||
value={text}
|
||||
options={[text]}
|
||||
on:click={dropdownRight.show} />
|
||||
{#if value?.type === 'query'}
|
||||
on:click={dropdownRight.show}
|
||||
/>
|
||||
{#if value?.type === "query"}
|
||||
<i class="ri-settings-5-line" on:click={drawer.show} />
|
||||
<Drawer title={'Query Parameters'} bind:this={drawer}>
|
||||
<div slot="buttons">
|
||||
<Button
|
||||
blue
|
||||
thin
|
||||
on:click={() => {
|
||||
notifications.success('Query parameters saved.')
|
||||
handleSelected(value)
|
||||
drawer.hide()
|
||||
}}>
|
||||
Save
|
||||
</Button>
|
||||
</div>
|
||||
<div class="drawer-contents" slot="body">
|
||||
{#if value.parameters.length > 0}
|
||||
<ParameterBuilder
|
||||
bind:customParams={value.queryParams}
|
||||
parameters={queries.find(query => query._id === value._id).parameters}
|
||||
bindings={queryBindableProperties} />
|
||||
{/if}
|
||||
<!-- <Spacer large />-->
|
||||
<IntegrationQueryEditor
|
||||
height={200}
|
||||
query={value}
|
||||
schema={fetchQueryDefinition(value)}
|
||||
datasource={$datasources.list.find(ds => ds._id === value.datasourceId)}
|
||||
editable={false} />
|
||||
<Spacer large />
|
||||
</div>
|
||||
<Drawer title={"Query Parameters"} bind:this={drawer}>
|
||||
<Button
|
||||
slot="buttons"
|
||||
cta
|
||||
on:click={() => {
|
||||
notifications.success("Query parameters saved.")
|
||||
handleSelected(value)
|
||||
drawer.hide()
|
||||
}}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
<DrawerContent slot="body">
|
||||
<Layout>
|
||||
{#if value.parameters.length > 0}
|
||||
<ParameterBuilder
|
||||
bind:customParams={value.queryParams}
|
||||
parameters={queries.find((query) => query._id === value._id)
|
||||
.parameters}
|
||||
bindings={queryBindableProperties}
|
||||
/>
|
||||
{/if}
|
||||
<IntegrationQueryEditor
|
||||
height={200}
|
||||
query={value}
|
||||
schema={fetchQueryDefinition(value)}
|
||||
datasource={$datasources.list.find(
|
||||
(ds) => ds._id === value.datasourceId
|
||||
)}
|
||||
editable={false}
|
||||
/>
|
||||
</Layout>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
{/if}
|
||||
</div>
|
||||
|
@ -144,7 +149,8 @@
|
|||
{#each tables as table}
|
||||
<li
|
||||
class:selected={value === table}
|
||||
on:click={() => handleSelected(table)}>
|
||||
on:click={() => handleSelected(table)}
|
||||
>
|
||||
{table.label}
|
||||
</li>
|
||||
{/each}
|
||||
|
@ -157,7 +163,8 @@
|
|||
{#each views as view}
|
||||
<li
|
||||
class:selected={value === view}
|
||||
on:click={() => handleSelected(view)}>
|
||||
on:click={() => handleSelected(view)}
|
||||
>
|
||||
{view.label}
|
||||
</li>
|
||||
{/each}
|
||||
|
@ -170,7 +177,8 @@
|
|||
{#each links as link}
|
||||
<li
|
||||
class:selected={value === link}
|
||||
on:click={() => handleSelected(link)}>
|
||||
on:click={() => handleSelected(link)}
|
||||
>
|
||||
{link.label}
|
||||
</li>
|
||||
{/each}
|
||||
|
@ -183,7 +191,8 @@
|
|||
{#each queries as query}
|
||||
<li
|
||||
class:selected={value === query}
|
||||
on:click={() => handleSelected(query)}>
|
||||
on:click={() => handleSelected(query)}
|
||||
>
|
||||
{query.label}
|
||||
</li>
|
||||
{/each}
|
||||
|
@ -198,7 +207,8 @@
|
|||
{#each otherSources as source}
|
||||
<li
|
||||
class:selected={value === source}
|
||||
on:click={() => handleSelected(source)}>
|
||||
on:click={() => handleSelected(source)}
|
||||
>
|
||||
{source.label}
|
||||
</li>
|
||||
{/each}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<script>
|
||||
import { flip } from "svelte/animate"
|
||||
import { dndzone } from "svelte-dnd-action"
|
||||
import { Icon, Button, Popover, Spacer } from "@budibase/bbui"
|
||||
import { Icon, Button, Popover, Layout, DrawerContent } from "@budibase/bbui"
|
||||
import actionTypes from "./actions"
|
||||
import { generate } from "shortid"
|
||||
|
||||
|
@ -68,14 +68,13 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<div class="actions-container">
|
||||
<div class="actions-list">
|
||||
<div>
|
||||
<DrawerContent>
|
||||
<div class="actions-list" slot="sidebar">
|
||||
<Layout>
|
||||
<div bind:this={addActionButton}>
|
||||
<Button wide secondary on:click={addActionDropdown.show}>
|
||||
Add Action
|
||||
</Button>
|
||||
<Spacer small />
|
||||
</div>
|
||||
<Popover
|
||||
bind:this={addActionDropdown}
|
||||
|
@ -90,44 +89,44 @@
|
|||
{/each}
|
||||
</div>
|
||||
</Popover>
|
||||
</div>
|
||||
|
||||
{#if actions && actions.length > 0}
|
||||
<div
|
||||
class="action-dnd-container"
|
||||
use:dndzone={{
|
||||
items: actions,
|
||||
flipDurationMs,
|
||||
dropTargetStyle: { outline: "none" },
|
||||
}}
|
||||
on:consider={handleDndConsider}
|
||||
on:finalize={handleDndFinalize}
|
||||
>
|
||||
{#each actions as action, index (action.id)}
|
||||
<div
|
||||
class="action-container"
|
||||
animate:flip={{ duration: flipDurationMs }}
|
||||
>
|
||||
{#if actions && actions.length > 0}
|
||||
<div
|
||||
class="action-dnd-container"
|
||||
use:dndzone={{
|
||||
items: actions,
|
||||
flipDurationMs,
|
||||
dropTargetStyle: { outline: "none" },
|
||||
}}
|
||||
on:consider={handleDndConsider}
|
||||
on:finalize={handleDndFinalize}
|
||||
>
|
||||
{#each actions as action, index (action.id)}
|
||||
<div
|
||||
class="action-header"
|
||||
class:selected={action === selectedAction}
|
||||
on:click={selectAction(action)}
|
||||
class="action-container"
|
||||
animate:flip={{ duration: flipDurationMs }}
|
||||
>
|
||||
{index + 1}.
|
||||
{action[EVENT_TYPE_KEY]}
|
||||
<div
|
||||
class="action-header"
|
||||
class:selected={action === selectedAction}
|
||||
on:click={selectAction(action)}
|
||||
>
|
||||
{index + 1}.
|
||||
{action[EVENT_TYPE_KEY]}
|
||||
</div>
|
||||
<div
|
||||
on:click={() => deleteAction(index)}
|
||||
style="margin-left: auto;"
|
||||
>
|
||||
<Icon size="S" hoverable name="Close" />
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
on:click={() => deleteAction(index)}
|
||||
style="margin-left: auto;"
|
||||
>
|
||||
<Icon size="S" hoverable name="Close" />
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</Layout>
|
||||
</div>
|
||||
<div class="action-config">
|
||||
<Layout>
|
||||
{#if selectedAction}
|
||||
<div class="selected-action-container">
|
||||
<svelte:component
|
||||
|
@ -136,15 +135,15 @@
|
|||
/>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</Layout>
|
||||
</DrawerContent>
|
||||
|
||||
<style>
|
||||
.action-header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
margin-top: var(--spacing-m);
|
||||
margin-top: var(--spacing-s);
|
||||
}
|
||||
|
||||
.action-header {
|
||||
|
@ -160,11 +159,6 @@
|
|||
color: var(--ink);
|
||||
}
|
||||
|
||||
.actions-list {
|
||||
border-right: var(--border-light);
|
||||
padding: var(--spacing-l);
|
||||
}
|
||||
|
||||
.available-action {
|
||||
padding: var(--spacing-s);
|
||||
font-size: var(--font-size-xs);
|
||||
|
@ -175,16 +169,6 @@
|
|||
background: var(--grey-2);
|
||||
}
|
||||
|
||||
.actions-container {
|
||||
height: 40vh;
|
||||
display: grid;
|
||||
grid-template-columns: 260px 1fr;
|
||||
grid-auto-flow: column;
|
||||
min-height: 0;
|
||||
padding-top: 0;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.action-container {
|
||||
border-bottom: 1px solid var(--grey-1);
|
||||
display: flex;
|
||||
|
@ -194,10 +178,6 @@
|
|||
border-bottom: none;
|
||||
}
|
||||
|
||||
.selected-action-container {
|
||||
padding: var(--spacing-l);
|
||||
}
|
||||
|
||||
i:hover {
|
||||
color: var(--red);
|
||||
cursor: pointer;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
import { Select, Label, Spacer } from "@budibase/bbui"
|
||||
import { Select, Label, Layout } from "@budibase/bbui"
|
||||
import { store, currentAsset } from "builderStore"
|
||||
import { datasources, integrations, queries } from "stores/backend"
|
||||
import { getBindableProperties } from "builderStore/dataBinding"
|
||||
|
@ -8,9 +8,9 @@
|
|||
|
||||
export let parameters
|
||||
|
||||
$: query = $queries.list.find(q => q._id === parameters.queryId)
|
||||
$: query = $queries.list.find((q) => q._id === parameters.queryId)
|
||||
$: datasource = $datasources.list.find(
|
||||
ds => ds._id === parameters.datasourceId
|
||||
(ds) => ds._id === parameters.datasourceId
|
||||
)
|
||||
$: bindableProperties = getBindableProperties(
|
||||
$currentAsset,
|
||||
|
@ -18,40 +18,44 @@
|
|||
)
|
||||
|
||||
function fetchQueryDefinition(query) {
|
||||
const source = $datasources.list.find(ds => ds._id === query.datasourceId)
|
||||
const source = $datasources.list.find((ds) => ds._id === query.datasourceId)
|
||||
.source
|
||||
return $integrations[source].query[query.queryVerb]
|
||||
}
|
||||
</script>
|
||||
|
||||
<Label>Datasource</Label>
|
||||
<Select
|
||||
bind:value={parameters.datasourceId}
|
||||
option={$datasources.list}
|
||||
getOptionLabel={source => source.name}
|
||||
getOptionValue={source => source._id} />
|
||||
|
||||
<Spacer medium />
|
||||
|
||||
{#if parameters.datasourceId}
|
||||
<Label>Query</Label>
|
||||
<Layout noGap noPadding>
|
||||
<Label>Datasource</Label>
|
||||
<Select
|
||||
bind:value={parameters.queryId}
|
||||
options={$queries.list.filter(query => query.datasourceId === datasource._id)}
|
||||
getOptionLabel={query => query.name}
|
||||
getOptionValue={query => query._id} />
|
||||
{/if}
|
||||
bind:value={parameters.datasourceId}
|
||||
option={$datasources.list}
|
||||
getOptionLabel={(source) => source.name}
|
||||
getOptionValue={(source) => source._id}
|
||||
/>
|
||||
|
||||
<Spacer medium />
|
||||
{#if parameters.datasourceId}
|
||||
<Label>Query</Label>
|
||||
<Select
|
||||
bind:value={parameters.queryId}
|
||||
options={$queries.list.filter(
|
||||
(query) => query.datasourceId === datasource._id
|
||||
)}
|
||||
getOptionLabel={(query) => query.name}
|
||||
getOptionValue={(query) => query._id}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
{#if query?.parameters?.length > 0}
|
||||
<ParameterBuilder
|
||||
bind:customParams={parameters.queryParams}
|
||||
parameters={query.parameters}
|
||||
bindings={bindableProperties} />
|
||||
<IntegrationQueryEditor
|
||||
height={200}
|
||||
{query}
|
||||
schema={fetchQueryDefinition(query)}
|
||||
editable={false} />
|
||||
{/if}
|
||||
{#if query?.parameters?.length > 0}
|
||||
<ParameterBuilder
|
||||
bind:customParams={parameters.queryParams}
|
||||
parameters={query.parameters}
|
||||
bindings={bindableProperties}
|
||||
/>
|
||||
<IntegrationQueryEditor
|
||||
height={200}
|
||||
{query}
|
||||
schema={fetchQueryDefinition(query)}
|
||||
editable={false}
|
||||
/>
|
||||
{/if}
|
||||
</Layout>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
import { Label, ActionButton, Button, Spacer, Select, Input } from "@budibase/bbui"
|
||||
import { Label, ActionButton, Button, Select, Input } from "@budibase/bbui"
|
||||
import { store, currentAsset } from "builderStore"
|
||||
import { getBindableProperties } from "builderStore/dataBinding"
|
||||
import { createEventDispatcher } from "svelte"
|
||||
|
@ -20,11 +20,11 @@
|
|||
)
|
||||
|
||||
const addField = () => {
|
||||
fields = [...fields.filter(field => field[0]), ["", ""]]
|
||||
fields = [...fields.filter((field) => field[0]), ["", ""]]
|
||||
}
|
||||
|
||||
const removeField = name => {
|
||||
fields = fields.filter(field => field[0] !== name)
|
||||
const removeField = (name) => {
|
||||
fields = fields.filter((field) => field[0] !== name)
|
||||
}
|
||||
|
||||
const updateFieldValue = (idx, value) => {
|
||||
|
@ -37,10 +37,10 @@
|
|||
fields = fields
|
||||
}
|
||||
|
||||
const onChange = fields => {
|
||||
const onChange = (fields) => {
|
||||
const newParamFields = {}
|
||||
fields
|
||||
.filter(field => field[0])
|
||||
.filter((field) => field[0])
|
||||
.forEach(([field, value]) => {
|
||||
newParamFields[field] = value
|
||||
})
|
||||
|
@ -54,32 +54,35 @@
|
|||
{#if schemaFields}
|
||||
<Select
|
||||
value={field[0]}
|
||||
on:change={event => updateFieldName(idx, event.detail)}
|
||||
options={schemaFields.map(field => field.name)} />
|
||||
on:change={(event) => updateFieldName(idx, event.detail)}
|
||||
options={schemaFields.map((field) => field.name)}
|
||||
/>
|
||||
{:else}
|
||||
<Input
|
||||
thin
|
||||
secondary
|
||||
value={field[0]}
|
||||
on:change={event => updateFieldName(idx, event.detail)} />
|
||||
on:change={(event) => updateFieldName(idx, event.detail)}
|
||||
/>
|
||||
{/if}
|
||||
<Label small>{valueLabel}</Label>
|
||||
<DrawerBindableInput
|
||||
title={`Value for "${field[0]}"`}
|
||||
value={field[1]}
|
||||
bindings={bindableProperties}
|
||||
on:change={event => updateFieldValue(idx, event.detail)} />
|
||||
on:change={(event) => updateFieldValue(idx, event.detail)}
|
||||
/>
|
||||
<ActionButton
|
||||
size="S"
|
||||
quiet
|
||||
icon="Delete"
|
||||
on:click={() => removeField(field[0])} />
|
||||
on:click={() => removeField(field[0])}
|
||||
/>
|
||||
{/each}
|
||||
<div>
|
||||
<Spacer small />
|
||||
<Button icon="AddCircle" size="S" cta on:click={addField}>
|
||||
Add
|
||||
{fieldLabel}
|
||||
</Button>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
import { Button, Drawer, Spacer, Body } from "@budibase/bbui"
|
||||
import { Button, Drawer, Body, DrawerContent, Layout } from "@budibase/bbui"
|
||||
import { createEventDispatcher } from "svelte"
|
||||
import { notifications } from "@budibase/bbui"
|
||||
import {
|
||||
|
@ -18,7 +18,7 @@
|
|||
|
||||
$: schemaFields = getSchemaFields(componentInstance)
|
||||
|
||||
const getSchemaFields = component => {
|
||||
const getSchemaFields = (component) => {
|
||||
const datasource = getDatasourceForProvider($currentAsset, component)
|
||||
const { schema } = getSchemaForDatasource(datasource)
|
||||
return Object.values(schema || {})
|
||||
|
@ -30,42 +30,37 @@
|
|||
drawer.hide()
|
||||
}
|
||||
|
||||
const onFieldsChanged = event => {
|
||||
const onFieldsChanged = (event) => {
|
||||
tempValue = event.detail
|
||||
}
|
||||
</script>
|
||||
|
||||
<Button secondary wide on:click={drawer.show}>Define Filters</Button>
|
||||
<Drawer bind:this={drawer} title='Filtering'>
|
||||
<Drawer bind:this={drawer} title="Filtering">
|
||||
<Button cta slot="buttons" on:click={saveFilter}>Save</Button>
|
||||
<div class="root" slot="body">
|
||||
<Body s>
|
||||
{#if !Object.keys(tempValue || {}).length}
|
||||
Add your first filter column.
|
||||
{:else}
|
||||
Results are filtered to only those which match all of the following
|
||||
constaints.
|
||||
{/if}
|
||||
</Body>
|
||||
<Spacer medium />
|
||||
<div class="fields">
|
||||
<SaveFields
|
||||
parameterFields={value}
|
||||
{schemaFields}
|
||||
valueLabel="Equals"
|
||||
on:change={onFieldsChanged} />
|
||||
</div>
|
||||
</div>
|
||||
<DrawerContent slot="body">
|
||||
<Layout>
|
||||
<Body s>
|
||||
{#if !Object.keys(tempValue || {}).length}
|
||||
Add your first filter column.
|
||||
{:else}
|
||||
Results are filtered to only those which match all of the following
|
||||
constaints.
|
||||
{/if}
|
||||
</Body>
|
||||
<div class="fields">
|
||||
<SaveFields
|
||||
parameterFields={value}
|
||||
{schemaFields}
|
||||
valueLabel="Equals"
|
||||
on:change={onFieldsChanged}
|
||||
/>
|
||||
</div>
|
||||
</Layout>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
|
||||
<style>
|
||||
.root {
|
||||
padding: var(--spacing-l);
|
||||
min-height: calc(40vh - 2 * var(--spacing-l));
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.fields {
|
||||
display: grid;
|
||||
column-gap: var(--spacing-l);
|
||||
|
|
|
@ -33,7 +33,7 @@
|
|||
{/each}
|
||||
</div>
|
||||
{#if !readOnly}
|
||||
<Button secondary thin outline on:click={addEntry}>Add</Button>
|
||||
<div><Button secondary thin outline on:click={addEntry}>Add</Button></div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
import CodeMirror from "./codemirror"
|
||||
import { Label, Spacer } from "@budibase/bbui"
|
||||
import { Label } from "@budibase/bbui"
|
||||
import { onMount, createEventDispatcher } from "svelte"
|
||||
import { themeStore } from "builderStore"
|
||||
|
||||
|
@ -134,7 +134,7 @@
|
|||
|
||||
editor = CodeMirror.fromTextArea(refs.editor, opts)
|
||||
|
||||
editor.on("change", instance => {
|
||||
editor.on("change", (instance) => {
|
||||
if (!updating_externally) {
|
||||
const value = instance.getValue()
|
||||
dispatch("change", { value })
|
||||
|
@ -160,13 +160,12 @@
|
|||
}
|
||||
|
||||
function sleep(ms) {
|
||||
return new Promise(fulfil => setTimeout(fulfil, ms))
|
||||
return new Promise((fulfil) => setTimeout(fulfil, ms))
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if label}
|
||||
<Label small>{label}</Label>
|
||||
<Spacer medium />
|
||||
{/if}
|
||||
<div style={`--code-mirror-height: ${editorHeight}px`}>
|
||||
<textarea tabindex="0" bind:this={refs.editor} readonly {value} />
|
||||
|
@ -177,6 +176,10 @@
|
|||
visibility: hidden;
|
||||
}
|
||||
|
||||
div {
|
||||
margin-top: var(--spacing-s);
|
||||
}
|
||||
|
||||
div :global(.CodeMirror) {
|
||||
height: var(--code-mirror-height) !important;
|
||||
border-radius: var(--border-radius-s);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
import { Label, Spacer, Input } from "@budibase/bbui"
|
||||
import { Label, Layout, Input } from "@budibase/bbui"
|
||||
import Editor from "./QueryEditor.svelte"
|
||||
import KeyValueBuilder from "./KeyValueBuilder.svelte"
|
||||
|
||||
|
@ -15,22 +15,22 @@
|
|||
</script>
|
||||
|
||||
<form on:submit|preventDefault>
|
||||
<div class="field">
|
||||
<Layout noPadding gap="S">
|
||||
{#each schemaKeys as field}
|
||||
{#if schema.fields[field]?.type === 'object'}
|
||||
{#if schema.fields[field]?.type === "object"}
|
||||
<div>
|
||||
<Label small>{field}</Label>
|
||||
<Spacer small />
|
||||
<KeyValueBuilder readOnly={!editable} bind:object={fields[field]} />
|
||||
</div>
|
||||
{:else if schema.fields[field]?.type === 'json'}
|
||||
{:else if schema.fields[field]?.type === "json"}
|
||||
<div>
|
||||
<Label extraSmall grey>{field}</Label>
|
||||
<Editor
|
||||
mode="json"
|
||||
on:change={({ detail }) => (fields[field] = detail.value)}
|
||||
readOnly={!editable}
|
||||
value={fields[field]} />
|
||||
value={fields[field]}
|
||||
/>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="horizontal">
|
||||
|
@ -41,11 +41,12 @@
|
|||
disabled={!editable}
|
||||
type={schema.fields[field]?.type}
|
||||
required={schema.fields[field]?.required}
|
||||
bind:value={fields[field]} />
|
||||
bind:value={fields[field]}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
</Layout>
|
||||
</form>
|
||||
{#if schema.customisable}
|
||||
<Editor
|
||||
|
@ -53,7 +54,8 @@
|
|||
mode="json"
|
||||
on:change={updateCustomFields}
|
||||
readOnly={!editable}
|
||||
value={fields.customData} />
|
||||
value={fields.customData}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
import { Icon, Body, Button, Input, Heading, Spacer } from "@budibase/bbui"
|
||||
import { Icon, Body, Button, Input, Heading, Layout } from "@budibase/bbui"
|
||||
import {
|
||||
readableToRuntimeBinding,
|
||||
runtimeToReadableBinding,
|
||||
|
@ -29,7 +29,7 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<section>
|
||||
<Layout paddingX="none" gap="S">
|
||||
<div class="controls">
|
||||
<Heading s>Parameters</Heading>
|
||||
{#if !bindable}
|
||||
|
@ -45,7 +45,6 @@
|
|||
values left blank.
|
||||
{/if}
|
||||
</Body>
|
||||
<Spacer large />
|
||||
<div class="parameters" class:bindable>
|
||||
{#each parameters as parameter, idx}
|
||||
<Input
|
||||
|
@ -81,7 +80,7 @@
|
|||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
</section>
|
||||
</Layout>
|
||||
|
||||
<style>
|
||||
.parameters.bindable {
|
||||
|
|
|
@ -4,12 +4,12 @@
|
|||
Icon,
|
||||
Select,
|
||||
Button,
|
||||
ButtonGroup,
|
||||
Body,
|
||||
Label,
|
||||
Layout,
|
||||
Input,
|
||||
Heading,
|
||||
Spacer,
|
||||
Tabs,
|
||||
Tab,
|
||||
} from "@budibase/bbui"
|
||||
|
@ -149,29 +149,26 @@
|
|||
</div>
|
||||
<div class="viewer-controls">
|
||||
<Heading s>Results</Heading>
|
||||
<div class="button-container">
|
||||
<ButtonGroup>
|
||||
<Button
|
||||
secondary
|
||||
thin
|
||||
cta
|
||||
disabled={data.length === 0 || !query.name}
|
||||
on:click={saveQuery}
|
||||
>
|
||||
Save Query
|
||||
</Button>
|
||||
<Spacer medium />
|
||||
<Button thin secondary on:click={previewQuery}>Run Query</Button>
|
||||
</div>
|
||||
<Button secondary on:click={previewQuery}>Run Query</Button>
|
||||
</ButtonGroup>
|
||||
</div>
|
||||
<Body s>
|
||||
Below, you can preview the results from your query and change the
|
||||
schema.
|
||||
</Body>
|
||||
<section class="viewer">
|
||||
{#if data}
|
||||
<Tabs selected="JSON">
|
||||
<Tab title="JSON">
|
||||
<pre
|
||||
class="preview">
|
||||
<Body s>
|
||||
Below, you can preview the results from your query and change the schema.
|
||||
</Body>
|
||||
<section class="viewer">
|
||||
{#if data}
|
||||
<Tabs selected="JSON">
|
||||
<Tab title="JSON">
|
||||
<pre
|
||||
class="preview">
|
||||
<!-- prettier-ignore -->
|
||||
{#if !data[0]}
|
||||
Please run your query to fetch some data.
|
||||
|
@ -179,25 +176,27 @@
|
|||
{JSON.stringify(data[0], undefined, 2)}
|
||||
{/if}
|
||||
</pre>
|
||||
</Tab>
|
||||
<Tab title="Schema">
|
||||
</Tab>
|
||||
<Tab title="Schema">
|
||||
<Layout gap="S">
|
||||
{#each fields as field, idx}
|
||||
<Spacer small />
|
||||
<div class="field">
|
||||
<Input placeholder="Field Name" bind:value={field.name} />
|
||||
<Select bind:value={field.type} options={typeOptions} />
|
||||
<Icon name="bleClose" on:click={() => deleteField(idx)} />
|
||||
</div>
|
||||
{/each}
|
||||
<Spacer extraLarge />
|
||||
<Button thin secondary on:click={newField}>Add Field</Button>
|
||||
</Tab>
|
||||
<Tab title="Preview">
|
||||
<ExternalDataSourceTable {query} {data} />
|
||||
</Tab>
|
||||
</Tabs>
|
||||
{/if}
|
||||
</section>
|
||||
<div>
|
||||
<Button secondary on:click={newField}>Add Field</Button>
|
||||
</div>
|
||||
</Layout>
|
||||
</Tab>
|
||||
<Tab title="Preview">
|
||||
<ExternalDataSourceTable {query} {data} />
|
||||
</Tab>
|
||||
</Tabs>
|
||||
{/if}
|
||||
</section>
|
||||
{/if}
|
||||
</Layout>
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
import { goto } from "@roxi/routify"
|
||||
import { ActionButton, Heading, Spacer, Icon } from "@budibase/bbui"
|
||||
import { ActionButton, Heading } from "@budibase/bbui"
|
||||
import { notifications } from "@budibase/bbui"
|
||||
import Spinner from "components/common/Spinner.svelte"
|
||||
import download from "downloadjs"
|
||||
|
@ -27,7 +27,6 @@
|
|||
|
||||
<div class="apps-card">
|
||||
<Heading s>{name}</Heading>
|
||||
<Spacer medium />
|
||||
<div class="card-footer" data-cy={`app-${name}`}>
|
||||
<ActionButton on:click={() => $goto(`/builder/${_id}`)}>
|
||||
Open
|
||||
|
@ -58,6 +57,7 @@
|
|||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-top: var(--spacing-m);
|
||||
}
|
||||
|
||||
i {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
import { Button, Heading, Body, Spacer } from "@budibase/bbui"
|
||||
import { Button, Heading, Body } from "@budibase/bbui"
|
||||
import AppCard from "./AppCard.svelte"
|
||||
import Spinner from "components/common/Spinner.svelte"
|
||||
import api from "builderStore/api"
|
||||
|
@ -25,7 +25,6 @@
|
|||
{#each templates as template}
|
||||
<div class="templates-card">
|
||||
<Heading black small>{template.name}</Heading>
|
||||
<Spacer small />
|
||||
<Body medium grey>{template.category}</Body>
|
||||
<Body lh small black>{template.description}</Body>
|
||||
<div><img src={template.image} width="100%" /></div>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
import { goto, beforeUrlChange } from "@roxi/routify"
|
||||
import { Button, Heading, Body, Spacer, Divider } from "@budibase/bbui"
|
||||
import { Button, Heading, Body, Divider, Layout } from "@budibase/bbui"
|
||||
import { datasources, integrations, queries } from "stores/backend"
|
||||
import { notifications } from "@budibase/bbui"
|
||||
import IntegrationConfigForm from "components/backend/DatasourceNavigator/TableIntegrationMenu/IntegrationConfigForm.svelte"
|
||||
|
@ -43,39 +43,36 @@
|
|||
|
||||
{#if datasource}
|
||||
<section>
|
||||
<Spacer extraLarge />
|
||||
<header>
|
||||
<svelte:component
|
||||
this={ICONS[datasource.source]}
|
||||
height="26"
|
||||
width="26"
|
||||
/>
|
||||
<Heading m>{datasource.name}</Heading>
|
||||
</header>
|
||||
<Body small grey lh>{integration.description}</Body>
|
||||
<Spacer extraLarge />
|
||||
<Divider />
|
||||
<Spacer extraLarge />
|
||||
<div class="container">
|
||||
<div class="config-header">
|
||||
<Heading s>Configuration</Heading>
|
||||
<Button secondary on:click={saveDatasource}>Save</Button>
|
||||
</div>
|
||||
<Body s>Connect your database to Budibase using the config below.</Body>
|
||||
<Spacer extraLarge />
|
||||
<IntegrationConfigForm
|
||||
schema={integration.datasource}
|
||||
integration={datasource.config}
|
||||
on:change={setUnsaved}
|
||||
/>
|
||||
<Spacer extraLarge />
|
||||
<Layout>
|
||||
<header>
|
||||
<svelte:component
|
||||
this={ICONS[datasource.source]}
|
||||
height="26"
|
||||
width="26"
|
||||
/>
|
||||
<Heading m>{datasource.name}</Heading>
|
||||
</header>
|
||||
<Body small grey lh>{integration.description}</Body>
|
||||
<Divider />
|
||||
<div class="container">
|
||||
<div class="config-header">
|
||||
<Heading s>Configuration</Heading>
|
||||
<Button secondary on:click={saveDatasource}>Save</Button>
|
||||
</div>
|
||||
<Body size="S"
|
||||
>Connect your database to Budibase using the config below.</Body
|
||||
>
|
||||
<IntegrationConfigForm
|
||||
schema={integration.datasource}
|
||||
integration={datasource.config}
|
||||
on:change={setUnsaved}
|
||||
/>
|
||||
</div>
|
||||
<Divider />
|
||||
<Spacer extraLarge />
|
||||
<div class="query-header">
|
||||
<Heading s>Queries</Heading>
|
||||
<Button secondary on:click={() => $goto("./new")}>Add Query</Button>
|
||||
</div>
|
||||
<Spacer extraLarge />
|
||||
<div class="query-list">
|
||||
{#each $queries.list.filter((query) => query.datasourceId === datasource._id) as query}
|
||||
<div class="query-list-item" on:click={() => onClickQuery(query)}>
|
||||
|
@ -85,7 +82,7 @@
|
|||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</Layout>
|
||||
</section>
|
||||
{/if}
|
||||
|
||||
|
@ -109,6 +106,7 @@
|
|||
}
|
||||
|
||||
.container {
|
||||
width: 100%;
|
||||
border-radius: var(--border-radius-m);
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
import AppList from "components/start/AppList.svelte"
|
||||
import { get } from "builderStore/api"
|
||||
import CreateAppModal from "components/start/CreateAppModal.svelte"
|
||||
import { Button, Heading, Modal, Spacer, ButtonGroup } from "@budibase/bbui"
|
||||
import { Button, Heading, Modal, ButtonGroup } from "@budibase/bbui"
|
||||
import TemplateList from "components/start/TemplateList.svelte"
|
||||
import analytics from "analytics"
|
||||
import Banner from "/assets/orange-landscape.png"
|
||||
|
@ -58,11 +58,10 @@
|
|||
<div class="container">
|
||||
<div class="header">
|
||||
<Heading m h1>Welcome to the Budibase Beta</Heading>
|
||||
<div class="button-group">
|
||||
<ButtonGroup>
|
||||
<Button secondary on:click={initiateAppImport}>Import Web App</Button>
|
||||
<Spacer medium />
|
||||
<Button cta on:click={modal.show}>Create New Web App</Button>
|
||||
</div>
|
||||
</ButtonGroup>
|
||||
</div>
|
||||
|
||||
<div class="banner">
|
||||
|
|
Loading…
Reference in New Issue