Merge 35909c7733
into d267addbb1
This commit is contained in:
commit
e2326440a5
Binary file not shown.
After Width: | Height: | Size: 75 KiB |
Binary file not shown.
After Width: | Height: | Size: 18 KiB |
|
@ -3,6 +3,7 @@
|
|||
export let imageSrc
|
||||
export let name
|
||||
export let icon
|
||||
export let description = ""
|
||||
export let overlayEnabled = true
|
||||
|
||||
let imageError = false
|
||||
|
@ -36,7 +37,10 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="template-thumbnail-text">
|
||||
<div>{name}</div>
|
||||
<div class="template-name">{name}</div>
|
||||
{#if description}
|
||||
<div class="template-description">{description}</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -70,19 +74,40 @@
|
|||
position: absolute;
|
||||
bottom: 0px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 30%;
|
||||
flex-direction: column;
|
||||
height: 35%;
|
||||
width: 100%;
|
||||
background-color: var(--spectrum-global-color-gray-50);
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
|
||||
.template-thumbnail-text > div {
|
||||
padding-left: 1.25rem;
|
||||
padding-right: 1.25rem;
|
||||
}
|
||||
|
||||
.template-name {
|
||||
color: var(
|
||||
--spectrum-heading-xs-text-color,
|
||||
var(--spectrum-alias-heading-text-color)
|
||||
);
|
||||
background-color: var(--spectrum-global-color-gray-50);
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
margin-top: 0.75rem;
|
||||
}
|
||||
|
||||
.template-thumbnail-text > div {
|
||||
padding-left: 1rem;
|
||||
padding-right: 1rem;
|
||||
.template-description {
|
||||
color: var(--spectrum-global-color-gray-600);
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
margin-top: 0.5rem;
|
||||
margin-bottom: 0;
|
||||
display: -webkit-box;
|
||||
line-clamp: 2;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.template-card {
|
||||
|
@ -91,7 +116,7 @@
|
|||
border-radius: var(--border-radius-s);
|
||||
border: 1px solid var(--spectrum-global-color-gray-300);
|
||||
overflow: hidden;
|
||||
min-height: 200px;
|
||||
min-height: 220px;
|
||||
}
|
||||
|
||||
.template-card > * {
|
||||
|
@ -112,7 +137,7 @@
|
|||
}
|
||||
|
||||
.card-body {
|
||||
padding-left: 1rem;
|
||||
padding-top: 1rem;
|
||||
padding-left: 1.25rem;
|
||||
padding-top: 1.25rem;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,137 +0,0 @@
|
|||
<script>
|
||||
import { Layout, Detail, Button, Modal } from "@budibase/bbui"
|
||||
import TemplateCard from "@/components/common/TemplateCard.svelte"
|
||||
import CreateAppModal from "@/components/start/CreateAppModal.svelte"
|
||||
import { licensing } from "@/stores/portal"
|
||||
import { Content, SideNav, SideNavItem } from "@/components/portal/page"
|
||||
|
||||
export let templates
|
||||
|
||||
let selectedCategory
|
||||
let creationModal
|
||||
let template
|
||||
|
||||
$: categories = getCategories(templates)
|
||||
$: filteredCategories = getFilteredCategories(categories, selectedCategory)
|
||||
|
||||
const getCategories = templates => {
|
||||
let categories = {}
|
||||
templates?.forEach(template => {
|
||||
if (!categories[template.category]) {
|
||||
categories[template.category] = []
|
||||
}
|
||||
categories[template.category].push(template)
|
||||
})
|
||||
categories = Object.entries(categories).map(
|
||||
([category, categoryTemplates]) => {
|
||||
return {
|
||||
name: category,
|
||||
templates: categoryTemplates,
|
||||
}
|
||||
}
|
||||
)
|
||||
categories.sort((a, b) => {
|
||||
return a.name < b.name ? -1 : 1
|
||||
})
|
||||
return categories
|
||||
}
|
||||
|
||||
const getFilteredCategories = (categories, selectedCategory) => {
|
||||
if (!selectedCategory) {
|
||||
return categories
|
||||
}
|
||||
return categories.filter(x => x.name === selectedCategory)
|
||||
}
|
||||
|
||||
const stopAppCreation = () => {
|
||||
template = null
|
||||
}
|
||||
</script>
|
||||
|
||||
<Content>
|
||||
<div slot="side-nav">
|
||||
<SideNav>
|
||||
<SideNavItem
|
||||
on:click={() => (selectedCategory = null)}
|
||||
text="All"
|
||||
active={selectedCategory == null}
|
||||
/>
|
||||
{#each categories as category}
|
||||
<SideNavItem
|
||||
on:click={() => (selectedCategory = category.name)}
|
||||
text={category.name}
|
||||
active={selectedCategory === category.name}
|
||||
/>
|
||||
{/each}
|
||||
</SideNav>
|
||||
</div>
|
||||
<div class="template-categories">
|
||||
<Layout gap="XL" noPadding>
|
||||
{#each filteredCategories as category}
|
||||
<div class="template-category">
|
||||
<Detail size="M">{category.name}</Detail>
|
||||
<div class="template-grid">
|
||||
{#each category.templates as templateEntry}
|
||||
<TemplateCard
|
||||
name={templateEntry.name}
|
||||
imageSrc={templateEntry.image}
|
||||
backgroundColour={templateEntry.background}
|
||||
icon={templateEntry.icon}
|
||||
>
|
||||
{#if !($licensing?.usageMetrics?.apps >= 100)}
|
||||
<Button
|
||||
cta
|
||||
on:click={() => {
|
||||
template = templateEntry
|
||||
creationModal.show()
|
||||
}}
|
||||
>
|
||||
Use template
|
||||
</Button>
|
||||
{/if}
|
||||
<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>
|
||||
</Content>
|
||||
|
||||
<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>
|
|
@ -134,6 +134,8 @@
|
|||
data.append("templateKey", template.key)
|
||||
}
|
||||
|
||||
data.append("isOnboarding", "false")
|
||||
|
||||
// Create App
|
||||
const createdApp = await API.createApp(data)
|
||||
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
<script lang="ts">
|
||||
import { ModalContent, Layout } from "@budibase/bbui"
|
||||
import TemplateCard from "@/components/common/TemplateCard.svelte"
|
||||
import { templates } from "@/stores/portal"
|
||||
import type { TemplateMetadata } from "@budibase/types"
|
||||
|
||||
export let onSelectTemplate: (_template: TemplateMetadata) => void
|
||||
|
||||
let newTemplates: TemplateMetadata[] = []
|
||||
$: {
|
||||
const templateList = $templates as TemplateMetadata[]
|
||||
newTemplates = templateList?.filter(template => template.new) || []
|
||||
}
|
||||
</script>
|
||||
|
||||
<ModalContent
|
||||
title="Choose a starting template"
|
||||
size="XL"
|
||||
showCancelButton={false}
|
||||
showConfirmButton={false}
|
||||
>
|
||||
<Layout noPadding gap="M">
|
||||
<div class="template-grid">
|
||||
{#each newTemplates as template}
|
||||
<button
|
||||
class="template-wrapper"
|
||||
on:click={() => onSelectTemplate(template)}
|
||||
>
|
||||
<TemplateCard
|
||||
name={template.name}
|
||||
imageSrc={template.image}
|
||||
backgroundColour={template.background}
|
||||
icon={template.icon}
|
||||
description={template.description}
|
||||
/>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
</Layout>
|
||||
</ModalContent>
|
||||
|
||||
<style>
|
||||
.template-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: var(--spacing-l);
|
||||
padding: var(--spacing-m);
|
||||
}
|
||||
|
||||
.template-wrapper {
|
||||
cursor: pointer;
|
||||
transition: transform 0.2s ease;
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 0;
|
||||
text-align: left;
|
||||
font-family: var(--font-sans);
|
||||
}
|
||||
|
||||
.template-wrapper:hover {
|
||||
transform: translateY(-4px);
|
||||
}
|
||||
</style>
|
|
@ -1,69 +0,0 @@
|
|||
<script>
|
||||
import { url } from "@roxi/routify"
|
||||
import FirstAppOnboarding from "./onboarding/index.svelte"
|
||||
import { Layout, Page, Button, Modal } from "@budibase/bbui"
|
||||
import CreateAppModal from "@/components/start/CreateAppModal.svelte"
|
||||
import TemplateDisplay from "@/components/common/TemplateDisplay.svelte"
|
||||
import AppLimitModal from "@/components/portal/licensing/AppLimitModal.svelte"
|
||||
import { appsStore, templates, licensing } from "@/stores/portal"
|
||||
import { Breadcrumbs, Breadcrumb, Header } from "@/components/portal/page"
|
||||
|
||||
let template
|
||||
let creationModal = false
|
||||
let appLimitModal
|
||||
|
||||
const initiateAppCreation = () => {
|
||||
if ($licensing?.usageMetrics?.apps >= 100) {
|
||||
appLimitModal.show()
|
||||
} else {
|
||||
template = null
|
||||
creationModal.show()
|
||||
}
|
||||
}
|
||||
|
||||
const stopAppCreation = () => {
|
||||
template = null
|
||||
}
|
||||
|
||||
const initiateAppImport = () => {
|
||||
if ($licensing?.usageMetrics?.apps >= 100) {
|
||||
appLimitModal.show()
|
||||
} else {
|
||||
template = { fromFile: true }
|
||||
creationModal.show()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if !$appsStore.apps.length}
|
||||
<FirstAppOnboarding />
|
||||
{:else}
|
||||
<Page>
|
||||
<Layout noPadding gap="L">
|
||||
<Breadcrumbs>
|
||||
<Breadcrumb url={$url("./")} text="Apps" />
|
||||
<Breadcrumb text="Create new app" />
|
||||
</Breadcrumbs>
|
||||
<Header title={"Create new app"}>
|
||||
<div slot="buttons">
|
||||
<Button size="M" secondary on:click={initiateAppImport}>
|
||||
Import app
|
||||
</Button>
|
||||
<Button size="M" cta on:click={initiateAppCreation}>
|
||||
Start from scratch
|
||||
</Button>
|
||||
</div>
|
||||
</Header>
|
||||
<TemplateDisplay templates={$templates} />
|
||||
</Layout>
|
||||
</Page>
|
||||
<Modal
|
||||
bind:this={creationModal}
|
||||
padding={false}
|
||||
width="600px"
|
||||
on:hide={stopAppCreation}
|
||||
>
|
||||
<CreateAppModal {template} />
|
||||
</Modal>
|
||||
<AppLimitModal bind:this={appLimitModal} />
|
||||
{/if}
|
|
@ -26,15 +26,18 @@
|
|||
licensing,
|
||||
enrichedApps,
|
||||
sortBy,
|
||||
templates,
|
||||
} from "@/stores/portal"
|
||||
import { goto } from "@roxi/routify"
|
||||
import AppRow from "@/components/start/AppRow.svelte"
|
||||
import Logo from "assets/bb-space-man.svg"
|
||||
import TemplatesModal from "@/components/start/TemplatesModal.svelte"
|
||||
|
||||
let template
|
||||
let creationModal
|
||||
let appLimitModal
|
||||
let accountLockedModal
|
||||
let templatesModal
|
||||
let searchTerm = ""
|
||||
let creatingFromTemplate = false
|
||||
let automationErrors
|
||||
|
@ -91,8 +94,6 @@
|
|||
const initiateAppCreation = async () => {
|
||||
if ($licensing?.usageMetrics?.apps >= 100) {
|
||||
appLimitModal.show()
|
||||
} else if ($appsStore.apps?.length) {
|
||||
$goto("/builder/portal/apps/create")
|
||||
} else {
|
||||
template = null
|
||||
creationModal.show()
|
||||
|
@ -120,6 +121,7 @@
|
|||
data.append("name", appName)
|
||||
data.append("useTemplate", true)
|
||||
data.append("templateKey", template.key)
|
||||
data.append("isOnboarding", "false")
|
||||
|
||||
// Create App
|
||||
const createdApp = await API.createApp(data)
|
||||
|
@ -159,6 +161,12 @@
|
|||
}
|
||||
}
|
||||
|
||||
const handleTemplateSelect = selectedTemplate => {
|
||||
template = selectedTemplate
|
||||
templatesModal.hide()
|
||||
autoCreateApp()
|
||||
}
|
||||
|
||||
onMount(async () => {
|
||||
try {
|
||||
// If the portal is loaded from an external URL with a template param
|
||||
|
@ -170,6 +178,7 @@
|
|||
if (usersLimitLockAction) {
|
||||
usersLimitLockAction()
|
||||
}
|
||||
await templates.load()
|
||||
} catch (error) {
|
||||
notifications.error("Error getting init info")
|
||||
}
|
||||
|
@ -224,21 +233,20 @@
|
|||
>
|
||||
Create new app
|
||||
</Button>
|
||||
{#if $appsStore.apps?.length > 0 && !$admin.offlineMode}
|
||||
|
||||
{#if $appsStore.apps?.length > 0}
|
||||
{#if !$admin.offlineMode}
|
||||
<Button
|
||||
size="M"
|
||||
secondary
|
||||
on:click={usersLimitLockAction || templatesModal.show}
|
||||
>
|
||||
View templates
|
||||
</Button>
|
||||
{/if}
|
||||
<Button
|
||||
size="M"
|
||||
secondary
|
||||
on:click={usersLimitLockAction ||
|
||||
$goto("/builder/portal/apps/templates")}
|
||||
>
|
||||
View templates
|
||||
</Button>
|
||||
{/if}
|
||||
{#if !$appsStore.apps?.length}
|
||||
<Button
|
||||
size="L"
|
||||
quiet
|
||||
secondary
|
||||
on:click={usersLimitLockAction || initiateAppImport}
|
||||
>
|
||||
Import app
|
||||
|
@ -308,6 +316,10 @@
|
|||
<CreateAppModal {template} />
|
||||
</Modal>
|
||||
|
||||
<Modal bind:this={templatesModal}>
|
||||
<TemplatesModal onSelectTemplate={handleTemplateSelect} />
|
||||
</Modal>
|
||||
|
||||
<AppLimitModal bind:this={appLimitModal} />
|
||||
<AccountLockedModal
|
||||
bind:this={accountLockedModal}
|
||||
|
|
|
@ -1,122 +1,47 @@
|
|||
<script>
|
||||
export let name = ""
|
||||
<script lang="ts">
|
||||
import { onMount } from "svelte"
|
||||
|
||||
const rows = [
|
||||
{
|
||||
firstName: "Julie",
|
||||
lastName: "Jimenez",
|
||||
email: "julie.jimenez@example.com",
|
||||
address: "4250 New Street",
|
||||
city: "Stevenage",
|
||||
postcode: "EE32 3SE",
|
||||
phone: "01754 13523",
|
||||
},
|
||||
{
|
||||
firstName: "Mandy",
|
||||
lastName: "Clark",
|
||||
email: "mandy.clark@example.com",
|
||||
address: "8632 North Street",
|
||||
city: "Hereford",
|
||||
postcode: "GT81 7DG",
|
||||
phone: "016973 32814",
|
||||
},
|
||||
{
|
||||
firstName: "Holly",
|
||||
lastName: "Carroll",
|
||||
email: "holly.carroll@example.com",
|
||||
address: "5976 Springfield Road",
|
||||
city: "Edinburgh",
|
||||
postcode: "Y4 2LH",
|
||||
phone: "016977 73053",
|
||||
},
|
||||
{
|
||||
firstName: "Francis",
|
||||
lastName: "Castro",
|
||||
email: "francis.castro@example.com",
|
||||
address: "3970 High Street",
|
||||
city: "Wells",
|
||||
postcode: "X12 6QA",
|
||||
phone: "017684 23551",
|
||||
},
|
||||
]
|
||||
let container: HTMLDivElement
|
||||
let panel: HTMLImageElement
|
||||
let panelWidth: number = 0
|
||||
let containerWidth: number = 0
|
||||
let isPanelCutOff: boolean = false
|
||||
|
||||
// side panel screenshot is overlaid over grid screenshot
|
||||
// it should be anchored to the right until the screen is too narrow
|
||||
function checkPanelVisibility(): void {
|
||||
if (container && panel) {
|
||||
containerWidth = container.clientWidth
|
||||
panelWidth = panel.clientWidth
|
||||
|
||||
isPanelCutOff = panelWidth > containerWidth
|
||||
}
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
checkPanelVisibility()
|
||||
window.addEventListener("resize", checkPanelVisibility)
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("resize", checkPanelVisibility)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<div tabindex="-1" class="exampleApp">
|
||||
<div class="page">
|
||||
<div class="header">
|
||||
<img alt="Budibase Logo" src={"/builder/bblogo.png"} />
|
||||
<h1>{name}</h1>
|
||||
</div>
|
||||
<div class="nav">Home</div>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>FIRST NAME</th>
|
||||
<th>LAST NAME</th>
|
||||
<th>EMAIL</th>
|
||||
<th>ADDRESS</th>
|
||||
<th>CITY</th>
|
||||
<th>POSTCODE</th>
|
||||
<th>PHONE</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each rows as row}
|
||||
<tr>
|
||||
{#each Object.values(row) as value}
|
||||
<td>{value}</td>
|
||||
{/each}
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="sidePanel">
|
||||
<h2>{rows[0].firstName}</h2>
|
||||
<div class="field">
|
||||
<label for="exampleLastName">lastName</label>
|
||||
<input
|
||||
id="exampleLastName"
|
||||
tabIndex="-1"
|
||||
readonly
|
||||
value={rows[0].lastName}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label for="exampleEmail">Email</label>
|
||||
<input id="exampleEmail" tabIndex="-1" readonly value={rows[0].email} />
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label for="exampleAddress">Address</label>
|
||||
<input
|
||||
id="exampleAddress"
|
||||
tabIndex="-1"
|
||||
readonly
|
||||
value={rows[0].address}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label for="exampleCity">City</label>
|
||||
<input id="exampleCity" tabIndex="-1" readonly value={rows[0].city} />
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label for="examplePostcode">Postcode</label>
|
||||
<input
|
||||
id="examplePostcode"
|
||||
tabIndex="-1"
|
||||
readonly
|
||||
value={rows[0].postcode}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label for="examplePhone">Phone</label>
|
||||
<input id="examplePhone" tabIndex="-1" readonly value={rows[0].phone} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="mockupContainer" bind:this={container}>
|
||||
<img
|
||||
class="baseScreen"
|
||||
alt="Base app screen"
|
||||
src={"/builder/onboarding/grid.png"}
|
||||
/>
|
||||
<img
|
||||
class="overlayPanel"
|
||||
class:leftAnchored={isPanelCutOff}
|
||||
bind:this={panel}
|
||||
alt="Side panel overlay"
|
||||
src={"/builder/onboarding/sidebar.png"}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -124,119 +49,44 @@
|
|||
.exampleApp {
|
||||
box-sizing: border-box;
|
||||
height: 100vh;
|
||||
padding: 100px 0 100px 100px;
|
||||
--text: #191919;
|
||||
--lightText: #303030;
|
||||
--extraLightText: #646464;
|
||||
--backgroundLight: #ffffff;
|
||||
--background: #f5f5f5;
|
||||
--tableBorder: 1px solid #e6e6e6;
|
||||
padding: 100px 0 100px 5vw;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.page {
|
||||
overflow: hidden;
|
||||
@media (max-width: 980px) {
|
||||
.exampleApp {
|
||||
padding-left: 2vw;
|
||||
}
|
||||
}
|
||||
|
||||
.mockupContainer {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
background-color: var(--background);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.header {
|
||||
background-color: var(--backgroundLight);
|
||||
display: flex;
|
||||
padding: 32px 0 20px 32px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.header img {
|
||||
height: 36px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
margin: 0;
|
||||
font-weight: 600;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.nav {
|
||||
background-color: var(--backgroundLight);
|
||||
padding: 20px 0 20px 32px;
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
border-bottom: 1px solid #d0d0d0;
|
||||
}
|
||||
|
||||
table {
|
||||
margin: 32px;
|
||||
border: var(--tableBorder);
|
||||
border-collapse: collapse;
|
||||
min-width: 100%;
|
||||
}
|
||||
|
||||
thead {
|
||||
border-bottom: var(--tableBorder);
|
||||
}
|
||||
|
||||
thead th {
|
||||
font-family: "Source Sans Pro";
|
||||
color: var(--lightText);
|
||||
white-space: nowrap;
|
||||
padding: 12px;
|
||||
font-weight: 600;
|
||||
font-size: 12px;
|
||||
text-align: left;
|
||||
min-width: 70px;
|
||||
}
|
||||
|
||||
tbody td {
|
||||
border-bottom: 1px solid #e6e6e6;
|
||||
background-color: var(--backgroundLight);
|
||||
padding: 12px;
|
||||
color: var(--extraLightText);
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.sidePanel {
|
||||
position: absolute;
|
||||
width: 300px;
|
||||
background-color: var(--backgroundLight);
|
||||
box-shadow: 0px 4px 25px 0px #00000040;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
right: -364px;
|
||||
padding: 42px 32px;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.sidePanel h2 {
|
||||
margin: 0;
|
||||
font-weight: 600;
|
||||
font-size: 22px;
|
||||
margin-bottom: 35px;
|
||||
}
|
||||
|
||||
.field {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.field label {
|
||||
font-weight: 500;
|
||||
font-size: 12px;
|
||||
color: #b0b0b0;
|
||||
width: 65px;
|
||||
}
|
||||
|
||||
.field input {
|
||||
border: 1px solid #d0d0d0;
|
||||
overflow: hidden;
|
||||
background: #f5f5f5;
|
||||
border-radius: 4px;
|
||||
color: var(--lightText);
|
||||
padding: 7.5px 12px;
|
||||
font-size: 13px;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.baseScreen {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
object-position: left;
|
||||
}
|
||||
|
||||
.overlayPanel {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
height: 100%;
|
||||
width: auto;
|
||||
object-fit: cover;
|
||||
box-shadow: -4px 0 25px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.overlayPanel.leftAnchored {
|
||||
right: auto;
|
||||
left: 0;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
data.append("name", name.trim())
|
||||
data.append("url", url.trim())
|
||||
data.append("useTemplate", false)
|
||||
data.append("isOnboarding", "true")
|
||||
|
||||
const createdApp = await API.createApp(data)
|
||||
|
||||
|
@ -56,7 +57,7 @@
|
|||
<SplitPage>
|
||||
<NamePanel bind:name bind:url disabled={loading} onNext={handleCreateApp} />
|
||||
<div slot="right">
|
||||
<ExampleApp {name} />
|
||||
<ExampleApp />
|
||||
</div>
|
||||
</SplitPage>
|
||||
</div>
|
||||
|
|
|
@ -1,18 +0,0 @@
|
|||
<script>
|
||||
import { url } from "@roxi/routify"
|
||||
import { Layout, Page } from "@budibase/bbui"
|
||||
import TemplateDisplay from "@/components/common/TemplateDisplay.svelte"
|
||||
import { templates } from "@/stores/portal"
|
||||
import { Breadcrumbs, Breadcrumb, Header } from "@/components/portal/page"
|
||||
</script>
|
||||
|
||||
<Page>
|
||||
<Layout noPadding gap="L">
|
||||
<Breadcrumbs>
|
||||
<Breadcrumb url={$url("./")} text="Apps" />
|
||||
<Breadcrumb text="Templates" />
|
||||
</Breadcrumbs>
|
||||
<Header title="Templates" />
|
||||
<TemplateDisplay templates={$templates} />
|
||||
</Layout>
|
||||
</Page>
|
|
@ -289,6 +289,13 @@ async function performAppCreate(
|
|||
const { body } = ctx.request
|
||||
const { name, url, encryptionPassword, templateKey } = body
|
||||
|
||||
let isOnboarding = false
|
||||
if (typeof body.isOnboarding === "string") {
|
||||
isOnboarding = body.isOnboarding === "true"
|
||||
} else if (typeof body.isOnboarding === "boolean") {
|
||||
isOnboarding = body.isOnboarding
|
||||
}
|
||||
|
||||
let useTemplate = false
|
||||
if (typeof body.useTemplate === "string") {
|
||||
useTemplate = body.useTemplate === "true"
|
||||
|
@ -322,7 +329,7 @@ async function performAppCreate(
|
|||
const instance = await createInstance(appId, instanceConfig)
|
||||
const db = context.getAppDB()
|
||||
const isImport = !!instanceConfig.file
|
||||
const addSampleData = !isImport && !useTemplate
|
||||
const addSampleData = isOnboarding && !isImport && !useTemplate
|
||||
|
||||
if (instanceConfig.useTemplate && !instanceConfig.file) {
|
||||
await updateUserColumns(appId, db, ctx.user._id!)
|
||||
|
|
|
@ -15,6 +15,7 @@ export interface CreateAppRequest {
|
|||
fileToImport?: string
|
||||
encryptionPassword?: string
|
||||
file?: { path: string }
|
||||
isOnboarding?: string
|
||||
}
|
||||
|
||||
export interface CreateAppResponse extends App {}
|
||||
|
|
|
@ -12,6 +12,7 @@ export interface TemplateMetadata {
|
|||
type: TemplateType
|
||||
key: string
|
||||
image: string
|
||||
new: boolean
|
||||
}
|
||||
|
||||
export type FetchTemplateResponse = TemplateMetadata[]
|
||||
|
|
Loading…
Reference in New Issue