Add redesign for apps pages

This commit is contained in:
Andrew Kingston 2022-10-27 19:20:55 +01:00
parent d016ee9775
commit 0cdc814bcc
16 changed files with 466 additions and 445 deletions

View File

@ -1,6 +1,6 @@
<script>
export let wide = false
export let maxWidth = "80ch"
export let maxWidth = "1080px"
export let noPadding = false
</script>
@ -16,8 +16,8 @@
align-items: stretch;
max-width: var(--max-width);
margin: 0 auto;
padding: calc(var(--spacing-xl) * 2);
min-height: calc(100% - var(--spacing-xl) * 4);
flex: 1 1 auto;
padding-bottom: 50px;
}
.wide {

View File

@ -8,6 +8,7 @@
ProgressCircle,
Layout,
Body,
Icon,
} from "@budibase/bbui"
import { auth, apps } from "stores/portal"
import { processStringSync } from "@budibase/string-templates"
@ -58,19 +59,14 @@
<div class="lock-status">
{#if lockedBy}
<Button
quiet
secondary
icon="LockClosed"
<Icon
name="LockClosed"
hoverable
size={buttonSize}
on:click={() => {
appLockModal.show()
}}
>
<span class="lock-status-text">
{lockedByHeading}
</span>
</Button>
/>
{/if}
</div>

View File

@ -1,128 +1,110 @@
<script>
import {
Layout,
Detail,
Heading,
Button,
Modal,
ActionGroup,
ActionButton,
} from "@budibase/bbui"
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 selectedTemplateCategory
let selectedCategory
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
$: categories = getCategories(templates)
$: filteredCategories = getFilteredCategories(categories, selectedCategory)
const getCategories = templates => {
let categories = {}
templates?.forEach(template => {
if (!categories[template.category]) {
categories[template.category] = []
}
acc[template.category] = !acc[template.category]
? []
: acc[template.category]
acc[template.category].push(template)
return acc
}, {})
return grouped
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
}
$: filteredTemplates = groupTemplatesByCategory(
templates,
selectedTemplateCategory
)
$: filteredTemplateCategories = filteredTemplates
? Object.keys(filteredTemplates).sort()
: []
$: templateCategories = templates
? Object.keys(groupTemplatesByCategory(templates)).sort()
: []
const getFilteredCategories = (categories, selectedCategory) => {
if (!selectedCategory) {
return categories
}
return categories.filter(x => x.name === selectedCategory)
}
const stopAppCreation = () => {
template = null
}
</script>
<div class="template-header">
<Layout noPadding gap="S">
<Heading size="S">Templates</Heading>
<div class="template-category-filters spectrum-ActionGroup">
<ActionGroup>
<ActionButton
selected={!selectedTemplateCategory}
on:click={() => {
selectedTemplateCategory = null
}}
>
All
</ActionButton>
{#each templateCategories as templateCategoryKey}
<ActionButton
dataCy={templateCategoryKey}
selected={templateCategoryKey == selectedTemplateCategory}
on:click={() => {
selectedTemplateCategory = templateCategoryKey
}}
>
{templateCategoryKey}
</ActionButton>
{/each}
</ActionGroup>
</div>
</Layout>
</div>
<div class="template-categories">
<Layout gap="XL" noPadding>
{#each filteredTemplateCategories as templateCategoryKey}
<div class="template-category" data-cy={templateCategoryKey}>
<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}
>
{#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
<Content>
<div slot="side-nav">
<SideNav title="Templates">
<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" data-cy={category.name}>
<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}
>
Details
</a>
</TemplateCard>
{/each}
{#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>
</div>
{/each}
</Layout>
</div>
{/each}
</Layout>
</div>
</Content>
<Modal
bind:this={creationModal}

View File

@ -0,0 +1,34 @@
<script>
import { Icon } from "@budibase/bbui"
export let url
export let text
</script>
<div>
<a href={url}>
{text}
</a>
<Icon name="ChevronRight" />
</div>
<style>
div {
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
color: var(--spectrum-global-color-gray-700);
gap: var(--spacing-m);
font-size: 16px;
font-weight: 600;
}
div :global(.spectrum-Icon),
a {
color: inherit;
transition: color 130ms ease-out;
}
a:hover {
color: var(--spectrum-global-color-gray-900);
}
</style>

View File

@ -0,0 +1,20 @@
<div>
<slot />
</div>
<style>
div {
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
gap: var(--spacing-m);
}
div :global(> *:last-child .spectrum-Icon) {
display: none;
}
div :global(> *:last-child) {
color: var(--spectrum-global-color-gray-900);
}
</style>

View File

@ -0,0 +1,27 @@
<script>
</script>
<div class="content">
<div class="side-nav">
<slot name="side-nav" />
</div>
<div class="main">
<slot />
</div>
</div>
<style>
.content {
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: stretch;
gap: 40px;
}
.side-nav {
flex: 0 0 200px;
}
.main {
flex: 1 1 auto;
}
</style>

View File

@ -0,0 +1,31 @@
<script>
import { Heading } from "@budibase/bbui"
export let title
</script>
<div class="header">
<Heading size="L">{title}</Heading>
<div class="buttons">
<slot name="buttons" />
</div>
</div>
<style>
.header {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
}
.buttons {
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
gap: var(--spacing-xl);
}
.buttons :global(> div) {
display: contents;
}
</style>

View File

@ -0,0 +1,25 @@
<script>
export let title
</script>
<div class="side-nav">
{#if title}
<div class="title">{title}</div>
{/if}
<slot />
</div>
<style>
.side-nav {
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: stretch;
}
.title {
margin-left: var(--spacing-m);
font-size: 12px;
color: var(--spectrum-global-color-gray-700);
margin-bottom: var(--spacing-m);
}
</style>

View File

@ -0,0 +1,23 @@
<script>
export let text
export let url
export let active = false
</script>
<a on:click {url} class:active>
{text}
</a>
<style>
a {
padding: var(--spacing-s) var(--spacing-m);
color: var(--spectrum-global-color-gray-900);
border-radius: 4px;
transition: background 130ms ease-out;
}
.active,
a:hover {
background-color: var(--spectrum-global-color-gray-200);
cursor: pointer;
}
</style>

View File

@ -0,0 +1,6 @@
export { default as Breadcrumb } from "./Breadcrumb.svelte"
export { default as Breadcrumbs } from "./Breadcrumbs.svelte"
export { default as Header } from "./Header.svelte"
export { default as Content } from "./Content.svelte"
export { default as SideNavItem } from "./SideNavItem.svelte"
export { default as SideNav } from "./SideNav.svelte"

View File

@ -1,5 +1,5 @@
<script>
import { Heading, Button, Icon } from "@budibase/bbui"
import { Heading, Body, Button, Icon } from "@budibase/bbui"
import AppLockModal from "../common/AppLockModal.svelte"
import { processStringSync } from "@budibase/string-templates"
@ -8,78 +8,106 @@
export let appOverview
</script>
<div class="title" data-cy={`${app.devId}`}>
<div>
<div class="app-icon" style="color: {app.icon?.color || ''}">
<Icon size="XL" name={app.icon?.name || "Apps"} />
<div class="app-row">
<div class="header">
<div class="title" data-cy={`${app.devId}`}>
<div class="app-icon" style="color: {app.icon?.color || ''}">
<Icon size="L" name={app.icon?.name || "Apps"} />
</div>
<div class="name" data-cy="app-name-link" on:click={() => editApp(app)}>
<Heading size="S">
{app.name}
</Heading>
</div>
</div>
<div class="name" data-cy="app-name-link" on:click={() => editApp(app)}>
<Heading size="XS">
{app.name}
</Heading>
<div class="updated">
{#if app.updatedAt}
{processStringSync("Updated {{ duration time 'millisecond' }} ago", {
time: new Date().getTime() - new Date(app.updatedAt).getTime(),
})}
{:else}
Never updated
{/if}
</div>
</div>
</div>
<div class="desktop">
{#if app.updatedAt}
{processStringSync("Updated {{ duration time 'millisecond' }} ago", {
time: new Date().getTime() - new Date(app.updatedAt).getTime(),
})}
{:else}
Never updated
{/if}
</div>
<div class="desktop">
<span><AppLockModal {app} buttonSize="M" /></span>
</div>
<div class="desktop">
<div class="app-status">
{#if app.deployed}
<Icon name="Globe" disabled={false} />
Published
{:else}
<Icon name="GlobeStrike" disabled={true} />
<span class="disabled"> Unpublished </span>
{/if}
<div class="title app-status" class:deployed={app.deployed}>
<Icon size="L" name={app.deployed ? "GlobeCheck" : "GlobeStrike"} />
<Body size="S">{`${window.origin}/app${app.url}`}</Body>
</div>
</div>
<div data-cy={`row_actions_${app.appId}`}>
<div class="app-row-actions">
<Button size="S" secondary newStyles on:click={() => appOverview(app)}>
Manage
</Button>
<Button
size="S"
primary
newStyles
disabled={app.lockedOther}
on:click={() => editApp(app)}
>
Edit
</Button>
<div data-cy={`row_actions_${app.appId}`}>
<div class="app-row-actions">
<Button
size="S"
primary
newStyles
disabled={app.lockedOther}
on:click={() => editApp(app)}
>
Edit
</Button>
<Button size="S" secondary newStyles on:click={() => appOverview(app)}>
Manage
</Button>
<AppLockModal {app} buttonSize="M" />
</div>
</div>
</div>
<style>
div.title,
div.title > div {
.app-row {
background: var(--background);
padding: 24px 32px;
border-radius: 8px;
display: flex;
max-width: 100%;
flex-direction: column;
justify-content: flex-start;
align-items: stretch;
gap: var(--spacing-m);
}
.app-row-actions {
grid-gap: var(--spacing-s);
.header {
display: flex;
flex-direction: row;
justify-content: flex-end;
justify-content: space-between;
align-items: center;
}
.updated {
color: var(--spectrum-global-color-gray-700);
}
.title,
.app-status {
display: grid;
grid-gap: var(--spacing-s);
grid-template-columns: 24px 100px;
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
gap: 10px;
}
.app-status span.disabled {
opacity: 0.3;
.title :global(.spectrum-Heading),
.title :global(.spectrum-Icon),
.title :global(.spectrum-Body) {
color: var(--spectrum-global-color-gray-900);
}
.app-status:not(.deployed) :global(.spectrum-Icon),
.app-status:not(.deployed) :global(.spectrum-Body) {
color: var(--spectrum-global-color-gray-700);
}
.app-row-actions {
gap: var(--spacing-m);
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
margin-top: var(--spacing-m);
}
.name {
text-decoration: none;
overflow: hidden;
@ -88,7 +116,6 @@
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
margin-left: calc(1.5 * var(--spacing-xl));
}
.title :global(h1:hover) {
color: var(--spectrum-global-color-blue-600);

View File

@ -246,9 +246,7 @@
</div>
</div>
<div class="main">
<div class="content">
<slot />
</div>
<slot />
</div>
</div>
<Modal bind:this={userInfoModal}>
@ -333,10 +331,7 @@
justify-content: center;
align-items: stretch;
overflow: auto;
padding: 50px;
}
.content {
max-width: 1080px;
padding: 50px 50px 0 50px;
}
@media (max-width: 640px) {

View File

@ -0,0 +1,33 @@
<script>
import { notifications } from "@budibase/bbui"
import { apps, templates, licensing } from "stores/portal"
import { onMount } from "svelte"
import { goto } from "@roxi/routify"
let loaded = false
onMount(async () => {
try {
// Always load latest
await apps.load()
await licensing.init()
await templates.load()
if ($templates?.length === 0) {
notifications.error("There was a problem loading quick start templates")
}
// Go to new app page if no apps exists
if (!$apps.length) {
$goto("./create")
}
} catch (error) {
notifications.error("Error loading apps and templates")
}
loaded = true
})
</script>
{#if loaded}
<slot />
{/if}

View File

@ -1,49 +1,17 @@
<script>
import { goto } from "@roxi/routify"
import {
Layout,
Page,
notifications,
Button,
Heading,
Body,
Modal,
Divider,
ActionButton,
} from "@budibase/bbui"
import { url } from "@roxi/routify"
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 { onMount } from "svelte"
import { templates, licensing } from "stores/portal"
import { apps, templates, licensing } from "stores/portal"
import { Breadcrumbs, Breadcrumb, Header } from "components/portal/page"
let loaded = $templates?.length
let template
let creationModal = false
let appLimitModal
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()
// always load latest
await licensing.init()
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 = () => {
if ($licensing?.usageMetrics?.apps >= 100) {
appLimitModal.show()
@ -70,58 +38,34 @@
}
</script>
<Page wide>
<Layout noPadding gap="XL">
<span>
<ActionButton
secondary
icon={"ArrowLeft"}
on:click={() => {
$goto("../")
}}
>
Back
</ActionButton>
</span>
<div class="title">
<div class="welcome">
<Layout noPadding gap="XS">
<Heading size="L">{createAppTitle}</Heading>
<Body size="M">
{welcomeBody}
</Body>
</Layout>
<div class="buttons">
<Button
dataCy="create-app-btn"
size="M"
icon="Add"
cta
on:click={initiateAppCreation}
>
{createAppButtonText}
</Button>
<Button
dataCy="import-app-btn"
icon="Import"
size="M"
quiet
secondary
on:click={initiateAppImport}
>
Import app
</Button>
</div>
<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"
newStyles
secondary
on:click={initiateAppImport}
>
Import app
</Button>
<Button
dataCy="create-app-btn"
size="M"
cta
on:click={initiateAppCreation}
>
Start from scratch
</Button>
</div>
</div>
<Divider />
{#if loaded && $templates?.length}
<TemplateDisplay templates={$templates} />
{/if}
</Header>
<TemplateDisplay templates={$templates} />
</Layout>
</Page>

View File

@ -9,9 +9,9 @@
notifications,
Notification,
Body,
Icon,
Search,
} 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"
@ -20,12 +20,11 @@
import { store, automationStore } from "builderStore"
import { API } from "api"
import { onMount } from "svelte"
import { apps, auth, admin, templates, licensing } from "stores/portal"
import { apps, auth, admin, licensing } from "stores/portal"
import { goto } from "@roxi/routify"
import AppRow from "components/start/AppRow.svelte"
import { AppStatus } from "constants"
import Logo from "assets/bb-space-man.svg"
import AccessFilter from "./_components/AcessFilter.svelte"
let sortBy = "name"
let template
@ -34,28 +33,13 @@
let updatingModal
let appLimitModal
let creatingApp = false
let loaded = $apps?.length || $templates?.length
let searchTerm = ""
let cloud = $admin.cloud
let creatingFromTemplate = false
let automationErrors
let accessFilterList = null
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"
$: welcomeHeader = `Welcome ${auth?.user?.firstName || "back"}`
$: enrichedApps = enrichApps($apps, $auth.user, sortBy)
$: filteredApps = enrichedApps.filter(
app =>
@ -207,10 +191,6 @@
$goto(`../../app/${app.devId}`)
}
const accessFilterAction = accessFilter => {
accessFilterList = accessFilter.detail
}
function createAppFromTemplateUrl(templateKey) {
// validate the template key just to make sure
const templateParts = templateKey.split("/")
@ -226,33 +206,21 @@
onMount(async () => {
try {
await apps.load()
await templates.load()
// always load latest
await licensing.init()
if ($templates?.length === 0) {
notifications.error(
"There was a problem loading quick start templates."
)
}
// If the portal is loaded from an external URL with a template param
const initInfo = await auth.getInitInfo()
if (initInfo?.init_template) {
creatingFromTemplate = true
createAppFromTemplateUrl(initInfo.init_template)
return
}
} catch (error) {
notifications.error("Error loading apps and templates")
notifications.error("Error getting init info")
}
loaded = true
})
</script>
<Page wide>
<Layout noPadding gap="M">
{#if loaded}
{#if $apps.length}
<Page>
<Layout noPadding gap="L">
{#each Object.keys(automationErrors || {}) as appId}
<Notification
wide
@ -272,42 +240,15 @@
{/each}
<div class="title">
<div class="welcome">
<Layout noPadding gap="XS">
<Layout noPadding gap="S">
<Heading size="L">{welcomeHeader}</Heading>
<Body size="M">
{welcomeBody}
Manage your apps and get a head start with templates
</Body>
</Layout>
{#if !$apps?.length}
<div class="buttons">
<Button
dataCy="create-app-btn"
size="M"
icon="Add"
cta
on:click={initiateAppCreation}
>
{createAppButtonText}
</Button>
<Button
dataCy="import-app-btn"
icon="Import"
size="L"
quiet
secondary
on:click={initiateAppImport}
>
Import app
</Button>
</div>
{/if}
</div>
</div>
{#if !$apps?.length && $templates?.length}
<TemplateDisplay templates={$templates} />
{/if}
{#if enrichedApps.length}
<Layout noPadding gap="L">
<div class="title">
@ -315,27 +256,24 @@
<Button
dataCy="create-app-btn"
size="M"
icon="Add"
cta
on:click={initiateAppCreation}
>
{createAppButtonText}
Create new app
</Button>
{#if $apps?.length > 0}
<Button
icon="Experience"
size="M"
quiet
newStyles
secondary
on:click={$goto("/builder/portal/apps/templates")}
>
Templates
View templates
</Button>
{/if}
{#if !$apps?.length}
<Button
dataCy="import-app-btn"
icon="Import"
size="L"
quiet
secondary
@ -348,33 +286,23 @@
{#if enrichedApps.length > 1}
<div class="app-actions">
{#if cloud}
<Button
size="M"
icon="Export"
quiet
secondary
<Icon
name="Download"
hoverable
on:click={initiateAppsExport}
>
Export apps
</Button>
{/if}
<div class="filter">
{#if $licensing.groupsEnabled}
<AccessFilter on:change={accessFilterAction} />
{/if}
<Select
quiet
autoWidth
bind:value={sortBy}
placeholder={null}
options={[
{ label: "Sort by name", value: "name" },
{ label: "Sort by recently updated", value: "updated" },
{ label: "Sort by status", value: "status" },
]}
/>
<Search placeholder="Search" bind:value={searchTerm} />
</div>
{/if}
<Select
autoWidth
bind:value={sortBy}
placeholder={null}
options={[
{ label: "Sort by name", value: "name" },
{ label: "Sort by recently updated", value: "updated" },
{ label: "Sort by status", value: "status" },
]}
/>
<Search placeholder="Search" bind:value={searchTerm} />
</div>
{/if}
</div>
@ -386,17 +314,17 @@
</div>
</Layout>
{/if}
{/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>
{/if}
</Layout>
</Page>
{#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>
{/if}
</Layout>
</Page>
{/if}
<Modal
bind:this={creationModal}
@ -414,18 +342,6 @@
<AppLimitModal bind:this={appLimitModal} />
<style>
.appTable {
border-top: var(--border-light);
}
.app-actions {
display: flex;
}
.app-actions :global(> button) {
margin-right: 10px;
}
.title .welcome > .buttons {
padding-top: var(--spacing-l);
}
.title {
display: flex;
flex-direction: row;
@ -447,34 +363,20 @@
display: none;
}
}
.filter {
.app-actions {
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
gap: var(--spacing-xl);
}
.appTable {
display: grid;
grid-template-rows: auto;
grid-template-columns: 1fr 1fr 1fr 1fr auto;
align-items: center;
}
.appTable.unlocked {
grid-template-columns: 1fr 1fr auto 1fr auto;
}
.appTable :global(> div) {
height: 70px;
display: grid;
align-items: center;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.appTable :global(> div) {
border-bottom: var(--border-light);
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: stretch;
gap: 24px;
}
@media (max-width: 640px) {

View File

@ -1,42 +1,18 @@
<script>
import { goto } from "@roxi/routify"
import { Layout, Page, notifications, ActionButton } from "@budibase/bbui"
import { url } from "@roxi/routify"
import { Layout, Page } from "@budibase/bbui"
import TemplateDisplay from "components/common/TemplateDisplay.svelte"
import { onMount } from "svelte"
import { templates } from "stores/portal"
let loaded = $templates?.length
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
})
import { Breadcrumbs, Breadcrumb, Header } from "components/portal/page"
</script>
<Page wide>
<Layout noPadding gap="XL">
<span>
<ActionButton
secondary
icon={"ArrowLeft"}
on:click={() => {
$goto("../")
}}
>
Back
</ActionButton>
</span>
{#if loaded && $templates?.length}
<TemplateDisplay templates={$templates} />
{/if}
<Page>
<Layout noPadding gap="L">
<Breadcrumbs>
<Breadcrumb url={$url("./")} text="Apps" />
<Breadcrumb text="Templates" />
</Breadcrumbs>
<Header title="Templates" />
<TemplateDisplay templates={$templates} />
</Layout>
</Page>