Merge pull request #2896 from Budibase/feature/onboarding-templates

Feature/onboarding templates
This commit is contained in:
Martin McKeaveney 2021-10-06 14:50:12 +01:00 committed by GitHub
commit 3cb088e930
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 121 additions and 1073 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 167 KiB

View File

@ -30,7 +30,7 @@ Cypress.Commands.add("login", () => {
Cypress.Commands.add("createApp", name => {
cy.visit(`localhost:${Cypress.env("PORT")}/builder`)
cy.wait(500)
cy.contains(/Create (new )?app/).click()
cy.contains(/Start from scratch/).click()
cy.get(".spectrum-Modal")
.within(() => {
cy.get("input").eq(0).type(name).should("have.value", name).blur()

View File

@ -17,6 +17,7 @@
import { capitalise } from "helpers"
import { goto } from "@roxi/routify"
import { APP_NAME_REGEX } from "constants"
import TemplateList from "./TemplateList.svelte"
export let template
@ -31,12 +32,16 @@
APP_NAME_REGEX,
"App name must be letters, numbers and spaces only"
),
file: template ? mixed().required("Please choose a file to import") : null,
file: template?.fromFile
? mixed().required("Please choose a file to import")
: null,
}
let submitting = false
let valid = false
$: checkValidity($values, validator)
$: showTemplateSelection = !template?.fromFile && !template?.key
onMount(async () => {
await hostingStore.actions.fetchDeployedApps()
@ -73,7 +78,7 @@
submitting = true
// Check a template exists if we are important
if (template && !$values.file) {
if (template?.fromFile && !$values.file) {
$errors.file = "Please choose a file to import"
valid = false
submitting = false
@ -133,33 +138,59 @@
}
</script>
<ModalContent
title={template ? "Import app" : "Create app"}
confirmText={template ? "Import app" : "Create app"}
onConfirm={createNewApp}
disabled={!valid}
>
{#if template}
<Dropzone
error={$touched.file && $errors.file}
gallery={false}
label="File to import"
value={[$values.file]}
on:change={e => {
$values.file = e.detail?.[0]
$touched.file = true
{#if showTemplateSelection}
<ModalContent
title={"Get started quickly"}
showConfirmButton={false}
size="L"
onConfirm={() => {
showTemplateSelection = false
return false
}}
showCancelButton={false}
showCloseIcon={false}
>
<Body size="M">Select a template below, or start from scratch.</Body>
<TemplateList
onSelect={selected => {
if (!selected) {
showTemplateSelection = false
return
}
template = selected
}}
/>
{/if}
<Body size="S">
Give your new app a name, and choose which groups have access (paid plans
only).
</Body>
<Input
bind:value={$values.name}
error={$touched.name && $errors.name}
on:blur={() => ($touched.name = true)}
label="Name"
/>
<Checkbox label="Group access" disabled value={true} text="All users" />
</ModalContent>
</ModalContent>
{:else}
<ModalContent
title={template?.fromFile ? "Import app" : "Create app"}
confirmText={template?.fromFile ? "Import app" : "Create app"}
onConfirm={createNewApp}
disabled={!valid}
>
{#if template?.fromFile}
<Dropzone
error={$touched.file && $errors.file}
gallery={false}
label="File to import"
value={[$values.file]}
on:change={e => {
$values.file = e.detail?.[0]
$touched.file = true
}}
/>
{/if}
<Body size="S">
Give your new app a name, and choose which groups have access (paid plans
only).
</Body>
<Input
bind:value={$values.name}
error={$touched.name && $errors.name}
on:blur={() => ($touched.name = true)}
label="Name"
/>
<Checkbox label="Group access" disabled value={true} text="All users" />
</ModalContent>
{/if}

View File

@ -1,5 +1,5 @@
<script>
import { Button, Heading, Body } from "@budibase/bbui"
import { Heading, Layout, Icon } from "@budibase/bbui"
import Spinner from "components/common/Spinner.svelte"
import api from "builderStore/api"
@ -13,8 +13,7 @@
let templatesPromise = fetchTemplates()
</script>
<div class="root">
<Heading size="M">Start With a Template</Heading>
<Layout gap="XS" noPadding>
{#await templatesPromise}
<div class="spinner-container">
<Spinner size="30" />
@ -22,41 +21,69 @@
{:then templates}
<div class="templates">
{#each templates as template}
<div class="templates-card">
<Heading size="S">{template.name}</Heading>
<Body size="M" grey>{template.category}</Body>
<Body size="S" black>{template.description}</Body>
<div><img alt="template" src={template.image} width="100%" /></div>
<div class="card-footer">
<Button secondary on:click={() => onSelect(template)}>
Create
{template.name}
</Button>
<div class="template" on:click={() => onSelect(template)}>
<div
class="background-icon"
style={`background: ${template.background};`}
>
<Icon name={template.icon} />
</div>
<Heading size="XS">{template.name}</Heading>
<p class="detail">{template?.category?.toUpperCase()}</p>
</div>
{/each}
<div class="template start-from-scratch" on:click={() => onSelect(null)}>
<div class="background-icon" style={`background: var(--background);`}>
<Icon name="Add" />
</div>
<Heading size="XS">Start from scratch</Heading>
<p class="detail">BLANK</p>
</div>
</div>
{:catch err}
<h1 style="color:red">{err}</h1>
{/await}
</div>
</Layout>
<style>
.templates {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
grid-gap: var(--layout-m);
width: 100%;
grid-gap: var(--spacing-m);
grid-template-columns: 1fr;
justify-content: start;
margin-top: 15px;
}
.templates-card {
background-color: var(--background);
padding: var(--spacing-xl);
border-radius: var(--border-radius-m);
border: var(--border-dark);
.background-icon {
padding: 10px;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
width: 18px;
color: white;
}
.card-footer {
margin-top: var(--spacing-m);
.template {
height: 60px;
display: grid;
grid-gap: var(--layout-m);
grid-template-columns: 5% 1fr 15%;
border: 1px solid #494949;
align-items: center;
cursor: pointer;
border-radius: 4px;
background: #1a1a1a;
padding: 8px 16px;
}
.detail {
text-align: right;
}
.start-from-scratch {
background: var(--spectrum-global-color-gray-50);
margin-top: 20px;
}
</style>

View File

@ -159,8 +159,6 @@
cursor: pointer;
filter: brightness(110%);
}
.group {
}
.app {
display: grid;
grid-template-columns: auto 1fr auto;

View File

@ -8,10 +8,8 @@
ButtonGroup,
Select,
Modal,
ModalContent,
Page,
notifications,
Body,
Search,
} from "@budibase/bbui"
import CreateAppModal from "components/start/CreateAppModal.svelte"
@ -270,22 +268,7 @@
{#if !enrichedApps.length && !creatingApp && loaded}
<div class="empty-wrapper">
<Modal inline>
<ModalContent
title="Create your first app"
confirmText="Create app"
showCancelButton={false}
showCloseIcon={false}
onConfirm={initiateAppCreation}
size="M"
>
<div slot="footer">
<Button on:click={initiateAppImport} secondary>Import app</Button>
</div>
<Body size="S">
The purpose of the Budibase builder is to help you build beautiful,
powerful applications quickly and easily.
</Body>
</ModalContent>
<CreateAppModal {template} />
</Modal>
</div>
{/if}

File diff suppressed because it is too large Load Diff

View File

@ -76,8 +76,9 @@ exports.getTemplateStream = async template => {
if (template.file) {
return fs.createReadStream(template.file.path)
} else {
const tmpPath = await exports.downloadTemplate(...template.key.split("/"))
return fs.createReadStream(join(tmpPath, "db", "dump.txt"))
const [type, name] = template.key.split("/")
const tmpPath = await exports.downloadTemplate(type, name)
return fs.createReadStream(join(tmpPath, name, "db", "dump.txt"))
}
}