Merge pull request #2896 from Budibase/feature/onboarding-templates
Feature/onboarding templates
This commit is contained in:
commit
3cb088e930
Binary file not shown.
After Width: | Height: | Size: 167 KiB |
|
@ -30,7 +30,7 @@ Cypress.Commands.add("login", () => {
|
||||||
Cypress.Commands.add("createApp", name => {
|
Cypress.Commands.add("createApp", name => {
|
||||||
cy.visit(`localhost:${Cypress.env("PORT")}/builder`)
|
cy.visit(`localhost:${Cypress.env("PORT")}/builder`)
|
||||||
cy.wait(500)
|
cy.wait(500)
|
||||||
cy.contains(/Create (new )?app/).click()
|
cy.contains(/Start from scratch/).click()
|
||||||
cy.get(".spectrum-Modal")
|
cy.get(".spectrum-Modal")
|
||||||
.within(() => {
|
.within(() => {
|
||||||
cy.get("input").eq(0).type(name).should("have.value", name).blur()
|
cy.get("input").eq(0).type(name).should("have.value", name).blur()
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
import { capitalise } from "helpers"
|
import { capitalise } from "helpers"
|
||||||
import { goto } from "@roxi/routify"
|
import { goto } from "@roxi/routify"
|
||||||
import { APP_NAME_REGEX } from "constants"
|
import { APP_NAME_REGEX } from "constants"
|
||||||
|
import TemplateList from "./TemplateList.svelte"
|
||||||
|
|
||||||
export let template
|
export let template
|
||||||
|
|
||||||
|
@ -31,12 +32,16 @@
|
||||||
APP_NAME_REGEX,
|
APP_NAME_REGEX,
|
||||||
"App name must be letters, numbers and spaces only"
|
"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 submitting = false
|
||||||
let valid = false
|
let valid = false
|
||||||
|
|
||||||
$: checkValidity($values, validator)
|
$: checkValidity($values, validator)
|
||||||
|
$: showTemplateSelection = !template?.fromFile && !template?.key
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
await hostingStore.actions.fetchDeployedApps()
|
await hostingStore.actions.fetchDeployedApps()
|
||||||
|
@ -73,7 +78,7 @@
|
||||||
submitting = true
|
submitting = true
|
||||||
|
|
||||||
// Check a template exists if we are important
|
// 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"
|
$errors.file = "Please choose a file to import"
|
||||||
valid = false
|
valid = false
|
||||||
submitting = false
|
submitting = false
|
||||||
|
@ -133,13 +138,38 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
{#if showTemplateSelection}
|
||||||
<ModalContent
|
<ModalContent
|
||||||
title={template ? "Import app" : "Create app"}
|
title={"Get started quickly"}
|
||||||
confirmText={template ? "Import app" : "Create app"}
|
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
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</ModalContent>
|
||||||
|
{:else}
|
||||||
|
<ModalContent
|
||||||
|
title={template?.fromFile ? "Import app" : "Create app"}
|
||||||
|
confirmText={template?.fromFile ? "Import app" : "Create app"}
|
||||||
onConfirm={createNewApp}
|
onConfirm={createNewApp}
|
||||||
disabled={!valid}
|
disabled={!valid}
|
||||||
>
|
>
|
||||||
{#if template}
|
{#if template?.fromFile}
|
||||||
<Dropzone
|
<Dropzone
|
||||||
error={$touched.file && $errors.file}
|
error={$touched.file && $errors.file}
|
||||||
gallery={false}
|
gallery={false}
|
||||||
|
@ -163,3 +193,4 @@
|
||||||
/>
|
/>
|
||||||
<Checkbox label="Group access" disabled value={true} text="All users" />
|
<Checkbox label="Group access" disabled value={true} text="All users" />
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
|
{/if}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import { Button, Heading, Body } from "@budibase/bbui"
|
import { Heading, Layout, Icon } from "@budibase/bbui"
|
||||||
import Spinner from "components/common/Spinner.svelte"
|
import Spinner from "components/common/Spinner.svelte"
|
||||||
import api from "builderStore/api"
|
import api from "builderStore/api"
|
||||||
|
|
||||||
|
@ -13,8 +13,7 @@
|
||||||
let templatesPromise = fetchTemplates()
|
let templatesPromise = fetchTemplates()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="root">
|
<Layout gap="XS" noPadding>
|
||||||
<Heading size="M">Start With a Template</Heading>
|
|
||||||
{#await templatesPromise}
|
{#await templatesPromise}
|
||||||
<div class="spinner-container">
|
<div class="spinner-container">
|
||||||
<Spinner size="30" />
|
<Spinner size="30" />
|
||||||
|
@ -22,41 +21,69 @@
|
||||||
{:then templates}
|
{:then templates}
|
||||||
<div class="templates">
|
<div class="templates">
|
||||||
{#each templates as template}
|
{#each templates as template}
|
||||||
<div class="templates-card">
|
<div class="template" on:click={() => onSelect(template)}>
|
||||||
<Heading size="S">{template.name}</Heading>
|
<div
|
||||||
<Body size="M" grey>{template.category}</Body>
|
class="background-icon"
|
||||||
<Body size="S" black>{template.description}</Body>
|
style={`background: ${template.background};`}
|
||||||
<div><img alt="template" src={template.image} width="100%" /></div>
|
>
|
||||||
<div class="card-footer">
|
<Icon name={template.icon} />
|
||||||
<Button secondary on:click={() => onSelect(template)}>
|
|
||||||
Create
|
|
||||||
{template.name}
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
|
<Heading size="XS">{template.name}</Heading>
|
||||||
|
<p class="detail">{template?.category?.toUpperCase()}</p>
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/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>
|
</div>
|
||||||
{:catch err}
|
{:catch err}
|
||||||
<h1 style="color:red">{err}</h1>
|
<h1 style="color:red">{err}</h1>
|
||||||
{/await}
|
{/await}
|
||||||
</div>
|
</Layout>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.templates {
|
.templates {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
width: 100%;
|
||||||
grid-gap: var(--layout-m);
|
grid-gap: var(--spacing-m);
|
||||||
|
grid-template-columns: 1fr;
|
||||||
justify-content: start;
|
justify-content: start;
|
||||||
|
margin-top: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.templates-card {
|
.background-icon {
|
||||||
background-color: var(--background);
|
padding: 10px;
|
||||||
padding: var(--spacing-xl);
|
border-radius: 4px;
|
||||||
border-radius: var(--border-radius-m);
|
display: flex;
|
||||||
border: var(--border-dark);
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 18px;
|
||||||
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-footer {
|
.template {
|
||||||
margin-top: var(--spacing-m);
|
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>
|
</style>
|
||||||
|
|
|
@ -159,8 +159,6 @@
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
filter: brightness(110%);
|
filter: brightness(110%);
|
||||||
}
|
}
|
||||||
.group {
|
|
||||||
}
|
|
||||||
.app {
|
.app {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: auto 1fr auto;
|
grid-template-columns: auto 1fr auto;
|
||||||
|
|
|
@ -8,10 +8,8 @@
|
||||||
ButtonGroup,
|
ButtonGroup,
|
||||||
Select,
|
Select,
|
||||||
Modal,
|
Modal,
|
||||||
ModalContent,
|
|
||||||
Page,
|
Page,
|
||||||
notifications,
|
notifications,
|
||||||
Body,
|
|
||||||
Search,
|
Search,
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
import CreateAppModal from "components/start/CreateAppModal.svelte"
|
import CreateAppModal from "components/start/CreateAppModal.svelte"
|
||||||
|
@ -270,22 +268,7 @@
|
||||||
{#if !enrichedApps.length && !creatingApp && loaded}
|
{#if !enrichedApps.length && !creatingApp && loaded}
|
||||||
<div class="empty-wrapper">
|
<div class="empty-wrapper">
|
||||||
<Modal inline>
|
<Modal inline>
|
||||||
<ModalContent
|
<CreateAppModal {template} />
|
||||||
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>
|
|
||||||
</Modal>
|
</Modal>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -76,8 +76,9 @@ exports.getTemplateStream = async template => {
|
||||||
if (template.file) {
|
if (template.file) {
|
||||||
return fs.createReadStream(template.file.path)
|
return fs.createReadStream(template.file.path)
|
||||||
} else {
|
} else {
|
||||||
const tmpPath = await exports.downloadTemplate(...template.key.split("/"))
|
const [type, name] = template.key.split("/")
|
||||||
return fs.createReadStream(join(tmpPath, "db", "dump.txt"))
|
const tmpPath = await exports.downloadTemplate(type, name)
|
||||||
|
return fs.createReadStream(join(tmpPath, name, "db", "dump.txt"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue