Initial commit of home screen modifications and template browsing
This commit is contained in:
parent
d2e7b28304
commit
6c269bf091
|
@ -0,0 +1,124 @@
|
|||
<script>
|
||||
export let backgroundColour
|
||||
export let imageSrc
|
||||
export let name
|
||||
export let icon
|
||||
export let overlayEnabled = true
|
||||
|
||||
let imageError = false
|
||||
let imageLoaded = false
|
||||
|
||||
const imageRenderError = () => {
|
||||
imageError = true
|
||||
}
|
||||
|
||||
const imageLoadSuccess = () => {
|
||||
imageLoaded = true
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="template-card" style="background-color:{backgroundColour};">
|
||||
<div class="template-thumbnail card-body">
|
||||
<img
|
||||
alt={name}
|
||||
src={imageSrc}
|
||||
on:error={imageRenderError}
|
||||
on:load={imageLoadSuccess}
|
||||
class={`${imageLoaded ? "loaded" : ""}`}
|
||||
/>
|
||||
<div style={`display:${imageError ? "block" : "none"}`}>
|
||||
<svg
|
||||
width="26px"
|
||||
height="26px"
|
||||
class="spectrum-Icon"
|
||||
style="color: white"
|
||||
focusable="false"
|
||||
>
|
||||
<use xlink:href="#spectrum-icon-18-{icon}" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class={overlayEnabled ? "template-thumbnail-action-overlay" : ""}>
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
<div class="template-thumbnail-text">
|
||||
<div>{name}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
|
||||
.template-thumbnail {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.template-card:hover .template-thumbnail-action-overlay {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.template-thumbnail-action-overlay {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
width: 100%;
|
||||
height: 70%;
|
||||
display: none;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
border-top-right-radius: inherit;
|
||||
border-top-left-radius: inherit;
|
||||
}
|
||||
|
||||
.template-thumbnail-text {
|
||||
position: absolute;
|
||||
bottom: 0px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 30%;
|
||||
width: 100%;
|
||||
color: var(
|
||||
--spectrum-heading-xs-text-color,
|
||||
var(--spectrum-alias-heading-text-color)
|
||||
);
|
||||
background-color: var(--spectrum-global-color-gray-50);
|
||||
}
|
||||
|
||||
.template-thumbnail-text > div {
|
||||
padding-left: 1rem;
|
||||
padding-right: 1rem;
|
||||
}
|
||||
|
||||
.template-card {
|
||||
position: relative;
|
||||
display: flex;
|
||||
border-radius: var(--border-radius-s);
|
||||
border: 1px solid var(--spectrum-global-color-gray-300);
|
||||
overflow: hidden;
|
||||
min-height: 200px;
|
||||
}
|
||||
|
||||
.template-card > *{
|
||||
width : 100%
|
||||
}
|
||||
|
||||
.template-card img.loaded {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.template-card img {
|
||||
display: none;
|
||||
max-width: 100%;
|
||||
border-radius: var(--border-radius-s) 0px var(--border-radius-s) 0px;
|
||||
}
|
||||
|
||||
.template-card:hover {
|
||||
background: var(--spectrum-alias-background-color-tertiary);
|
||||
}
|
||||
|
||||
.card-body {
|
||||
padding-left: 1rem;
|
||||
padding-top: 1rem;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,147 @@
|
|||
<script>
|
||||
import { Layout, Detail, Heading, Button, Modal } from "@budibase/bbui"
|
||||
import TemplateCard from "components/common/TemplateCard.svelte"
|
||||
import CreateAppModal from "components/start/CreateAppModal.svelte"
|
||||
|
||||
export let templates
|
||||
|
||||
let selectedTemplateCategory
|
||||
let creationModal
|
||||
let template
|
||||
|
||||
const groupTemplatesByCategory = (templates, categoryFilter) => {
|
||||
let grouped = templates.reduce((acc, template) => {
|
||||
if (
|
||||
typeof categoryFilter === "string" &&
|
||||
[categoryFilter].indexOf(template.category) < 0
|
||||
) {
|
||||
return acc
|
||||
}
|
||||
|
||||
acc[template.category] = !acc[template.category]
|
||||
? []
|
||||
: acc[template.category]
|
||||
acc[template.category].push(template)
|
||||
|
||||
return acc
|
||||
}, {})
|
||||
return grouped
|
||||
}
|
||||
|
||||
$: filteredTemplates = groupTemplatesByCategory(
|
||||
templates,
|
||||
selectedTemplateCategory
|
||||
)
|
||||
|
||||
$: filteredTemplateCategories = filteredTemplates
|
||||
? Object.keys(filteredTemplates).sort()
|
||||
: []
|
||||
|
||||
$: templateCategories = templates
|
||||
? Object.keys(groupTemplatesByCategory(templates)).sort()
|
||||
: []
|
||||
|
||||
const stopAppCreation = () => {
|
||||
template = null
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="template-category-filters">
|
||||
<Layout noPadding gap="S">
|
||||
<Heading size="S">Templates</Heading>
|
||||
|
||||
<div class="template-category-filters spectrum-ActionGroup">
|
||||
<button
|
||||
on:click={() => {
|
||||
selectedTemplateCategory = null
|
||||
}}
|
||||
class="template-category-filter-all template-category-filter spectrum-ActionButton spectrum-ActionButton--sizeM
|
||||
spectrum-ActionGroup-item {!selectedTemplateCategory ||
|
||||
'is-selected'}"
|
||||
>
|
||||
<span class="spectrum-ActionButton-label">All</span>
|
||||
</button>
|
||||
|
||||
{#each templateCategories as templateCategoryKey}
|
||||
<button
|
||||
on:click={() => {
|
||||
selectedTemplateCategory = templateCategoryKey
|
||||
}}
|
||||
class="template-category-filter spectrum-ActionButton spectrum-ActionButton--sizeM
|
||||
spectrum-ActionGroup-item {templateCategoryKey ==
|
||||
selectedTemplateCategory || 'is-selected'}"
|
||||
>
|
||||
<span class="spectrum-ActionButton-label">{templateCategoryKey}</span>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
</Layout>
|
||||
</div>
|
||||
|
||||
<div class="template-categories">
|
||||
<Layout gap="XL" noPadding>
|
||||
{#each filteredTemplateCategories as templateCategoryKey}
|
||||
<div class="template-category">
|
||||
<Detail size="M">{templateCategoryKey}</Detail>
|
||||
<div class="template-grid">
|
||||
{#each filteredTemplates[templateCategoryKey] as templateEntry}
|
||||
<TemplateCard
|
||||
name={templateEntry.name}
|
||||
imageSrc={templateEntry.image}
|
||||
backgroundColour={templateEntry.background}
|
||||
icon={templateEntry.icon}
|
||||
>
|
||||
<Button
|
||||
cta
|
||||
on:click={() => {
|
||||
template = templateEntry
|
||||
creationModal.show()
|
||||
}}
|
||||
>
|
||||
Use template
|
||||
</Button>
|
||||
<a
|
||||
href={templateEntry.url}
|
||||
target="_blank"
|
||||
class="overlay-preview-link spectrum-Button spectrum-Button--sizeM spectrum-Button--secondary"
|
||||
on:click|stopPropagation
|
||||
>
|
||||
Details
|
||||
</a>
|
||||
</TemplateCard>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</Layout>
|
||||
</div>
|
||||
|
||||
<Modal
|
||||
bind:this={creationModal}
|
||||
padding={false}
|
||||
width="600px"
|
||||
on:hide={stopAppCreation}
|
||||
>
|
||||
<CreateAppModal {template} />
|
||||
</Modal>
|
||||
|
||||
<style>
|
||||
.template-grid {
|
||||
padding-top: 10px;
|
||||
display: grid;
|
||||
grid-gap: var(--spacing-xl);
|
||||
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
|
||||
}
|
||||
|
||||
a:hover.spectrum-Button.spectrum-Button--secondary.overlay-preview-link {
|
||||
background-color: #c8c8c8;
|
||||
border-color: #c8c8c8;
|
||||
color: #505050;
|
||||
}
|
||||
|
||||
a.spectrum-Button--secondary.overlay-preview-link {
|
||||
margin-top: 20px;
|
||||
border-color: #c8c8c8;
|
||||
color: #c8c8c8;
|
||||
}
|
||||
</style>
|
|
@ -9,6 +9,7 @@
|
|||
import { goto } from "@roxi/routify"
|
||||
import { createValidationStore } from "helpers/validation/yup"
|
||||
import * as appValidation from "helpers/validation/yup/app"
|
||||
import TemplateCard from "components/common/TemplateCard.svelte"
|
||||
|
||||
export let template
|
||||
|
||||
|
@ -17,9 +18,30 @@
|
|||
$: validation.check($values)
|
||||
|
||||
onMount(async () => {
|
||||
$values.url = resolveAppUrl(template, $values.name, $values.url)
|
||||
$values.name = resolveAppName(template, $values.name)
|
||||
await setupValidation()
|
||||
})
|
||||
|
||||
$: appUrl = `${window.location.origin}${
|
||||
$values.url ? $values.url : `/${resolveAppUrl(template, $values.name)}`
|
||||
}`
|
||||
|
||||
const resolveAppUrl = (template, name) => {
|
||||
let parsedName
|
||||
const resolvedName = resolveAppName(template, name)
|
||||
parsedName = resolvedName ? resolvedName.toLowerCase() : ""
|
||||
const parsedUrl = parsedName ? parsedName.replace(/\s+/g, "-") : ""
|
||||
return encodeURI(parsedUrl)
|
||||
}
|
||||
|
||||
const resolveAppName = (template, name) => {
|
||||
if (template && !name) {
|
||||
return template.name
|
||||
}
|
||||
return name.trim()
|
||||
}
|
||||
|
||||
const setupValidation = async () => {
|
||||
const applications = svelteGet(apps)
|
||||
appValidation.name(validation, { apps: applications })
|
||||
|
@ -83,6 +105,15 @@
|
|||
onConfirm={createNewApp}
|
||||
disabled={!$validation.valid}
|
||||
>
|
||||
{#if template && !template?.fromFile}
|
||||
<TemplateCard
|
||||
name={template.name}
|
||||
imageSrc={template.image}
|
||||
backgroundColour={template.background}
|
||||
overlayEnabled={false}
|
||||
icon={template.icon}
|
||||
/>
|
||||
{/if}
|
||||
{#if template?.fromFile}
|
||||
<Dropzone
|
||||
error={$validation.touched.file && $validation.errors.file}
|
||||
|
@ -104,13 +135,38 @@
|
|||
? `${$auth.user.firstName}s app`
|
||||
: "My app"}
|
||||
/>
|
||||
<span>
|
||||
<Input
|
||||
bind:value={$values.url}
|
||||
error={$validation.touched.url && $validation.errors.url}
|
||||
on:blur={() => ($validation.touched.url = true)}
|
||||
label="URL"
|
||||
placeholder={$values.name
|
||||
? "/" + encodeURIComponent($values.name).toLowerCase()
|
||||
: "/"}
|
||||
placeholder={$values.url
|
||||
? $values.url
|
||||
: `/${resolveAppUrl(template, $values.name)}`}
|
||||
/>
|
||||
{#if $values.name}
|
||||
<div class="app-server-wrap" title={appUrl}>
|
||||
<span class="app-server-prefix">
|
||||
{window.location.origin}
|
||||
</span>
|
||||
{$values.url
|
||||
? $values.url
|
||||
: `/${resolveAppUrl(template, $values.name)}`}
|
||||
</div>
|
||||
{/if}
|
||||
</span>
|
||||
</ModalContent>
|
||||
|
||||
<style>
|
||||
.app-server-prefix {
|
||||
color: var(--spectrum-global-color-gray-500);
|
||||
}
|
||||
.app-server-wrap {
|
||||
margin-top: 10px;
|
||||
width: 320px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -0,0 +1,141 @@
|
|||
<script>
|
||||
import { goto } from "@roxi/routify"
|
||||
import {
|
||||
Layout,
|
||||
Page,
|
||||
notifications,
|
||||
Button,
|
||||
Heading,
|
||||
Body,
|
||||
Modal,
|
||||
Divider,
|
||||
} from "@budibase/bbui"
|
||||
import CreateAppModal from "components/start/CreateAppModal.svelte"
|
||||
import TemplateDisplay from "components/common/TemplateDisplay.svelte"
|
||||
import { onMount } from "svelte"
|
||||
import { templates } from "stores/portal"
|
||||
|
||||
let loaded = false
|
||||
let template
|
||||
let creationModal = false
|
||||
let creatingApp = false
|
||||
|
||||
const welcomeBody =
|
||||
"Start from scratch or get a head start with one of our templates"
|
||||
const createAppTitle = "Create new app"
|
||||
const createAppButtonText = "Start from scratch"
|
||||
|
||||
onMount(async () => {
|
||||
try {
|
||||
await templates.load()
|
||||
if ($templates?.length === 0) {
|
||||
notifications.error(
|
||||
"There was a problem loading quick start templates."
|
||||
)
|
||||
}
|
||||
} catch (error) {
|
||||
notifications.error("Error loading apps and templates")
|
||||
}
|
||||
loaded = true
|
||||
})
|
||||
|
||||
const initiateAppCreation = () => {
|
||||
template = null
|
||||
creationModal.show()
|
||||
creatingApp = true
|
||||
}
|
||||
|
||||
const stopAppCreation = () => {
|
||||
template = null
|
||||
creatingApp = false
|
||||
}
|
||||
|
||||
const initiateAppImport = () => {
|
||||
template = { fromFile: true }
|
||||
creationModal.show()
|
||||
creatingApp = true
|
||||
}
|
||||
</script>
|
||||
|
||||
<Page wide>
|
||||
<Layout noPadding gap="XL">
|
||||
<span>
|
||||
<Button
|
||||
primary
|
||||
on:click={() => {
|
||||
$goto("../")
|
||||
}}
|
||||
>
|
||||
Back
|
||||
</Button>
|
||||
</span>
|
||||
|
||||
<div class="title">
|
||||
<div class="welcome">
|
||||
<Layout noPadding gap="XS">
|
||||
<Heading size="M">{createAppTitle}</Heading>
|
||||
<Body size="S">
|
||||
{welcomeBody}
|
||||
</Body>
|
||||
</Layout>
|
||||
|
||||
<div class="buttons">
|
||||
<Button size="L" icon="Add" cta on:click={initiateAppCreation}>
|
||||
{createAppButtonText}
|
||||
</Button>
|
||||
<Button
|
||||
icon="Import"
|
||||
size="L"
|
||||
quiet
|
||||
secondary
|
||||
on:click={initiateAppImport}
|
||||
>
|
||||
Import app
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<Divider size="S" />
|
||||
</div>
|
||||
|
||||
{#if loaded && $templates?.length}
|
||||
<TemplateDisplay templates={$templates} />
|
||||
{/if}
|
||||
</Layout>
|
||||
</Page>
|
||||
|
||||
<Modal
|
||||
bind:this={creationModal}
|
||||
padding={false}
|
||||
width="600px"
|
||||
on:hide={stopAppCreation}
|
||||
>
|
||||
<CreateAppModal {template} />
|
||||
</Modal>
|
||||
|
||||
<style>
|
||||
.title .welcome > .buttons {
|
||||
padding-top: 30px;
|
||||
}
|
||||
.title {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: var(--spacing-xl);
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.buttons {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
gap: var(--spacing-xl);
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
@media (max-width: 640px) {
|
||||
.buttons {
|
||||
flex-direction: row-reverse;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -11,8 +11,9 @@
|
|||
notifications,
|
||||
Body,
|
||||
Search,
|
||||
Icon,
|
||||
Divider,
|
||||
} from "@budibase/bbui"
|
||||
import TemplateDisplay from "components/common/TemplateDisplay.svelte"
|
||||
import Spinner from "components/common/Spinner.svelte"
|
||||
import CreateAppModal from "components/start/CreateAppModal.svelte"
|
||||
import UpdateAppModal from "components/start/UpdateAppModal.svelte"
|
||||
|
@ -45,6 +46,21 @@
|
|||
let appName = ""
|
||||
let creatingFromTemplate = false
|
||||
|
||||
const resolveWelcomeMessage = (auth, apps) => {
|
||||
const userWelcome = auth?.user?.firstName
|
||||
? `Welcome ${auth?.user?.firstName}!`
|
||||
: "Welcome back!"
|
||||
return apps?.length ? userWelcome : "Let's create your first app!"
|
||||
}
|
||||
$: welcomeHeader = resolveWelcomeMessage($auth, $apps)
|
||||
$: welcomeBody = $apps?.length
|
||||
? "Manage your apps and get a head start with templates"
|
||||
: "Start from scratch or get a head start with one of our templates"
|
||||
|
||||
$: createAppButtonText = $apps?.length
|
||||
? "Create new app"
|
||||
: "Start from scratch"
|
||||
|
||||
$: enrichedApps = enrichApps($apps, $auth.user, sortBy)
|
||||
$: filteredApps = enrichedApps.filter(app =>
|
||||
app?.name?.toLowerCase().includes(searchTerm.toLowerCase())
|
||||
|
@ -79,10 +95,14 @@
|
|||
}
|
||||
|
||||
const initiateAppCreation = () => {
|
||||
if ($apps?.length) {
|
||||
$goto("/builder/portal/apps/create")
|
||||
} else {
|
||||
template = null
|
||||
creationModal.show()
|
||||
creatingApp = true
|
||||
}
|
||||
}
|
||||
|
||||
const initiateAppsExport = () => {
|
||||
try {
|
||||
|
@ -268,26 +288,32 @@
|
|||
|
||||
<Page wide>
|
||||
<Layout noPadding gap="XL">
|
||||
{#if loaded}
|
||||
<div class="title">
|
||||
<div class="welcome">
|
||||
<Layout noPadding gap="XS">
|
||||
<Heading size="M">Welcome to Budibase</Heading>
|
||||
<Heading size="M">{welcomeHeader}</Heading>
|
||||
<Body size="S">
|
||||
Manage your apps and get a head start with templates
|
||||
{welcomeBody}
|
||||
</Body>
|
||||
</Layout>
|
||||
|
||||
<div class="buttons">
|
||||
{#if cloud}
|
||||
<Button size="L" icon="Add" cta on:click={initiateAppCreation}>
|
||||
{createAppButtonText}
|
||||
</Button>
|
||||
{#if $apps?.length > 0}
|
||||
<Button
|
||||
icon="Experience"
|
||||
size="L"
|
||||
icon="Export"
|
||||
quiet
|
||||
secondary
|
||||
on:click={initiateAppsExport}
|
||||
on:click={$goto("/builder/portal/apps/templates")}
|
||||
>
|
||||
Export apps
|
||||
Templates
|
||||
</Button>
|
||||
{/if}
|
||||
{#if !$apps?.length}
|
||||
<Button
|
||||
icon="Import"
|
||||
size="L"
|
||||
|
@ -297,60 +323,38 @@
|
|||
>
|
||||
Import app
|
||||
</Button>
|
||||
<Button size="L" icon="Add" cta on:click={initiateAppCreation}>
|
||||
Create app
|
||||
</Button>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Layout noPadding gap="S">
|
||||
<Detail size="L">Quick start templates</Detail>
|
||||
<div class="grid">
|
||||
{#each $templates as item}
|
||||
<div
|
||||
on:click={() => {
|
||||
template = item
|
||||
creationModal.show()
|
||||
creatingApp = true
|
||||
}}
|
||||
class="template-card"
|
||||
>
|
||||
<a
|
||||
href={item.url}
|
||||
target="_blank"
|
||||
class="external-link"
|
||||
on:click|stopPropagation
|
||||
>
|
||||
<Icon name="LinkOut" size="S" />
|
||||
</a>
|
||||
<div class="card-body">
|
||||
<div style="color: {item.background}" class="iconAlign">
|
||||
<svg
|
||||
width="26px"
|
||||
height="26px"
|
||||
class="spectrum-Icon"
|
||||
style="color:{item.background};"
|
||||
focusable="false"
|
||||
>
|
||||
<use xlink:href="#spectrum-icon-18-{item.icon}" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="iconAlign">
|
||||
<Body weight="900" size="S">{item.name}</Body>
|
||||
<div style="font-size: 10px;">
|
||||
<Body size="S">{item.category.toUpperCase()}</Body>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
<div>
|
||||
<Layout gap="S" justifyItems="center">
|
||||
<img class="img-logo img-size" alt="logo" src={Logo} />
|
||||
</Layout>
|
||||
</div>
|
||||
<Divider size="S" />
|
||||
</div>
|
||||
|
||||
{#if loaded && enrichedApps.length}
|
||||
{#if !$apps?.length && $templates?.length}
|
||||
<TemplateDisplay templates={$templates} />
|
||||
{/if}
|
||||
|
||||
{#if enrichedApps.length}
|
||||
<Layout noPadding gap="S">
|
||||
<div class="title">
|
||||
<Detail size="L">My apps</Detail>
|
||||
{#if enrichedApps.length > 1}
|
||||
<div class="app-actions">
|
||||
{#if cloud}
|
||||
<Button
|
||||
size="M"
|
||||
icon="Export"
|
||||
quiet
|
||||
secondary
|
||||
on:click={initiateAppsExport}
|
||||
>
|
||||
Export apps
|
||||
</Button>
|
||||
{/if}
|
||||
<div class="filter">
|
||||
<Select
|
||||
quiet
|
||||
|
@ -366,6 +370,8 @@
|
|||
<Search placeholder="Search" bind:value={searchTerm} />
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="appTable">
|
||||
{#each filteredApps as app (app.appId)}
|
||||
|
@ -384,32 +390,11 @@
|
|||
</div>
|
||||
</Layout>
|
||||
{/if}
|
||||
|
||||
{#if !enrichedApps.length && !creatingApp && loaded}
|
||||
<div class="empty-wrapper">
|
||||
<div class="centered">
|
||||
<div class="main">
|
||||
<Layout gap="S" justifyItems="center">
|
||||
<img class="img-size" alt="logo" src={Logo} />
|
||||
<div class="new-screen-text">
|
||||
<Detail size="M">Create a business app in minutes!</Detail>
|
||||
</div>
|
||||
<Button on:click={() => initiateAppCreation()} size="M" cta>
|
||||
<div class="new-screen-button">
|
||||
<div class="background-icon" style="color: white;">
|
||||
<Icon name="Add" />
|
||||
</div>
|
||||
Create App
|
||||
</div></Button
|
||||
>
|
||||
</Layout>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if creatingFromTemplate}
|
||||
<div class="empty-wrapper">
|
||||
<img class="img-logo img-size" alt="logo" src={Logo} />
|
||||
<p>Creating your Budibase app from your selected template...</p>
|
||||
<Spinner size="10" />
|
||||
</div>
|
||||
|
@ -459,6 +444,15 @@
|
|||
<ChooseIconModal app={selectedApp} bind:this={iconModal} />
|
||||
|
||||
<style>
|
||||
.app-actions {
|
||||
display: flex;
|
||||
}
|
||||
.app-actions :global(> button) {
|
||||
margin-right: 10px
|
||||
}
|
||||
.title .welcome > .buttons {
|
||||
padding-top: 30px;
|
||||
}
|
||||
.title {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
@ -475,13 +469,11 @@
|
|||
gap: var(--spacing-xl);
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
@media (max-width: 640px) {
|
||||
.buttons {
|
||||
flex-direction: row-reverse;
|
||||
justify-content: flex-end;
|
||||
@media (max-width: 1000px) {
|
||||
.img-logo {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.filter {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
@ -489,49 +481,6 @@
|
|||
align-items: center;
|
||||
gap: var(--spacing-xl);
|
||||
}
|
||||
|
||||
.grid {
|
||||
height: 200px;
|
||||
display: grid;
|
||||
overflow: hidden;
|
||||
grid-gap: var(--spacing-xl);
|
||||
grid-template-columns: repeat(auto-fill, minmax(270px, 1fr));
|
||||
grid-template-rows: minmax(70px, 1fr) minmax(100px, 1fr) minmax(0px, 0);
|
||||
}
|
||||
.template-card {
|
||||
height: 70px;
|
||||
border-radius: var(--border-radius-s);
|
||||
border: 1px solid var(--spectrum-global-color-gray-300);
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.template-card:hover {
|
||||
background: var(--spectrum-alias-background-color-tertiary);
|
||||
}
|
||||
.card-body {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.external-link {
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
right: 5px;
|
||||
color: var(--spectrum-global-color-gray-300);
|
||||
z-index: 99;
|
||||
}
|
||||
.external-link:hover {
|
||||
color: var(--spectrum-global-color-gray-500);
|
||||
}
|
||||
|
||||
.iconAlign {
|
||||
padding: 0 0 0 var(--spacing-m);
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.appTable {
|
||||
display: grid;
|
||||
grid-template-rows: auto;
|
||||
|
@ -557,7 +506,6 @@
|
|||
grid-template-columns: 1fr auto;
|
||||
}
|
||||
}
|
||||
|
||||
.empty-wrapper {
|
||||
flex: 1 1 auto;
|
||||
height: 100%;
|
||||
|
@ -566,42 +514,8 @@
|
|||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.centered {
|
||||
width: calc(100% - 350px);
|
||||
height: calc(100% - 100px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.main {
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
.new-screen-text {
|
||||
width: 160px;
|
||||
text-align: center;
|
||||
color: #2c2c2c;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.new-screen-button {
|
||||
margin-left: 5px;
|
||||
height: 20px;
|
||||
width: 100px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.img-size {
|
||||
width: 160px;
|
||||
height: 160px;
|
||||
}
|
||||
|
||||
.background-icon {
|
||||
margin-top: 4px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
<script>
|
||||
import { goto } from "@roxi/routify"
|
||||
import { Layout, Page, notifications, Button } from "@budibase/bbui"
|
||||
import TemplateDisplay from "components/common/TemplateDisplay.svelte"
|
||||
import { onMount } from "svelte"
|
||||
import { templates } from "stores/portal"
|
||||
|
||||
let loaded = false
|
||||
|
||||
onMount(async () => {
|
||||
try {
|
||||
await templates.load()
|
||||
if ($templates?.length === 0) {
|
||||
notifications.error(
|
||||
"There was a problem loading quick start templates."
|
||||
)
|
||||
}
|
||||
} catch (error) {
|
||||
notifications.error("Error loading apps and templates")
|
||||
}
|
||||
loaded = true
|
||||
})
|
||||
</script>
|
||||
|
||||
<Page wide>
|
||||
<Layout noPadding gap="XL">
|
||||
<span>
|
||||
<Button
|
||||
primary
|
||||
on:click={() => {
|
||||
$goto("../")
|
||||
}}
|
||||
>
|
||||
Back
|
||||
</Button>
|
||||
</span>
|
||||
{#if loaded && $templates?.length}
|
||||
<TemplateDisplay templates={$templates} />
|
||||
{/if}
|
||||
</Layout>
|
||||
</Page>
|
Loading…
Reference in New Issue