New App Onboarding (#9489)

* New App Onboarding

* Lint

* Move app creation onboarding into its own route

* Fix quiet action button variant

* Fix alt attribute background image flashing

* Update routing logic and redirects to show app creation onboarding

* Navigate to data rather than design upon initial app creation to allow tour to function properly

* Tidy up popover logic and remove tip functionality

* Fix binding popovers

* Lint

* Silence spammy warnings from the builder

* Exclude SVG files from rollup plugin image to fix spectrum icons

* Fix help menu icon colours not working in light themes

* Tweak help menu styles

---------

Co-authored-by: Andrew Kingston <andrew@kingston.dev>
This commit is contained in:
Gerard Burns 2023-01-31 19:34:32 +00:00 committed by GitHub
parent 9f3cb5934b
commit dd343a5946
23 changed files with 830 additions and 119 deletions

View File

@ -86,7 +86,7 @@
margin-left: 0;
transition: color ease-out 130ms;
}
.is-selected:not(.spectrum-ActionButton--emphasized) {
.is-selected:not(.spectrum-ActionButton--emphasized):not(.spectrum-ActionButton--quiet) {
background: var(--spectrum-global-color-gray-300);
border-color: var(--spectrum-global-color-gray-700);
}

View File

@ -16,10 +16,10 @@ export default function positionDropdown(
top: null,
}
let popoverLeftPad = 20
// Determine vertical styles
if (window.innerHeight - anchorBounds.bottom < 100) {
if (align === "right-outside") {
styles.top = anchorBounds.top
} else if (window.innerHeight - anchorBounds.bottom < 100) {
styles.top = anchorBounds.top - elementBounds.height - 5
} else {
styles.top = anchorBounds.bottom + 5
@ -34,15 +34,9 @@ export default function positionDropdown(
styles.minWidth = anchorBounds.width
}
if (align === "right") {
let left =
anchorBounds.left + anchorBounds.width / 2 - elementBounds.width
// Accommodate margin on popover: 1.25rem; ~20px
if (left + elementBounds.width + popoverLeftPad > window.innerWidth) {
left -= 20
}
styles.left = left
} else if (align === "right-side") {
styles.left = anchorBounds.left + anchorBounds.width
styles.left = anchorBounds.left + anchorBounds.width - elementBounds.width
} else if (align === "right-outside") {
styles.left = anchorBounds.right + 10
} else {
styles.left = anchorBounds.left
}

View File

@ -247,10 +247,6 @@
width: 100%;
}
.no-variables-height {
height: 100px;
}
.no-variables-text {
padding: var(--spacing-m);
color: var(--spectrum-global-color-gray-600);

View File

@ -15,15 +15,10 @@
export let portalTarget
export let dataCy
export let maxWidth
export let direction = "bottom"
export let showTip = false
export let open = false
export let useAnchorWidth = false
export let dismissible = true
$: tooltipClasses = showTip
? `spectrum-Popover--withTip spectrum-Popover--${direction}`
: ""
$: target = portalTarget || getContext(Context.PopoverRoot) || ".spectrum"
export const show = () => {
@ -62,29 +57,31 @@
</script>
{#if open}
<Portal {target}>
<div
tabindex="0"
use:positionDropdown={{
anchor,
align,
maxWidth,
useAnchorWidth,
showTip: false,
}}
use:clickOutside={{
callback: dismissible ? handleOutsideClick : () => {},
anchor,
}}
on:keydown={handleEscape}
class={"spectrum-Popover is-open " + (tooltipClasses || "")}
role="presentation"
data-cy={dataCy}
transition:fly|local={{ y: -20, duration: 200 }}
>
<slot />
</div>
</Portal>
{#key anchor}
<Portal {target}>
<div
tabindex="0"
use:positionDropdown={{
anchor,
align,
maxWidth,
useAnchorWidth,
showTip: false,
}}
use:clickOutside={{
callback: dismissible ? handleOutsideClick : () => {},
anchor,
}}
on:keydown={handleEscape}
class="spectrum-Popover is-open"
role="presentation"
data-cy={dataCy}
transition:fly|local={{ y: -20, duration: 200 }}
>
<slot />
</div>
</Portal>
{/key}
{/if}
<style>
@ -93,13 +90,4 @@
border-color: var(--spectrum-global-color-gray-300);
overflow: auto;
}
.spectrum-Popover.is-open.spectrum-Popover--withTip {
margin-top: var(--spacing-xs);
margin-left: var(--spacing-xl);
}
:global(.spectrum-Popover--bottom .spectrum-Popover-tip),
:global(.spectrum-Popover--top .spectrum-Popover-tip) {
left: 90%;
margin-left: calc(var(--spectrum-global-dimension-size-150) * -1);
}
</style>

View File

@ -182,3 +182,9 @@
</g>
</g>
</svg>
<style>
svg {
padding-top: 3px;
}
</style>

View File

@ -11,6 +11,6 @@
<style>
img {
padding-top: 1px;
padding-top: 5px;
}
</style>

View File

@ -29,6 +29,18 @@
: BUDIBASE_INTERNAL_DB_ID
export let name
export let beforeSave = async () => {}
export let afterSave = async table => {
notifications.success(`Table ${name} created successfully.`)
// Navigate to new table
const currentUrl = $url()
const path = currentUrl.endsWith("data")
? `./table/${table._id}`
: `../../table/${table._id}`
$goto(path)
}
let error = ""
let autoColumns = getAutoColumnInformation()
let schema = {}
@ -78,15 +90,9 @@
// Create table
let table
try {
await beforeSave()
table = await tables.save(newTable)
notifications.success(`Table ${name} created successfully.`)
// Navigate to new table
const currentUrl = $url()
const path = currentUrl.endsWith("data")
? `./table/${table._id}`
: `../../table/${table._id}`
$goto(path)
await afterSave(table)
} catch (e) {
notifications.error(e)
// reload in case the table was created

View File

@ -6,10 +6,22 @@
faBook,
faPlay,
faLock,
faFileArrowUp,
faChevronLeft,
} from "@fortawesome/free-solid-svg-icons"
import { faGithub, faDiscord } from "@fortawesome/free-brands-svg-icons"
library.add(faXmark, faBook, faPlay, faLock, faGithub, faDiscord, faEnvelope)
library.add(
faXmark,
faBook,
faPlay,
faLock,
faGithub,
faDiscord,
faEnvelope,
faFileArrowUp,
faChevronLeft
)
dom.watch()
</script>
@ -24,8 +36,6 @@
<style>
span {
display: contents;
}
.svg-inline--fa {
color: var(--spectrum-body-m-text-color, var(--spectrum-alias-text-color));
color: var(--spectrum-global-color-gray-900);
}
</style>

View File

@ -143,11 +143,11 @@
display: flex;
padding: 12px;
align-items: center;
transition: filter 0.5s;
transition: filter 0.5s, background 0.13s ease-out;
}
a:hover {
background-color: var(--spectrum-global-color-gray-300);
background-color: var(--spectrum-global-color-gray-200);
}
a:last-child {
@ -168,14 +168,14 @@
}
.disabled {
opacity: 70%;
opacity: 60%;
}
.premiumBadge {
align-items: center;
margin-left: auto;
display: flex;
border: var(--border-light);
border: 1px solid var(--spectrum-global-color-gray-400);
border-radius: 4px;
padding: 4px 7px 5px 8px;
}

View File

@ -179,7 +179,7 @@
<span class="detailPopover">
<Popover
align="right-side"
align="right-outside"
bind:this={popover}
anchor={popoverAnchor}
maxWidth={300}

View File

@ -125,7 +125,6 @@
align="right"
disabled={!isPublished}
dataCy="publish-popover-menu"
showTip={true}
anchor={publishPopoverAnchor}
>
<Layout gap="M">

View File

@ -1,7 +1,7 @@
<script>
import { isActive, redirect, goto, url } from "@roxi/routify"
import { Icon, notifications, Tabs, Tab } from "@budibase/bbui"
import { organisation, auth, menu } from "stores/portal"
import { organisation, auth, menu, apps } from "stores/portal"
import { onMount } from "svelte"
import UpgradeButton from "./_components/UpgradeButton.svelte"
import MobileMenu from "./_components/MobileMenu.svelte"
@ -14,6 +14,7 @@
let activeTab = "Apps"
$: $url(), updateActiveTab($menu)
$: fullScreen = !$apps?.length
const updateActiveTab = menu => {
for (let entry of menu) {
@ -46,7 +47,9 @@
})
</script>
{#if $auth.user && loaded}
{#if fullScreen}
<slot />
{:else if $auth.user && loaded}
<HelpMenu />
<div class="container">
<div class="nav">

View File

@ -2,7 +2,7 @@
import { notifications } from "@budibase/bbui"
import { apps, templates, licensing, groups } from "stores/portal"
import { onMount } from "svelte"
import { goto } from "@roxi/routify"
import { redirect } from "@roxi/routify"
// Don't block loading if we've already hydrated state
let loaded = $apps.length > 0
@ -24,7 +24,7 @@
// Go to new app page if no apps exists
if (!$apps.length) {
$goto("./create")
$redirect("./onboarding")
}
} catch (error) {
notifications.error("Error loading apps and templates")

View File

@ -1,5 +1,6 @@
<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"
@ -38,42 +39,45 @@
}
</script>
<Page>
<Layout noPadding gap="L">
<Breadcrumbs>
<Breadcrumb url={$url("./")} text="Apps" />
<Breadcrumb text="Create new app" />
</Breadcrumbs>
<Header title={$apps.length ? "Create new app" : "Create your first app"}>
<div slot="buttons">
<Button
dataCy="import-app-btn"
size="M"
secondary
on:click={initiateAppImport}
>
Import app
</Button>
<Button
dataCy="create-app-btn"
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 !$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
dataCy="import-app-btn"
size="M"
secondary
on:click={initiateAppImport}
>
Import app
</Button>
<Button
dataCy="create-app-btn"
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}

View File

@ -0,0 +1,13 @@
<script>
import PanelHeader from "./PanelHeader.svelte"
export let onBack = () => {}
</script>
<div>
<PanelHeader
title="Give it some data"
subtitle="Not ready to add yours? Get started with sample data!"
{onBack}
/>
<slot />
</div>

View File

@ -0,0 +1,109 @@
<script>
import { Button, FancyForm, FancyInput, FancyCheckbox } from "@budibase/bbui"
import { capitalise } from "helpers/helpers"
import PanelHeader from "./PanelHeader.svelte"
export let title = ""
export let onBack = null
export let onNext = () => {}
export let fields = {}
let errors = {}
const formatName = name => {
if (name === "ca") {
return "CA"
}
if (name === "ssl") {
return "SSL"
}
if (name === "rejectUnauthorized") {
return "Reject Unauthorized"
}
return capitalise(name)
}
const getDefaultValues = fields => {
const newValues = {}
Object.entries(fields).forEach(([name, { default: defaultValue }]) => {
if (defaultValue) {
newValues[name] = defaultValue
}
})
return newValues
}
const values = getDefaultValues(fields)
const validateRequired = value => {
if (value.length < 1) {
return "Required field"
}
}
const getIsValid = (fields, errors, values) => {
for (const [name, { required }] of Object.entries(fields)) {
if (required && !values[name]) {
return false
}
}
return Object.values(errors).every(error => !error)
}
$: isValid = getIsValid(fields, errors, values)
const handleNext = () => {
const parsedValues = {}
Object.entries(values).forEach(([name, value]) => {
if (fields[name].type === "number") {
parsedValues[name] = parseInt(value, 10)
} else {
parsedValues[name] = value
}
})
return onNext(parsedValues)
}
</script>
<div>
<PanelHeader
{title}
subtitle="Fill in the required fields to fetch your tables"
{onBack}
/>
<div class="form">
<FancyForm>
{#each Object.entries(fields) as [name, { type, default: defaultValue, required }]}
{#if type !== "boolean"}
<FancyInput
bind:value={values[name]}
bind:error={errors[name]}
validate={required ? validateRequired : () => {}}
label={formatName(name)}
{type}
/>
{/if}
{/each}
{#each Object.entries(fields) as [name, { type, default: defaultValue, required }]}
{#if type === "boolean"}
<FancyCheckbox bind:value={values[name]} text={formatName(name)} />
{/if}
{/each}
</FancyForm>
</div>
<Button cta disabled={!isValid} on:click={handleNext}>Connect</Button>
</div>
<style>
.form {
margin-bottom: 36px;
}
</style>

View File

@ -0,0 +1,254 @@
<script>
export let name = ""
export let showData = false
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",
},
]
</script>
<div tabindex="-1" class="exampleApp">
<div class="page">
<div class="header">
<img alt="Budibase Logo" src={"https://i.imgur.com/Xhdt1YP.png"} />
<h1>{name}</h1>
</div>
<div class="nav">Home</div>
<table class={`table ${showData ? "tableVisible" : ""}`}>
<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 ${showData ? "sidePanelVisible" : ""}`}>
<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>
</div>
<style>
.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;
pointer-events: none;
}
.page {
overflow: hidden;
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;
}
.table {
opacity: 0;
}
.tableVisible {
opacity: 1;
}
.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;
}
.sidePanelVisible {
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;
border-radius: 4px;
color: var(--lightText);
padding: 7.5px 12px;
font-size: 13px;
flex-grow: 1;
}
</style>

View File

@ -0,0 +1,60 @@
<script>
import { Button, FancyForm, FancyInput } from "@budibase/bbui"
import PanelHeader from "./PanelHeader.svelte"
export let name = ""
export let url = ""
export let onNext = () => {}
let nameError = null
let urlError = null
$: isValid = name.length && url.length && !nameError && !urlError
const validateName = name => {
if (name.length < 1) {
return "Name must be provided"
}
}
const validateUrl = url => {
if (url.length < 1) {
return "URL must be provided"
}
}
</script>
<div>
<PanelHeader
title="Build your first app"
subtitle="Name your app and set the URL"
/>
<FancyForm>
<FancyInput
bind:value={name}
bind:error={nameError}
validate={validateName}
label="Name"
/>
<FancyInput
bind:value={url}
bind:error={urlError}
validate={validateUrl}
label="URL"
/>
</FancyForm>
{#if url}
<p><span class="host">{window.location.origin}/app/</span>{url}</p>
{:else}
<p></p>
{/if}
<Button size="L" cta disabled={!isValid} on:click={onNext}>Lets go!</Button>
</div>
<style>
p {
color: white;
}
.host {
color: #b0b0b0;
}
</style>

View File

@ -0,0 +1,49 @@
<script>
import FontAwesomeIcon from "components/common/FontAwesomeIcon.svelte"
import { Heading, Body } from "@budibase/bbui"
export let onBack = null
export let title = ""
export let subtitle = ""
</script>
<div class="header">
<img
alt="Budibase Logo"
class="budibaseLogo"
src={"https://i.imgur.com/Xhdt1YP.png"}
/>
<div class="headingAndBack">
{#if onBack}
<button on:click={onBack}>
<FontAwesomeIcon name="fa-solid fa-chevron-left" />
</button>
{/if}
<Heading>{title}</Heading>
</div>
<Body>{subtitle}</Body>
</div>
<style>
.header {
margin-bottom: 20px;
}
.budibaseLogo {
width: 42px;
margin-bottom: 20px;
}
.headingAndBack {
display: flex;
margin-bottom: 13px;
}
button {
background-color: transparent;
border: none;
color: white;
padding-right: 18px;
cursor: pointer;
}
</style>

View File

@ -0,0 +1,202 @@
<script>
import { goto } from "@roxi/routify"
import NamePanel from "./_components/NamePanel.svelte"
import DataPanel from "./_components/DataPanel.svelte"
import DatasourceConfigPanel from "./_components/DatasourceConfigPanel.svelte"
import ExampleApp from "./_components/ExampleApp.svelte"
import { FancyButton, notifications, Modal } from "@budibase/bbui"
import IntegrationIcon from "components/backend/DatasourceNavigator/IntegrationIcon.svelte"
import { SplitPage } from "@budibase/frontend-core"
import { API } from "api"
import { store, automationStore } from "builderStore"
import { saveDatasource } from "builderStore/datasource"
import { integrations } from "stores/backend"
import { auth, admin } from "stores/portal"
import FontAwesomeIcon from "components/common/FontAwesomeIcon.svelte"
import CreateTableModal from "components/backend/TableNavigator/modals/CreateTableModal.svelte"
import createFromScratchScreen from "builderStore/store/screenTemplates/createFromScratchScreen"
import { Roles } from "constants/backend"
let name = ""
let url = ""
let stage = "name"
let appId = null
let plusIntegrations = {}
let integrationsLoading = true
$: getIntegrations()
let uploadModal
const createApp = async useSampleData => {
// Create form data to create app
// This is form based and not JSON
let data = new FormData()
data.append("name", name.trim())
data.append("url", url.trim())
data.append("useTemplate", false)
if (useSampleData) {
data.append("sampleData", true)
}
const createdApp = await API.createApp(data)
// Select Correct Application/DB in prep for creating user
const pkg = await API.fetchAppPackage(createdApp.instance._id)
await store.actions.initialise(pkg)
await automationStore.actions.fetch()
// Update checklist - in case first app
await admin.init()
// Create user
await auth.setInitInfo({})
let defaultScreenTemplate = createFromScratchScreen.create()
defaultScreenTemplate.routing.route = "/home"
defaultScreenTemplate.routing.roldId = Roles.BASIC
await store.actions.screens.save(defaultScreenTemplate)
return createdApp.instance._id
}
const getIntegrations = async () => {
try {
await integrations.init()
const newPlusIntegrations = {}
Object.entries($integrations).forEach(([integrationType, schema]) => {
if (schema?.plus) {
newPlusIntegrations[integrationType] = schema
}
})
plusIntegrations = newPlusIntegrations
} catch (e) {
notifications.error("There was a problem communicating with the server.")
} finally {
integrationsLoading = false
}
}
const goToApp = appId => {
$goto(`/builder/app/${appId}`)
notifications.success(`App created successfully`)
}
const handleCreateApp = async ({ datasourceConfig, useSampleData }) => {
try {
appId = await createApp(useSampleData)
if (datasourceConfig) {
await saveDatasource({
plus: true,
auth: undefined,
name: plusIntegrations[stage].friendlyName,
schema: plusIntegrations[stage].datasource,
config: datasourceConfig,
type: stage,
})
}
goToApp(appId)
} catch (e) {
console.log(e)
notifications.error("There was a problem creating your app")
}
}
</script>
<Modal bind:this={uploadModal}>
<CreateTableModal
name="Your Data"
beforeSave={createApp}
afterSave={() => goToApp(appId)}
/>
</Modal>
<SplitPage>
{#if stage === "name"}
<NamePanel bind:name bind:url onNext={() => (stage = "data")} />
{:else if integrationsLoading}
<p>loading...</p>
{:else if stage === "data"}
<DataPanel onBack={() => (stage = "name")}>
<div class="dataButton">
<FancyButton on:click={() => handleCreateApp({ useSampleData: true })}>
<div class="dataButtonContent">
<div class="dataButtonIcon">
<img
alt="Budibase Logo"
class="budibaseLogo"
src={"https://i.imgur.com/Xhdt1YP.png"}
/>
</div>
Budibase Sample data
</div>
</FancyButton>
</div>
<div class="dataButton">
<FancyButton on:click={uploadModal.show}>
<div class="dataButtonContent">
<div class="dataButtonIcon">
<FontAwesomeIcon name="fa-solid fa-file-arrow-up" />
</div>
Upload file
</div>
</FancyButton>
</div>
{#each Object.entries(plusIntegrations) as [integrationType, schema]}
<div class="dataButton">
<FancyButton on:click={() => (stage = integrationType)}>
<div class="dataButtonContent">
<div class="dataButtonIcon">
<IntegrationIcon {integrationType} {schema} />
</div>
{schema.friendlyName}
</div>
</FancyButton>
</div>
{/each}
</DataPanel>
{:else if stage in plusIntegrations}
<DatasourceConfigPanel
title={plusIntegrations[stage].friendlyName}
fields={plusIntegrations[stage].datasource}
onBack={() => (stage = "data")}
onNext={data => handleCreateApp({ datasourceConfig: data })}
/>
{:else}
<p>There was an problem. Please refresh the page and try again.</p>
{/if}
<div slot="right">
<ExampleApp {name} showData={stage !== "name"} />
</div>
</SplitPage>
<style>
.dataButton {
margin-bottom: 12px;
}
.dataButtonContent {
display: flex;
align-items: center;
}
.budibaseLogo {
height: 20px;
}
.dataButtonIcon {
width: 22px;
display: flex;
justify-content: center;
margin-right: 16px;
}
.dataButtonContent :global(svg) {
font-size: 18px;
color: white;
}
</style>

View File

@ -1,9 +1,16 @@
import { svelte } from "@sveltejs/vite-plugin-svelte"
import replace from "@rollup/plugin-replace"
import { defineConfig, loadEnv } from "vite"
import path from "path"
const ignoredWarnings = [
"unused-export-let",
"css-unused-selector",
"module-script-reactive-declaration",
"a11y-no-onchange",
"a11y-click-events-have-key-events",
]
export default defineConfig(({ mode }) => {
const isProduction = mode === "production"
const env = loadEnv(mode, process.cwd())
@ -29,6 +36,12 @@ export default defineConfig(({ mode }) => {
svelte({
hot: !isProduction,
emitCss: true,
onwarn: (warning, handler) => {
// Ignore some warnings
if (!ignoredWarnings.includes(warning.code)) {
handler(warning)
}
},
}),
replace({
preventAssignment: true,

View File

@ -17,6 +17,7 @@ const ignoredWarnings = [
"css-unused-selector",
"module-script-reactive-declaration",
"a11y-no-onchange",
"a11y-click-events-have-key-events",
]
export default {
@ -88,7 +89,9 @@ export default {
dedupe: ["svelte", "svelte/internal"],
}),
svg(),
image(),
image({
exclude: "**/*.svg",
}),
json(),
production && terser(),
!production && visualizer(),

View File

@ -9,7 +9,8 @@
</div>
</div>
<div class="right spectrum spectrum--darkest">
<img src={BG} alt="background" />
<!-- No alt attribute to avoid flash -->
<img src={BG} alt=" " />
<slot name="right" />
</div>
</div>
@ -30,6 +31,7 @@
overflow-y: auto;
}
.right {
overflow: hidden;
position: relative;
}
.right img {