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 imageSrc
|
||||||
export let name
|
export let name
|
||||||
export let icon
|
export let icon
|
||||||
|
export let description = ""
|
||||||
export let overlayEnabled = true
|
export let overlayEnabled = true
|
||||||
|
|
||||||
let imageError = false
|
let imageError = false
|
||||||
|
@ -36,7 +37,10 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="template-thumbnail-text">
|
<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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -70,19 +74,40 @@
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 0px;
|
bottom: 0px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
flex-direction: column;
|
||||||
height: 30%;
|
height: 35%;
|
||||||
width: 100%;
|
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(
|
color: var(
|
||||||
--spectrum-heading-xs-text-color,
|
--spectrum-heading-xs-text-color,
|
||||||
var(--spectrum-alias-heading-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 {
|
.template-description {
|
||||||
padding-left: 1rem;
|
color: var(--spectrum-global-color-gray-600);
|
||||||
padding-right: 1rem;
|
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 {
|
.template-card {
|
||||||
|
@ -91,7 +116,7 @@
|
||||||
border-radius: var(--border-radius-s);
|
border-radius: var(--border-radius-s);
|
||||||
border: 1px solid var(--spectrum-global-color-gray-300);
|
border: 1px solid var(--spectrum-global-color-gray-300);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
min-height: 200px;
|
min-height: 220px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.template-card > * {
|
.template-card > * {
|
||||||
|
@ -112,7 +137,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-body {
|
.card-body {
|
||||||
padding-left: 1rem;
|
padding-left: 1.25rem;
|
||||||
padding-top: 1rem;
|
padding-top: 1.25rem;
|
||||||
}
|
}
|
||||||
</style>
|
</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("templateKey", template.key)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data.append("isOnboarding", "false")
|
||||||
|
|
||||||
// Create App
|
// Create App
|
||||||
const createdApp = await API.createApp(data)
|
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,
|
licensing,
|
||||||
enrichedApps,
|
enrichedApps,
|
||||||
sortBy,
|
sortBy,
|
||||||
|
templates,
|
||||||
} from "@/stores/portal"
|
} from "@/stores/portal"
|
||||||
import { goto } from "@roxi/routify"
|
import { goto } from "@roxi/routify"
|
||||||
import AppRow from "@/components/start/AppRow.svelte"
|
import AppRow from "@/components/start/AppRow.svelte"
|
||||||
import Logo from "assets/bb-space-man.svg"
|
import Logo from "assets/bb-space-man.svg"
|
||||||
|
import TemplatesModal from "@/components/start/TemplatesModal.svelte"
|
||||||
|
|
||||||
let template
|
let template
|
||||||
let creationModal
|
let creationModal
|
||||||
let appLimitModal
|
let appLimitModal
|
||||||
let accountLockedModal
|
let accountLockedModal
|
||||||
|
let templatesModal
|
||||||
let searchTerm = ""
|
let searchTerm = ""
|
||||||
let creatingFromTemplate = false
|
let creatingFromTemplate = false
|
||||||
let automationErrors
|
let automationErrors
|
||||||
|
@ -91,8 +94,6 @@
|
||||||
const initiateAppCreation = async () => {
|
const initiateAppCreation = async () => {
|
||||||
if ($licensing?.usageMetrics?.apps >= 100) {
|
if ($licensing?.usageMetrics?.apps >= 100) {
|
||||||
appLimitModal.show()
|
appLimitModal.show()
|
||||||
} else if ($appsStore.apps?.length) {
|
|
||||||
$goto("/builder/portal/apps/create")
|
|
||||||
} else {
|
} else {
|
||||||
template = null
|
template = null
|
||||||
creationModal.show()
|
creationModal.show()
|
||||||
|
@ -120,6 +121,7 @@
|
||||||
data.append("name", appName)
|
data.append("name", appName)
|
||||||
data.append("useTemplate", true)
|
data.append("useTemplate", true)
|
||||||
data.append("templateKey", template.key)
|
data.append("templateKey", template.key)
|
||||||
|
data.append("isOnboarding", "false")
|
||||||
|
|
||||||
// Create App
|
// Create App
|
||||||
const createdApp = await API.createApp(data)
|
const createdApp = await API.createApp(data)
|
||||||
|
@ -159,6 +161,12 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleTemplateSelect = selectedTemplate => {
|
||||||
|
template = selectedTemplate
|
||||||
|
templatesModal.hide()
|
||||||
|
autoCreateApp()
|
||||||
|
}
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
try {
|
try {
|
||||||
// If the portal is loaded from an external URL with a template param
|
// If the portal is loaded from an external URL with a template param
|
||||||
|
@ -170,6 +178,7 @@
|
||||||
if (usersLimitLockAction) {
|
if (usersLimitLockAction) {
|
||||||
usersLimitLockAction()
|
usersLimitLockAction()
|
||||||
}
|
}
|
||||||
|
await templates.load()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
notifications.error("Error getting init info")
|
notifications.error("Error getting init info")
|
||||||
}
|
}
|
||||||
|
@ -224,20 +233,19 @@
|
||||||
>
|
>
|
||||||
Create new app
|
Create new app
|
||||||
</Button>
|
</Button>
|
||||||
{#if $appsStore.apps?.length > 0 && !$admin.offlineMode}
|
|
||||||
|
{#if $appsStore.apps?.length > 0}
|
||||||
|
{#if !$admin.offlineMode}
|
||||||
<Button
|
<Button
|
||||||
size="M"
|
size="M"
|
||||||
secondary
|
secondary
|
||||||
on:click={usersLimitLockAction ||
|
on:click={usersLimitLockAction || templatesModal.show}
|
||||||
$goto("/builder/portal/apps/templates")}
|
|
||||||
>
|
>
|
||||||
View templates
|
View templates
|
||||||
</Button>
|
</Button>
|
||||||
{/if}
|
{/if}
|
||||||
{#if !$appsStore.apps?.length}
|
|
||||||
<Button
|
<Button
|
||||||
size="L"
|
size="M"
|
||||||
quiet
|
|
||||||
secondary
|
secondary
|
||||||
on:click={usersLimitLockAction || initiateAppImport}
|
on:click={usersLimitLockAction || initiateAppImport}
|
||||||
>
|
>
|
||||||
|
@ -308,6 +316,10 @@
|
||||||
<CreateAppModal {template} />
|
<CreateAppModal {template} />
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
|
<Modal bind:this={templatesModal}>
|
||||||
|
<TemplatesModal onSelectTemplate={handleTemplateSelect} />
|
||||||
|
</Modal>
|
||||||
|
|
||||||
<AppLimitModal bind:this={appLimitModal} />
|
<AppLimitModal bind:this={appLimitModal} />
|
||||||
<AccountLockedModal
|
<AccountLockedModal
|
||||||
bind:this={accountLockedModal}
|
bind:this={accountLockedModal}
|
||||||
|
|
|
@ -1,242 +1,92 @@
|
||||||
<script>
|
<script lang="ts">
|
||||||
export let name = ""
|
import { onMount } from "svelte"
|
||||||
|
|
||||||
const rows = [
|
let container: HTMLDivElement
|
||||||
{
|
let panel: HTMLImageElement
|
||||||
firstName: "Julie",
|
let panelWidth: number = 0
|
||||||
lastName: "Jimenez",
|
let containerWidth: number = 0
|
||||||
email: "julie.jimenez@example.com",
|
let isPanelCutOff: boolean = false
|
||||||
address: "4250 New Street",
|
|
||||||
city: "Stevenage",
|
// side panel screenshot is overlaid over grid screenshot
|
||||||
postcode: "EE32 3SE",
|
// it should be anchored to the right until the screen is too narrow
|
||||||
phone: "01754 13523",
|
function checkPanelVisibility(): void {
|
||||||
},
|
if (container && panel) {
|
||||||
{
|
containerWidth = container.clientWidth
|
||||||
firstName: "Mandy",
|
panelWidth = panel.clientWidth
|
||||||
lastName: "Clark",
|
|
||||||
email: "mandy.clark@example.com",
|
isPanelCutOff = panelWidth > containerWidth
|
||||||
address: "8632 North Street",
|
}
|
||||||
city: "Hereford",
|
}
|
||||||
postcode: "GT81 7DG",
|
|
||||||
phone: "016973 32814",
|
onMount(() => {
|
||||||
},
|
checkPanelVisibility()
|
||||||
{
|
window.addEventListener("resize", checkPanelVisibility)
|
||||||
firstName: "Holly",
|
|
||||||
lastName: "Carroll",
|
return () => {
|
||||||
email: "holly.carroll@example.com",
|
window.removeEventListener("resize", checkPanelVisibility)
|
||||||
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",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div tabindex="-1" class="exampleApp">
|
<div tabindex="-1" class="exampleApp">
|
||||||
<div class="page">
|
<div class="mockupContainer" bind:this={container}>
|
||||||
<div class="header">
|
<img
|
||||||
<img alt="Budibase Logo" src={"/builder/bblogo.png"} />
|
class="baseScreen"
|
||||||
<h1>{name}</h1>
|
alt="Base app screen"
|
||||||
</div>
|
src={"/builder/onboarding/grid.png"}
|
||||||
<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>
|
<img
|
||||||
|
class="overlayPanel"
|
||||||
<div class="field">
|
class:leftAnchored={isPanelCutOff}
|
||||||
<label for="exampleEmail">Email</label>
|
bind:this={panel}
|
||||||
<input id="exampleEmail" tabIndex="-1" readonly value={rows[0].email} />
|
alt="Side panel overlay"
|
||||||
</div>
|
src={"/builder/onboarding/sidebar.png"}
|
||||||
|
|
||||||
<div class="field">
|
|
||||||
<label for="exampleAddress">Address</label>
|
|
||||||
<input
|
|
||||||
id="exampleAddress"
|
|
||||||
tabIndex="-1"
|
|
||||||
readonly
|
|
||||||
value={rows[0].address}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</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>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.exampleApp {
|
.exampleApp {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
padding: 100px 0 100px 100px;
|
padding: 100px 0 100px 5vw;
|
||||||
--text: #191919;
|
|
||||||
--lightText: #303030;
|
|
||||||
--extraLightText: #646464;
|
|
||||||
--backgroundLight: #ffffff;
|
|
||||||
--background: #f5f5f5;
|
|
||||||
--tableBorder: 1px solid #e6e6e6;
|
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.page {
|
@media (max-width: 980px) {
|
||||||
overflow: hidden;
|
.exampleApp {
|
||||||
|
padding-left: 2vw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mockupContainer {
|
||||||
position: relative;
|
position: relative;
|
||||||
height: 100%;
|
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%;
|
width: 100%;
|
||||||
align-items: center;
|
overflow: hidden;
|
||||||
margin-bottom: 20px;
|
background: #f5f5f5;
|
||||||
}
|
|
||||||
|
|
||||||
.field label {
|
|
||||||
font-weight: 500;
|
|
||||||
font-size: 12px;
|
|
||||||
color: #b0b0b0;
|
|
||||||
width: 65px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.field input {
|
|
||||||
border: 1px solid #d0d0d0;
|
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
color: var(--lightText);
|
}
|
||||||
padding: 7.5px 12px;
|
|
||||||
font-size: 13px;
|
.baseScreen {
|
||||||
flex-grow: 1;
|
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>
|
</style>
|
||||||
|
|
|
@ -22,6 +22,7 @@
|
||||||
data.append("name", name.trim())
|
data.append("name", name.trim())
|
||||||
data.append("url", url.trim())
|
data.append("url", url.trim())
|
||||||
data.append("useTemplate", false)
|
data.append("useTemplate", false)
|
||||||
|
data.append("isOnboarding", "true")
|
||||||
|
|
||||||
const createdApp = await API.createApp(data)
|
const createdApp = await API.createApp(data)
|
||||||
|
|
||||||
|
@ -56,7 +57,7 @@
|
||||||
<SplitPage>
|
<SplitPage>
|
||||||
<NamePanel bind:name bind:url disabled={loading} onNext={handleCreateApp} />
|
<NamePanel bind:name bind:url disabled={loading} onNext={handleCreateApp} />
|
||||||
<div slot="right">
|
<div slot="right">
|
||||||
<ExampleApp {name} />
|
<ExampleApp />
|
||||||
</div>
|
</div>
|
||||||
</SplitPage>
|
</SplitPage>
|
||||||
</div>
|
</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 { body } = ctx.request
|
||||||
const { name, url, encryptionPassword, templateKey } = body
|
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
|
let useTemplate = false
|
||||||
if (typeof body.useTemplate === "string") {
|
if (typeof body.useTemplate === "string") {
|
||||||
useTemplate = body.useTemplate === "true"
|
useTemplate = body.useTemplate === "true"
|
||||||
|
@ -322,7 +329,7 @@ async function performAppCreate(
|
||||||
const instance = await createInstance(appId, instanceConfig)
|
const instance = await createInstance(appId, instanceConfig)
|
||||||
const db = context.getAppDB()
|
const db = context.getAppDB()
|
||||||
const isImport = !!instanceConfig.file
|
const isImport = !!instanceConfig.file
|
||||||
const addSampleData = !isImport && !useTemplate
|
const addSampleData = isOnboarding && !isImport && !useTemplate
|
||||||
|
|
||||||
if (instanceConfig.useTemplate && !instanceConfig.file) {
|
if (instanceConfig.useTemplate && !instanceConfig.file) {
|
||||||
await updateUserColumns(appId, db, ctx.user._id!)
|
await updateUserColumns(appId, db, ctx.user._id!)
|
||||||
|
|
|
@ -15,6 +15,7 @@ export interface CreateAppRequest {
|
||||||
fileToImport?: string
|
fileToImport?: string
|
||||||
encryptionPassword?: string
|
encryptionPassword?: string
|
||||||
file?: { path: string }
|
file?: { path: string }
|
||||||
|
isOnboarding?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CreateAppResponse extends App {}
|
export interface CreateAppResponse extends App {}
|
||||||
|
|
|
@ -12,6 +12,7 @@ export interface TemplateMetadata {
|
||||||
type: TemplateType
|
type: TemplateType
|
||||||
key: string
|
key: string
|
||||||
image: string
|
image: string
|
||||||
|
new: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export type FetchTemplateResponse = TemplateMetadata[]
|
export type FetchTemplateResponse = TemplateMetadata[]
|
||||||
|
|
Loading…
Reference in New Issue