App Preview Skeleton (#12898)
* wip * wip * wip * wip * wip * wip * builder design skeleton * pre doc loading skeleton for portal preview * feature complete? * wip * linting * linting * linting * fix ts error? * ts types fix * linting * remove unnecessary build stuff * include skeleton in server app directly * linting * linting * fix builder preview aspect ratio for skeleton * remove dev tools skeleton * linting
This commit is contained in:
parent
2a8bb872ad
commit
9a9b745795
|
@ -12,17 +12,11 @@
|
|||
hoverStore,
|
||||
} from "stores/builder"
|
||||
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
||||
import {
|
||||
ProgressCircle,
|
||||
Layout,
|
||||
Heading,
|
||||
Body,
|
||||
Icon,
|
||||
notifications,
|
||||
} from "@budibase/bbui"
|
||||
import { Layout, Heading, Body, Icon, notifications } from "@budibase/bbui"
|
||||
import ErrorSVG from "@budibase/frontend-core/assets/error.svg?raw"
|
||||
import { findComponent, findComponentPath } from "helpers/components"
|
||||
import { isActive, goto } from "@roxi/routify"
|
||||
import { ClientAppSkeleton } from "@budibase/frontend-core"
|
||||
|
||||
let iframe
|
||||
let layout
|
||||
|
@ -240,8 +234,16 @@
|
|||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div class="component-container">
|
||||
{#if loading}
|
||||
<div class="center">
|
||||
<ProgressCircle />
|
||||
<div
|
||||
class={`loading ${$builderStore.theme}`}
|
||||
class:tablet={$previewStore.previewDevice === "tablet"}
|
||||
class:mobile={$previewStore.previewDevice === "mobile"}
|
||||
>
|
||||
<ClientAppSkeleton
|
||||
sideNav={$builderStore.navigation?.navigation === "Left"}
|
||||
hideFooter
|
||||
hideDevTools
|
||||
/>
|
||||
</div>
|
||||
{:else if error}
|
||||
<div class="center error">
|
||||
|
@ -258,8 +260,6 @@
|
|||
bind:this={iframe}
|
||||
src="/app/preview"
|
||||
class:hidden={loading || error}
|
||||
class:tablet={$previewStore.previewDevice === "tablet"}
|
||||
class:mobile={$previewStore.previewDevice === "mobile"}
|
||||
/>
|
||||
<div
|
||||
class="add-component"
|
||||
|
@ -279,6 +279,25 @@
|
|||
/>
|
||||
|
||||
<style>
|
||||
.loading {
|
||||
position: absolute;
|
||||
container-type: inline-size;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 2px solid transparent;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.loading.tablet {
|
||||
width: calc(1024px + 6px);
|
||||
max-height: calc(768px + 6px);
|
||||
}
|
||||
|
||||
.loading.mobile {
|
||||
width: calc(390px + 6px);
|
||||
max-height: calc(844px + 6px);
|
||||
}
|
||||
|
||||
.component-container {
|
||||
grid-row-start: middle;
|
||||
grid-column-start: middle;
|
||||
|
|
|
@ -1,16 +1,22 @@
|
|||
<script>
|
||||
import { onMount, onDestroy } from "svelte"
|
||||
import { params, goto } from "@roxi/routify"
|
||||
import { apps, auth, sideBarCollapsed } from "stores/portal"
|
||||
import { licensing, apps, auth, sideBarCollapsed } from "stores/portal"
|
||||
import { Link, Body, ActionButton } from "@budibase/bbui"
|
||||
import { sdk } from "@budibase/shared-core"
|
||||
import { API } from "api"
|
||||
import ErrorSVG from "./ErrorSVG.svelte"
|
||||
import { ClientAppSkeleton } from "@budibase/frontend-core"
|
||||
|
||||
$: app = $apps.find(app => app.appId === $params.appId)
|
||||
$: iframeUrl = getIframeURL(app)
|
||||
$: isBuilder = sdk.users.isBuilder($auth.user, app?.devId)
|
||||
|
||||
let loading = true
|
||||
|
||||
const getIframeURL = app => {
|
||||
loading = true
|
||||
|
||||
if (app.status === "published") {
|
||||
return `/app${app.url}`
|
||||
}
|
||||
|
@ -28,6 +34,20 @@
|
|||
}
|
||||
|
||||
$: fetchScreens(app?.devId)
|
||||
|
||||
const receiveMessage = async message => {
|
||||
if (message.data.type === "docLoaded") {
|
||||
loading = false
|
||||
}
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
window.addEventListener("message", receiveMessage)
|
||||
})
|
||||
|
||||
onDestroy(() => {
|
||||
window.removeEventListener("message", receiveMessage)
|
||||
})
|
||||
</script>
|
||||
|
||||
<div class="container">
|
||||
|
@ -78,7 +98,17 @@
|
|||
</Body>
|
||||
</div>
|
||||
{:else}
|
||||
<iframe src={iframeUrl} title={app.name} />
|
||||
<div class:hide={!loading} class="loading">
|
||||
<div class={`loadingThemeWrapper ${app.theme}`}>
|
||||
<ClientAppSkeleton
|
||||
noAnimation
|
||||
hideDevTools={app?.status === "published"}
|
||||
sideNav={app?.navigation.navigation === "Left"}
|
||||
hideFooter={$licensing.brandingEnabled}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<iframe class:hide={loading} src={iframeUrl} title={app.name} />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
|
@ -100,6 +130,23 @@
|
|||
flex: 0 0 50px;
|
||||
}
|
||||
|
||||
.loading {
|
||||
height: 100%;
|
||||
border: 1px solid var(--spectrum-global-color-gray-300);
|
||||
border-radius: var(--spacing-s);
|
||||
overflow: hidden;
|
||||
}
|
||||
.loadingThemeWrapper {
|
||||
height: 100%;
|
||||
container-type: inline-size;
|
||||
}
|
||||
|
||||
.hide {
|
||||
visibility: hidden;
|
||||
height: 0;
|
||||
border: none;
|
||||
}
|
||||
|
||||
iframe {
|
||||
flex: 1 1 auto;
|
||||
border-radius: var(--spacing-s);
|
||||
|
|
|
@ -80,11 +80,18 @@
|
|||
}
|
||||
}
|
||||
|
||||
let fontsLoaded = false
|
||||
|
||||
// Load app config
|
||||
onMount(async () => {
|
||||
document.fonts.ready.then(() => {
|
||||
fontsLoaded = true
|
||||
})
|
||||
|
||||
await initialise()
|
||||
await authStore.actions.fetchUser()
|
||||
dataLoaded = true
|
||||
|
||||
if (get(builderStore).inBuilder) {
|
||||
builderStore.actions.notifyLoaded()
|
||||
} else {
|
||||
|
@ -93,6 +100,12 @@
|
|||
})
|
||||
}
|
||||
})
|
||||
|
||||
$: {
|
||||
if (dataLoaded && fontsLoaded) {
|
||||
document.getElementById("clientAppSkeletonLoader")?.remove()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
|
@ -103,140 +116,140 @@
|
|||
{/if}
|
||||
</svelte:head>
|
||||
|
||||
{#if dataLoaded}
|
||||
<div
|
||||
id="spectrum-root"
|
||||
lang="en"
|
||||
dir="ltr"
|
||||
class="spectrum spectrum--medium {$themeStore.baseTheme} {$themeStore.theme}"
|
||||
class:builder={$builderStore.inBuilder}
|
||||
>
|
||||
<DeviceBindingsProvider>
|
||||
<UserBindingsProvider>
|
||||
<StateBindingsProvider>
|
||||
<RowSelectionProvider>
|
||||
<QueryParamsProvider>
|
||||
<!-- Settings bar can be rendered outside of device preview -->
|
||||
<!-- Key block needs to be outside the if statement or it breaks -->
|
||||
{#key $builderStore.selectedComponentId}
|
||||
{#if $builderStore.inBuilder}
|
||||
<SettingsBar />
|
||||
{/if}
|
||||
{/key}
|
||||
<div
|
||||
id="spectrum-root"
|
||||
lang="en"
|
||||
dir="ltr"
|
||||
class="spectrum spectrum--medium {$themeStore.baseTheme} {$themeStore.theme}"
|
||||
class:builder={$builderStore.inBuilder}
|
||||
class:show={fontsLoaded && dataLoaded}
|
||||
>
|
||||
<DeviceBindingsProvider>
|
||||
<UserBindingsProvider>
|
||||
<StateBindingsProvider>
|
||||
<RowSelectionProvider>
|
||||
<QueryParamsProvider>
|
||||
<!-- Settings bar can be rendered outside of device preview -->
|
||||
<!-- Key block needs to be outside the if statement or it breaks -->
|
||||
{#key $builderStore.selectedComponentId}
|
||||
{#if $builderStore.inBuilder}
|
||||
<SettingsBar />
|
||||
{/if}
|
||||
{/key}
|
||||
|
||||
<!-- Clip boundary for selection indicators -->
|
||||
<div
|
||||
id="clip-root"
|
||||
class:preview={$builderStore.inBuilder}
|
||||
class:tablet-preview={$builderStore.previewDevice === "tablet"}
|
||||
class:mobile-preview={$builderStore.previewDevice === "mobile"}
|
||||
>
|
||||
<!-- Actual app -->
|
||||
<div id="app-root">
|
||||
{#if showDevTools}
|
||||
<DevToolsHeader />
|
||||
<!-- Clip boundary for selection indicators -->
|
||||
<div
|
||||
id="clip-root"
|
||||
class:preview={$builderStore.inBuilder}
|
||||
class:tablet-preview={$builderStore.previewDevice === "tablet"}
|
||||
class:mobile-preview={$builderStore.previewDevice === "mobile"}
|
||||
>
|
||||
<!-- Actual app -->
|
||||
<div id="app-root">
|
||||
{#if showDevTools}
|
||||
<DevToolsHeader />
|
||||
{/if}
|
||||
|
||||
<div id="app-body">
|
||||
{#if permissionError}
|
||||
<div class="error">
|
||||
<Layout justifyItems="center" gap="S">
|
||||
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
||||
{@html ErrorSVG}
|
||||
<Heading size="L">
|
||||
You don't have permission to use this app
|
||||
</Heading>
|
||||
<Body size="S">
|
||||
Ask your administrator to grant you access
|
||||
</Body>
|
||||
</Layout>
|
||||
</div>
|
||||
{:else if !$screenStore.activeLayout}
|
||||
<div class="error">
|
||||
<Layout justifyItems="center" gap="S">
|
||||
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
||||
{@html ErrorSVG}
|
||||
<Heading size="L">
|
||||
Something went wrong rendering your app
|
||||
</Heading>
|
||||
<Body size="S">
|
||||
Get in touch with support if this issue persists
|
||||
</Body>
|
||||
</Layout>
|
||||
</div>
|
||||
{:else if embedNoScreens}
|
||||
<div class="error">
|
||||
<Layout justifyItems="center" gap="S">
|
||||
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
||||
{@html ErrorSVG}
|
||||
<Heading size="L">
|
||||
This Budibase app is not publicly accessible
|
||||
</Heading>
|
||||
</Layout>
|
||||
</div>
|
||||
{:else}
|
||||
<CustomThemeWrapper>
|
||||
{#key $screenStore.activeLayout._id}
|
||||
<Component
|
||||
isLayout
|
||||
instance={$screenStore.activeLayout.props}
|
||||
/>
|
||||
{/key}
|
||||
|
||||
<!--
|
||||
Flatpickr needs to be inside the theme wrapper.
|
||||
It also needs its own container because otherwise it hijacks
|
||||
key events on the whole page. It is painful to work with.
|
||||
-->
|
||||
<div id="flatpickr-root" />
|
||||
|
||||
<!-- Modal container to ensure they sit on top -->
|
||||
<div class="modal-container" />
|
||||
|
||||
<!-- Layers on top of app -->
|
||||
<NotificationDisplay />
|
||||
<ConfirmationDisplay />
|
||||
<PeekScreenDisplay />
|
||||
</CustomThemeWrapper>
|
||||
{/if}
|
||||
|
||||
<div id="app-body">
|
||||
{#if permissionError}
|
||||
<div class="error">
|
||||
<Layout justifyItems="center" gap="S">
|
||||
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
||||
{@html ErrorSVG}
|
||||
<Heading size="L">
|
||||
You don't have permission to use this app
|
||||
</Heading>
|
||||
<Body size="S">
|
||||
Ask your administrator to grant you access
|
||||
</Body>
|
||||
</Layout>
|
||||
</div>
|
||||
{:else if !$screenStore.activeLayout}
|
||||
<div class="error">
|
||||
<Layout justifyItems="center" gap="S">
|
||||
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
||||
{@html ErrorSVG}
|
||||
<Heading size="L">
|
||||
Something went wrong rendering your app
|
||||
</Heading>
|
||||
<Body size="S">
|
||||
Get in touch with support if this issue persists
|
||||
</Body>
|
||||
</Layout>
|
||||
</div>
|
||||
{:else if embedNoScreens}
|
||||
<div class="error">
|
||||
<Layout justifyItems="center" gap="S">
|
||||
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
||||
{@html ErrorSVG}
|
||||
<Heading size="L">
|
||||
This Budibase app is not publicly accessible
|
||||
</Heading>
|
||||
</Layout>
|
||||
</div>
|
||||
{:else}
|
||||
<CustomThemeWrapper>
|
||||
{#key $screenStore.activeLayout._id}
|
||||
<Component
|
||||
isLayout
|
||||
instance={$screenStore.activeLayout.props}
|
||||
/>
|
||||
{/key}
|
||||
|
||||
<!--
|
||||
Flatpickr needs to be inside the theme wrapper.
|
||||
It also needs its own container because otherwise it hijacks
|
||||
key events on the whole page. It is painful to work with.
|
||||
-->
|
||||
<div id="flatpickr-root" />
|
||||
|
||||
<!-- Modal container to ensure they sit on top -->
|
||||
<div class="modal-container" />
|
||||
|
||||
<!-- Layers on top of app -->
|
||||
<NotificationDisplay />
|
||||
<ConfirmationDisplay />
|
||||
<PeekScreenDisplay />
|
||||
</CustomThemeWrapper>
|
||||
{/if}
|
||||
|
||||
{#if showDevTools}
|
||||
<DevTools />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if !$builderStore.inBuilder && licensing.logoEnabled()}
|
||||
<FreeFooter />
|
||||
{#if showDevTools}
|
||||
<DevTools />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Preview and dev tools utilities -->
|
||||
{#if $appStore.isDevApp}
|
||||
<SelectionIndicator />
|
||||
{/if}
|
||||
{#if $builderStore.inBuilder || $devToolsStore.allowSelection}
|
||||
<HoverIndicator />
|
||||
{/if}
|
||||
{#if $builderStore.inBuilder}
|
||||
<DNDHandler />
|
||||
<GridDNDHandler />
|
||||
{#if !$builderStore.inBuilder && licensing.logoEnabled()}
|
||||
<FreeFooter />
|
||||
{/if}
|
||||
</div>
|
||||
</QueryParamsProvider>
|
||||
</RowSelectionProvider>
|
||||
</StateBindingsProvider>
|
||||
</UserBindingsProvider>
|
||||
</DeviceBindingsProvider>
|
||||
</div>
|
||||
<KeyboardManager />
|
||||
{/if}
|
||||
|
||||
<!-- Preview and dev tools utilities -->
|
||||
{#if $appStore.isDevApp}
|
||||
<SelectionIndicator />
|
||||
{/if}
|
||||
{#if $builderStore.inBuilder || $devToolsStore.allowSelection}
|
||||
<HoverIndicator />
|
||||
{/if}
|
||||
{#if $builderStore.inBuilder}
|
||||
<DNDHandler />
|
||||
<GridDNDHandler />
|
||||
{/if}
|
||||
</div>
|
||||
</QueryParamsProvider>
|
||||
</RowSelectionProvider>
|
||||
</StateBindingsProvider>
|
||||
</UserBindingsProvider>
|
||||
</DeviceBindingsProvider>
|
||||
</div>
|
||||
<KeyboardManager />
|
||||
|
||||
<style>
|
||||
#spectrum-root {
|
||||
height: 0;
|
||||
visibility: hidden;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
@ -257,6 +270,11 @@
|
|||
background-color: transparent;
|
||||
}
|
||||
|
||||
#spectrum-root.show {
|
||||
height: 100%;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
#app-root {
|
||||
overflow: hidden;
|
||||
height: 100%;
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
|
||||
<style>
|
||||
.free-footer {
|
||||
min-height: 51px;
|
||||
flex: 0 0 auto;
|
||||
padding: 16px 20px;
|
||||
border-top: 1px solid var(--spectrum-global-color-gray-300);
|
||||
|
|
|
@ -0,0 +1,244 @@
|
|||
<script>
|
||||
export let sideNav = false
|
||||
export let hideDevTools = false
|
||||
export let hideFooter = false
|
||||
export let noAnimation = false
|
||||
</script>
|
||||
|
||||
<div class:sideNav id="clientAppSkeletonLoader" class="skeleton">
|
||||
<div class="animation" class:noAnimation />
|
||||
|
||||
{#if !hideDevTools}
|
||||
<div class="devTools" />
|
||||
{/if}
|
||||
<div class="main">
|
||||
<div class="nav" />
|
||||
<div class="body">
|
||||
<div class="bodyVerticalPadding" />
|
||||
<div class="bodyHorizontal">
|
||||
<div class="bodyHorizontalPadding" />
|
||||
<svg
|
||||
class="svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
width="240"
|
||||
height="256"
|
||||
>
|
||||
<mask id="mask">
|
||||
<rect x="0" y="0" width="240" height="256" fill="white" />
|
||||
<rect x="0" y="0" width="240" height="32" rx="6" fill="black" />
|
||||
<rect x="0" y="56" width="240" height="32" rx="6" fill="black" />
|
||||
<rect x="0" y="112" width="240" height="32" rx="6" fill="black" />
|
||||
<rect x="0" y="168" width="240" height="32" rx="6" fill="black" />
|
||||
<rect x="71" y="224" width="98" height="32" rx="6" fill="black" />
|
||||
</mask>
|
||||
|
||||
<rect
|
||||
x="0"
|
||||
y="0"
|
||||
width="240"
|
||||
height="256"
|
||||
fill="black"
|
||||
mask="url(#mask)"
|
||||
/>
|
||||
</svg>
|
||||
<div class="bodyHorizontalPadding" />
|
||||
</div>
|
||||
<div class="bodyVerticalPadding" />
|
||||
</div>
|
||||
</div>
|
||||
{#if !hideFooter}
|
||||
<div class="footer" />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.skeleton {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
background-color: var(--spectrum-global-color-gray-200);
|
||||
}
|
||||
|
||||
.animation {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background: linear-gradient(
|
||||
to right,
|
||||
transparent 0%,
|
||||
var(--spectrum-global-color-gray-300) 20%,
|
||||
transparent 40%,
|
||||
transparent 100%
|
||||
);
|
||||
animation-duration: 1.3s;
|
||||
animation-fill-mode: forwards;
|
||||
animation-iteration-count: infinite;
|
||||
animation-name: shimmer;
|
||||
animation-timing-function: linear;
|
||||
}
|
||||
|
||||
.noAnimation {
|
||||
animation-name: none;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.devTools {
|
||||
display: flex;
|
||||
box-sizing: border-box;
|
||||
background-color: black;
|
||||
height: 60px;
|
||||
padding: 1px 24px 1px 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
z-index: 1;
|
||||
flex-shrink: 0;
|
||||
|
||||
color: white;
|
||||
mix-blend-mode: multiply;
|
||||
background: rgb(0 0 0);
|
||||
font-size: 30px;
|
||||
font-family: Source Sans Pro;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
.main {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
@media (max-width: 720px) {
|
||||
#clientAppSkeletonLoader .main {
|
||||
flex-direction: column;
|
||||
width: initial;
|
||||
}
|
||||
}
|
||||
|
||||
@container (max-width: 720px) {
|
||||
#clientAppSkeletonLoader .main {
|
||||
flex-direction: column;
|
||||
width: initial;
|
||||
}
|
||||
}
|
||||
|
||||
.sideNav .main {
|
||||
flex-direction: row;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.nav {
|
||||
flex-shrink: 0;
|
||||
width: 100%;
|
||||
height: 141px;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
@media (max-width: 720px) {
|
||||
#clientAppSkeletonLoader .nav {
|
||||
height: 61px;
|
||||
width: initial;
|
||||
}
|
||||
}
|
||||
|
||||
@container (max-width: 720px) {
|
||||
#clientAppSkeletonLoader .nav {
|
||||
height: 61px;
|
||||
width: initial;
|
||||
}
|
||||
}
|
||||
|
||||
.sideNav .nav {
|
||||
height: 100%;
|
||||
width: 251px;
|
||||
}
|
||||
|
||||
.body {
|
||||
z-index: 2;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@media (max-width: 720px) {
|
||||
#clientAppSkeletonLoader .body {
|
||||
width: initial;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@container (max-width: 720px) {
|
||||
#clientAppSkeletonLoader .body {
|
||||
width: initial;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.sideNav .body {
|
||||
width: 100%;
|
||||
height: initial;
|
||||
}
|
||||
|
||||
.body :global(svg > rect) {
|
||||
fill: var(--spectrum-alias-background-color-primary);
|
||||
}
|
||||
|
||||
.body :global(svg) {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.bodyHorizontal {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.bodyHorizontalPadding {
|
||||
height: 100%;
|
||||
flex-grow: 1;
|
||||
background-color: var(--spectrum-alias-background-color-primary);
|
||||
}
|
||||
|
||||
.bodyVerticalPadding {
|
||||
width: 100%;
|
||||
flex-grow: 1;
|
||||
background-color: var(--spectrum-alias-background-color-primary);
|
||||
}
|
||||
|
||||
.footer {
|
||||
flex-shrink: 0;
|
||||
box-sizing: border-box;
|
||||
z-index: 1;
|
||||
height: 52px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@media (max-width: 720px) {
|
||||
#clientAppSkeletonLoader .footer {
|
||||
border-top: none;
|
||||
}
|
||||
}
|
||||
|
||||
@container (max-width: 720px) {
|
||||
#clientAppSkeletonLoader .footer {
|
||||
border-top: none;
|
||||
}
|
||||
}
|
||||
|
||||
.sideNav .footer {
|
||||
border-top: 3px solid var(--spectrum-alias-background-color-primary);
|
||||
}
|
||||
|
||||
@keyframes shimmer {
|
||||
0% {
|
||||
left: -170%;
|
||||
}
|
||||
|
||||
100% {
|
||||
left: 170%;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -5,3 +5,4 @@ export { default as UserAvatar } from "./UserAvatar.svelte"
|
|||
export { default as UserAvatars } from "./UserAvatars.svelte"
|
||||
export { default as Updating } from "./Updating.svelte"
|
||||
export { Grid } from "./grid"
|
||||
export { default as ClientAppSkeleton } from "./ClientAppSkeleton.svelte"
|
||||
|
|
|
@ -17,5 +17,8 @@
|
|||
--modal-background: var(--spectrum-global-color-gray-50);
|
||||
--drop-shadow: rgba(0, 0, 0, 0.25) !important;
|
||||
--spectrum-global-color-blue-100: rgba(35, 40, 50) !important;
|
||||
|
||||
--spectrum-alias-background-color-secondary: var(--spectrum-global-color-gray-75);
|
||||
--spectrum-alias-background-color-primary: var(--spectrum-global-color-gray-100);
|
||||
}
|
||||
|
||||
|
|
|
@ -50,4 +50,7 @@
|
|||
--modal-background: var(--spectrum-global-color-gray-50);
|
||||
--drop-shadow: rgba(0, 0, 0, 0.15) !important;
|
||||
--spectrum-global-color-blue-100: rgb(56, 65, 84) !important;
|
||||
|
||||
--spectrum-alias-background-color-secondary: var(--spectrum-global-color-gray-75);
|
||||
--spectrum-alias-background-color-primary: var(--spectrum-global-color-gray-100);
|
||||
}
|
||||
|
|
|
@ -52,6 +52,7 @@
|
|||
"@budibase/pro": "0.0.0",
|
||||
"@budibase/shared-core": "0.0.0",
|
||||
"@budibase/string-templates": "0.0.0",
|
||||
"@budibase/frontend-core": "0.0.0",
|
||||
"@budibase/types": "0.0.0",
|
||||
"@bull-board/api": "5.10.2",
|
||||
"@bull-board/koa": "5.10.2",
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
import { InvalidFileExtensions } from "@budibase/shared-core"
|
||||
|
||||
import AppComponent from "./templates/BudibaseApp.svelte"
|
||||
|
||||
import { join } from "../../../utilities/centralPath"
|
||||
import * as uuid from "uuid"
|
||||
import { ObjectStoreBuckets } from "../../../constants"
|
||||
|
@ -24,7 +22,13 @@ import AWS from "aws-sdk"
|
|||
import fs from "fs"
|
||||
import sdk from "../../../sdk"
|
||||
import * as pro from "@budibase/pro"
|
||||
import { App, Ctx, ProcessAttachmentResponse } from "@budibase/types"
|
||||
import {
|
||||
UserCtx,
|
||||
App,
|
||||
Ctx,
|
||||
ProcessAttachmentResponse,
|
||||
Feature,
|
||||
} from "@budibase/types"
|
||||
import {
|
||||
getAppMigrationVersion,
|
||||
getLatestMigrationId,
|
||||
|
@ -32,6 +36,61 @@ import {
|
|||
|
||||
import send from "koa-send"
|
||||
|
||||
const getThemeVariables = (theme: string) => {
|
||||
if (theme === "spectrum--lightest") {
|
||||
return `
|
||||
--spectrum-global-color-gray-50: rgb(255, 255, 255);
|
||||
--spectrum-global-color-gray-200: rgb(244, 244, 244);
|
||||
--spectrum-global-color-gray-300: rgb(234, 234, 234);
|
||||
--spectrum-alias-background-color-primary: var(--spectrum-global-color-gray-50);
|
||||
`
|
||||
}
|
||||
if (theme === "spectrum--light") {
|
||||
return `
|
||||
--spectrum-global-color-gray-50: rgb(255, 255, 255);
|
||||
--spectrum-global-color-gray-200: rgb(234, 234, 234);
|
||||
--spectrum-global-color-gray-300: rgb(225, 225, 225);
|
||||
--spectrum-alias-background-color-primary: var(--spectrum-global-color-gray-50);
|
||||
|
||||
`
|
||||
}
|
||||
if (theme === "spectrum--dark") {
|
||||
return `
|
||||
--spectrum-global-color-gray-100: rgb(50, 50, 50);
|
||||
--spectrum-global-color-gray-200: rgb(62, 62, 62);
|
||||
--spectrum-global-color-gray-300: rgb(74, 74, 74);
|
||||
--spectrum-alias-background-color-primary: var(--spectrum-global-color-gray-100);
|
||||
`
|
||||
}
|
||||
if (theme === "spectrum--darkest") {
|
||||
return `
|
||||
--spectrum-global-color-gray-100: rgb(30, 30, 30);
|
||||
--spectrum-global-color-gray-200: rgb(44, 44, 44);
|
||||
--spectrum-global-color-gray-300: rgb(57, 57, 57);
|
||||
--spectrum-alias-background-color-primary: var(--spectrum-global-color-gray-100);
|
||||
`
|
||||
}
|
||||
if (theme === "spectrum--nord") {
|
||||
return `
|
||||
--spectrum-global-color-gray-100: #3b4252;
|
||||
|
||||
--spectrum-global-color-gray-200: #424a5c;
|
||||
--spectrum-global-color-gray-300: #4c566a;
|
||||
--spectrum-alias-background-color-primary: var(--spectrum-global-color-gray-100);
|
||||
`
|
||||
}
|
||||
if (theme === "spectrum--midnight") {
|
||||
return `
|
||||
--hue: 220;
|
||||
--sat: 10%;
|
||||
--spectrum-global-color-gray-100: hsl(var(--hue), var(--sat), 17%);
|
||||
--spectrum-global-color-gray-200: hsl(var(--hue), var(--sat), 20%);
|
||||
--spectrum-global-color-gray-300: hsl(var(--hue), var(--sat), 24%);
|
||||
--spectrum-alias-background-color-primary: var(--spectrum-global-color-gray-100);
|
||||
`
|
||||
}
|
||||
}
|
||||
|
||||
export const toggleBetaUiFeature = async function (ctx: Ctx) {
|
||||
const cookieName = `beta:${ctx.params.feature}`
|
||||
|
||||
|
@ -146,7 +205,7 @@ const requiresMigration = async (ctx: Ctx) => {
|
|||
return requiresMigrations
|
||||
}
|
||||
|
||||
export const serveApp = async function (ctx: Ctx) {
|
||||
export const serveApp = async function (ctx: UserCtx) {
|
||||
const needMigrations = await requiresMigration(ctx)
|
||||
|
||||
const bbHeaderEmbed =
|
||||
|
@ -167,9 +226,19 @@ export const serveApp = async function (ctx: Ctx) {
|
|||
const appInfo = await db.get<any>(DocumentType.APP_METADATA)
|
||||
let appId = context.getAppId()
|
||||
|
||||
const hideDevTools = !!ctx.params.appUrl
|
||||
const sideNav = appInfo.navigation.navigation === "Left"
|
||||
const hideFooter =
|
||||
ctx?.user?.license?.features?.includes(Feature.BRANDING) || false
|
||||
const themeVariables = getThemeVariables(appInfo?.theme)
|
||||
|
||||
if (!env.isJest()) {
|
||||
const plugins = objectStore.enrichPluginURLs(appInfo.usedPlugins)
|
||||
|
||||
const { head, html, css } = AppComponent.render({
|
||||
hideDevTools,
|
||||
sideNav,
|
||||
hideFooter,
|
||||
metaImage:
|
||||
branding?.metaImageUrl ||
|
||||
"https://res.cloudinary.com/daog6scxm/image/upload/v1698759482/meta-images/plain-branded-meta-image-coral_ocxmgu.png",
|
||||
|
@ -194,7 +263,7 @@ export const serveApp = async function (ctx: Ctx) {
|
|||
ctx.body = await processString(appHbs, {
|
||||
head,
|
||||
body: html,
|
||||
style: css.code,
|
||||
css: `:root{${themeVariables}} ${css.code}`,
|
||||
appId,
|
||||
embedded: bbHeaderEmbed,
|
||||
})
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
<script>
|
||||
import ClientAppSkeleton from "@budibase/frontend-core/src/components/ClientAppSkeleton.svelte"
|
||||
|
||||
export let title = ""
|
||||
export let favicon = ""
|
||||
|
||||
|
@ -9,6 +11,10 @@
|
|||
export let clientLibPath
|
||||
export let usedPlugins
|
||||
export let appMigrating
|
||||
|
||||
export let hideDevTools
|
||||
export let sideNav
|
||||
export let hideFooter
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
|
@ -96,6 +102,7 @@
|
|||
</svelte:head>
|
||||
|
||||
<body id="app">
|
||||
<ClientAppSkeleton {hideDevTools} {sideNav} {hideFooter} />
|
||||
<div id="error">
|
||||
{#if clientLibPath}
|
||||
<h1>There was an error loading your app</h1>
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
<html>
|
||||
|
||||
<script>
|
||||
document.fonts.ready.then(() => {
|
||||
window.parent.postMessage({ type: "docLoaded" });
|
||||
})
|
||||
</script>
|
||||
<head>
|
||||
{{{head}}}
|
||||
<style>{{{style}}}</style>
|
||||
<style>{{{css}}}</style>
|
||||
</head>
|
||||
|
||||
<script>
|
||||
|
|
|
@ -51,8 +51,8 @@ router
|
|||
controller.deleteObjects
|
||||
)
|
||||
.get("/app/preview", authorized(BUILDER), controller.serveBuilderPreview)
|
||||
.get("/:appId/:path*", controller.serveApp)
|
||||
.get("/app/:appUrl/:path*", controller.serveApp)
|
||||
.get("/:appId/:path*", controller.serveApp)
|
||||
.post(
|
||||
"/api/attachments/:datasourceId/url",
|
||||
authorized(PermissionType.TABLE, PermissionLevel.READ),
|
||||
|
|
|
@ -17,12 +17,6 @@ const { nodeExternalsPlugin } = require("esbuild-node-externals")
|
|||
const svelteCompilePlugin = {
|
||||
name: 'svelteCompile',
|
||||
setup(build) {
|
||||
// This resolve handler is necessary to bundle the Svelte runtime into the the final output,
|
||||
// otherwise the bundled script will attempt to resolve it at runtime
|
||||
build.onResolve({ filter: /svelte\/internal/ }, async () => {
|
||||
return { path: `${process.cwd()}/../../node_modules/svelte/src/runtime/internal/ssr.js` }
|
||||
})
|
||||
|
||||
// Compiles `.svelte` files into JS classes so that they can be directly imported into our
|
||||
// Typescript packages
|
||||
build.onLoad({ filter: /\.svelte$/ }, async (args) => {
|
||||
|
@ -80,11 +74,11 @@ async function runBuild(entry, outfile) {
|
|||
plugins: [
|
||||
svelteCompilePlugin,
|
||||
TsconfigPathsPlugin({ tsconfig: tsconfigPathPluginContent }),
|
||||
nodeExternalsPlugin(),
|
||||
nodeExternalsPlugin({
|
||||
allowList: ["@budibase/frontend-core", "svelte"]
|
||||
}),
|
||||
],
|
||||
preserveSymlinks: true,
|
||||
loader: {
|
||||
},
|
||||
metafile: true,
|
||||
external: [
|
||||
"deasync",
|
||||
|
|
Loading…
Reference in New Issue