2020-11-13 16:42:32 +01:00
|
|
|
<script>
|
2021-10-28 14:00:55 +02:00
|
|
|
import { writable, get } from "svelte/store"
|
2020-11-19 19:39:22 +01:00
|
|
|
import { setContext, onMount } from "svelte"
|
2021-09-01 12:41:48 +02:00
|
|
|
import { Layout, Heading, Body } from "@budibase/bbui"
|
2022-01-20 10:40:53 +01:00
|
|
|
import ErrorSVG from "@budibase/frontend-core/assets/error.svg"
|
2022-01-25 16:28:14 +01:00
|
|
|
import { Constants, CookieUtils } from "@budibase/frontend-core"
|
2020-11-13 16:42:32 +01:00
|
|
|
import Component from "./Component.svelte"
|
2021-09-01 12:41:48 +02:00
|
|
|
import SDK from "sdk"
|
2021-02-01 19:51:22 +01:00
|
|
|
import {
|
2024-03-25 17:39:42 +01:00
|
|
|
featuresStore,
|
2021-02-01 19:51:22 +01:00
|
|
|
createContextStore,
|
|
|
|
initialise,
|
|
|
|
screenStore,
|
|
|
|
authStore,
|
2021-05-20 15:47:17 +02:00
|
|
|
routeStore,
|
|
|
|
builderStore,
|
2021-09-02 12:38:41 +02:00
|
|
|
themeStore,
|
2021-11-26 14:25:02 +01:00
|
|
|
appStore,
|
|
|
|
devToolsStore,
|
2023-06-08 16:12:50 +02:00
|
|
|
devToolsEnabled,
|
2024-03-14 10:57:00 +01:00
|
|
|
environmentStore,
|
2024-06-17 09:21:26 +02:00
|
|
|
sidePanelStore,
|
2024-06-18 12:18:05 +02:00
|
|
|
modalStore,
|
2021-09-01 12:41:48 +02:00
|
|
|
} from "stores"
|
|
|
|
import NotificationDisplay from "components/overlay/NotificationDisplay.svelte"
|
|
|
|
import ConfirmationDisplay from "components/overlay/ConfirmationDisplay.svelte"
|
|
|
|
import PeekScreenDisplay from "components/overlay/PeekScreenDisplay.svelte"
|
|
|
|
import UserBindingsProvider from "components/context/UserBindingsProvider.svelte"
|
|
|
|
import DeviceBindingsProvider from "components/context/DeviceBindingsProvider.svelte"
|
2021-09-01 17:10:36 +02:00
|
|
|
import StateBindingsProvider from "components/context/StateBindingsProvider.svelte"
|
2022-02-11 12:55:35 +01:00
|
|
|
import RowSelectionProvider from "components/context/RowSelectionProvider.svelte"
|
2022-06-21 10:39:25 +02:00
|
|
|
import QueryParamsProvider from "components/context/QueryParamsProvider.svelte"
|
2021-09-01 12:41:48 +02:00
|
|
|
import SettingsBar from "components/preview/SettingsBar.svelte"
|
|
|
|
import SelectionIndicator from "components/preview/SelectionIndicator.svelte"
|
|
|
|
import HoverIndicator from "components/preview/HoverIndicator.svelte"
|
2021-09-03 12:50:09 +02:00
|
|
|
import CustomThemeWrapper from "./CustomThemeWrapper.svelte"
|
2021-09-16 08:28:59 +02:00
|
|
|
import DNDHandler from "components/preview/DNDHandler.svelte"
|
2022-10-24 10:38:07 +02:00
|
|
|
import GridDNDHandler from "components/preview/GridDNDHandler.svelte"
|
2021-10-28 14:00:55 +02:00
|
|
|
import KeyboardManager from "components/preview/KeyboardManager.svelte"
|
2021-11-26 14:25:02 +01:00
|
|
|
import DevToolsHeader from "components/devtools/DevToolsHeader.svelte"
|
|
|
|
import DevTools from "components/devtools/DevTools.svelte"
|
2023-04-19 15:21:11 +02:00
|
|
|
import FreeFooter from "components/FreeFooter.svelte"
|
2024-03-14 10:57:00 +01:00
|
|
|
import MaintenanceScreen from "components/MaintenanceScreen.svelte"
|
2024-03-14 17:16:24 +01:00
|
|
|
import SnippetsProvider from "./context/SnippetsProvider.svelte"
|
2020-11-13 16:42:32 +01:00
|
|
|
|
2020-11-20 10:50:10 +01:00
|
|
|
// Provide contexts
|
|
|
|
setContext("sdk", SDK)
|
2023-03-28 22:11:33 +02:00
|
|
|
setContext("component", writable({ id: null, ancestors: [] }))
|
2021-02-01 19:51:22 +01:00
|
|
|
setContext("context", createContextStore())
|
2020-11-13 16:42:32 +01:00
|
|
|
|
2021-05-20 15:47:17 +02:00
|
|
|
let dataLoaded = false
|
2021-07-07 12:29:35 +02:00
|
|
|
let permissionError = false
|
2023-06-16 13:30:08 +02:00
|
|
|
let embedNoScreens = false
|
2020-11-19 19:39:22 +01:00
|
|
|
|
2022-04-28 16:13:33 +02:00
|
|
|
// Determine if we should show devtools or not
|
2023-06-08 16:12:50 +02:00
|
|
|
$: showDevTools = $devToolsEnabled && !$routeStore.queryParams?.peek
|
2021-02-26 15:04:31 +01:00
|
|
|
|
2022-04-28 16:13:33 +02:00
|
|
|
// Handle no matching route
|
2021-05-20 15:47:17 +02:00
|
|
|
$: {
|
|
|
|
if (dataLoaded && $routeStore.routerLoaded && !$routeStore.activeRoute) {
|
2022-06-17 11:09:27 +02:00
|
|
|
if ($screenStore.screens.length) {
|
2022-06-17 11:18:39 +02:00
|
|
|
// If we have some available screens, use the first screen which
|
|
|
|
// represents the best route based on rank
|
|
|
|
const route = $screenStore.screens[0].routing?.route
|
|
|
|
if (!route) {
|
2021-07-07 12:29:35 +02:00
|
|
|
permissionError = true
|
2022-06-17 11:18:39 +02:00
|
|
|
console.error("No route found but screens exist")
|
|
|
|
} else {
|
|
|
|
permissionError = false
|
|
|
|
routeStore.actions.navigate(route)
|
2021-07-07 12:29:35 +02:00
|
|
|
}
|
2022-06-17 11:09:27 +02:00
|
|
|
} else if ($authStore) {
|
|
|
|
// If the user is logged in but has no screens, they don't have
|
|
|
|
// permission to use the app
|
|
|
|
permissionError = true
|
2023-06-16 13:30:08 +02:00
|
|
|
} else if ($appStore.embedded) {
|
|
|
|
embedNoScreens = true
|
2021-05-20 15:47:17 +02:00
|
|
|
} else {
|
2022-06-17 11:09:27 +02:00
|
|
|
// If they have no screens and are not logged in, it probably means
|
|
|
|
// they should log in to gain access
|
2021-05-20 15:47:17 +02:00
|
|
|
const returnUrl = `${window.location.pathname}${window.location.hash}`
|
2022-01-25 16:28:14 +01:00
|
|
|
CookieUtils.setCookie(Constants.Cookies.ReturnUrl, returnUrl)
|
|
|
|
window.location = "/builder/auth/login"
|
2021-05-20 15:47:17 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-11-26 14:25:02 +01:00
|
|
|
|
2024-03-25 17:39:42 +01:00
|
|
|
let fontsLoaded = false
|
|
|
|
|
2022-04-28 16:13:33 +02:00
|
|
|
// Load app config
|
|
|
|
onMount(async () => {
|
2024-03-25 17:39:42 +01:00
|
|
|
document.fonts.ready.then(() => {
|
|
|
|
fontsLoaded = true
|
|
|
|
})
|
|
|
|
|
2022-04-28 16:13:33 +02:00
|
|
|
await initialise()
|
|
|
|
await authStore.actions.fetchUser()
|
|
|
|
dataLoaded = true
|
2024-03-25 17:39:42 +01:00
|
|
|
|
2022-04-28 16:13:33 +02:00
|
|
|
if (get(builderStore).inBuilder) {
|
|
|
|
builderStore.actions.notifyLoaded()
|
2022-08-10 12:01:54 +02:00
|
|
|
} else {
|
2023-06-16 13:30:08 +02:00
|
|
|
builderStore.actions.analyticsPing({
|
2023-06-23 12:56:59 +02:00
|
|
|
embedded: !!$appStore.embedded,
|
2023-06-16 13:30:08 +02:00
|
|
|
})
|
2022-04-28 16:13:33 +02:00
|
|
|
}
|
2024-06-17 09:21:26 +02:00
|
|
|
const handleHashChange = () => {
|
2024-06-18 12:18:05 +02:00
|
|
|
const { open: sidePanelOpen } = $sidePanelStore
|
2024-07-16 13:59:43 +02:00
|
|
|
// only close if the sidepanel is open and theres no onload side panel actions on the screen.
|
|
|
|
if (
|
|
|
|
sidePanelOpen &&
|
|
|
|
!$screenStore.activeScreen.onLoad?.some(
|
|
|
|
item => item["##eventHandlerType"] === "Open Side Panel"
|
|
|
|
)
|
|
|
|
) {
|
2024-06-17 09:21:26 +02:00
|
|
|
sidePanelStore.actions.close()
|
|
|
|
}
|
2024-06-18 12:18:05 +02:00
|
|
|
|
|
|
|
const { open: modalOpen } = $modalStore
|
2024-07-16 13:59:43 +02:00
|
|
|
// only close if the modal is open and theres onload modals actions on the screen.
|
|
|
|
if (
|
|
|
|
modalOpen &&
|
|
|
|
!$screenStore.activeScreen.onLoad?.some(
|
|
|
|
item => item["##eventHandlerType"] === "Open Modal"
|
|
|
|
)
|
|
|
|
) {
|
2024-06-18 12:18:05 +02:00
|
|
|
modalStore.actions.close()
|
|
|
|
}
|
2024-06-17 09:21:26 +02:00
|
|
|
}
|
|
|
|
window.addEventListener("hashchange", handleHashChange)
|
|
|
|
return () => {
|
|
|
|
window.removeEventListener("hashchange", handleHashChange)
|
|
|
|
}
|
2022-04-28 16:13:33 +02:00
|
|
|
})
|
2024-03-25 17:39:42 +01:00
|
|
|
|
|
|
|
$: {
|
|
|
|
if (dataLoaded && fontsLoaded) {
|
|
|
|
document.getElementById("clientAppSkeletonLoader")?.remove()
|
|
|
|
}
|
|
|
|
}
|
2020-11-13 16:42:32 +01:00
|
|
|
</script>
|
|
|
|
|
2022-08-11 18:05:42 +02:00
|
|
|
<svelte:head>
|
|
|
|
{#if $builderStore.usedPlugins?.length}
|
2023-04-19 11:41:23 +02:00
|
|
|
{#each $builderStore.usedPlugins as plugin (plugin.hash)}
|
2022-12-15 12:35:22 +01:00
|
|
|
<script src={`${plugin.jsUrl}`}></script>
|
2022-08-11 18:05:42 +02:00
|
|
|
{/each}
|
|
|
|
{/if}
|
|
|
|
</svelte:head>
|
|
|
|
|
2024-04-17 17:26:18 +02:00
|
|
|
{#if dataLoaded}
|
|
|
|
<div
|
|
|
|
id="spectrum-root"
|
|
|
|
lang="en"
|
|
|
|
dir="ltr"
|
|
|
|
class="spectrum spectrum--medium {$themeStore.baseTheme} {$themeStore.theme}"
|
|
|
|
class:builder={$builderStore.inBuilder}
|
|
|
|
class:show={fontsLoaded && dataLoaded}
|
|
|
|
>
|
|
|
|
{#if $environmentStore.maintenance.length > 0}
|
|
|
|
<MaintenanceScreen maintenanceList={$environmentStore.maintenance} />
|
|
|
|
{:else}
|
|
|
|
<DeviceBindingsProvider>
|
|
|
|
<UserBindingsProvider>
|
|
|
|
<StateBindingsProvider>
|
|
|
|
<RowSelectionProvider>
|
|
|
|
<QueryParamsProvider>
|
|
|
|
<SnippetsProvider>
|
|
|
|
<!-- 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 />
|
2024-03-25 17:39:42 +01:00
|
|
|
{/if}
|
2024-04-17 17:26:18 +02:00
|
|
|
{/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 />
|
|
|
|
{/if}
|
2024-03-14 17:24:16 +01:00
|
|
|
|
2024-04-17 17:26:18 +02:00
|
|
|
<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}
|
2022-06-21 10:39:25 +02:00
|
|
|
|
2024-04-17 17:26:18 +02:00
|
|
|
<!-- Modal container to ensure they sit on top -->
|
|
|
|
<div class="modal-container" />
|
2022-06-21 10:39:25 +02:00
|
|
|
|
2024-04-17 17:26:18 +02:00
|
|
|
<!-- Layers on top of app -->
|
|
|
|
<NotificationDisplay />
|
|
|
|
<ConfirmationDisplay />
|
|
|
|
<PeekScreenDisplay />
|
|
|
|
</CustomThemeWrapper>
|
|
|
|
{/if}
|
2024-03-14 17:24:16 +01:00
|
|
|
|
2024-04-17 17:26:18 +02:00
|
|
|
{#if showDevTools}
|
|
|
|
<DevTools />
|
|
|
|
{/if}
|
|
|
|
</div>
|
|
|
|
|
|
|
|
{#if !$builderStore.inBuilder && $featuresStore.logoEnabled}
|
|
|
|
<FreeFooter />
|
2024-03-14 17:16:24 +01:00
|
|
|
{/if}
|
|
|
|
</div>
|
|
|
|
|
2024-04-17 17:26:18 +02:00
|
|
|
<!-- Preview and dev tools utilities -->
|
|
|
|
{#if $appStore.isDevApp}
|
|
|
|
<SelectionIndicator />
|
|
|
|
{/if}
|
|
|
|
{#if $builderStore.inBuilder || $devToolsStore.allowSelection}
|
|
|
|
<HoverIndicator />
|
|
|
|
{/if}
|
|
|
|
{#if $builderStore.inBuilder}
|
|
|
|
<DNDHandler />
|
|
|
|
<GridDNDHandler />
|
2022-06-21 10:39:25 +02:00
|
|
|
{/if}
|
|
|
|
</div>
|
2024-04-17 17:26:18 +02:00
|
|
|
</SnippetsProvider>
|
|
|
|
</QueryParamsProvider>
|
|
|
|
</RowSelectionProvider>
|
|
|
|
</StateBindingsProvider>
|
|
|
|
</UserBindingsProvider>
|
|
|
|
</DeviceBindingsProvider>
|
|
|
|
{/if}
|
|
|
|
</div>
|
|
|
|
<KeyboardManager />
|
|
|
|
{/if}
|
2021-05-13 17:32:52 +02:00
|
|
|
|
|
|
|
<style>
|
2021-09-08 10:40:25 +02:00
|
|
|
#spectrum-root {
|
2024-03-25 17:39:42 +01:00
|
|
|
height: 0;
|
|
|
|
visibility: hidden;
|
2021-06-24 13:14:19 +02:00
|
|
|
padding: 0;
|
|
|
|
margin: 0;
|
2021-06-08 16:16:37 +02:00
|
|
|
overflow: hidden;
|
2021-09-08 10:40:25 +02:00
|
|
|
width: 100%;
|
|
|
|
display: flex;
|
|
|
|
flex-direction: row;
|
|
|
|
justify-content: center;
|
|
|
|
align-items: center;
|
2021-06-08 16:16:37 +02:00
|
|
|
}
|
2023-06-09 12:03:49 +02:00
|
|
|
#spectrum-root.builder {
|
|
|
|
background: transparent;
|
|
|
|
}
|
2022-02-24 15:03:29 +01:00
|
|
|
|
2021-09-08 11:28:19 +02:00
|
|
|
#clip-root {
|
2021-09-08 10:40:25 +02:00
|
|
|
max-width: 100%;
|
|
|
|
max-height: 100%;
|
|
|
|
width: 100%;
|
|
|
|
height: 100%;
|
2021-05-13 17:32:52 +02:00
|
|
|
position: relative;
|
2021-09-08 10:40:25 +02:00
|
|
|
overflow: hidden;
|
|
|
|
background-color: transparent;
|
2021-05-13 17:32:52 +02:00
|
|
|
}
|
2022-02-24 15:03:29 +01:00
|
|
|
|
2024-03-25 17:39:42 +01:00
|
|
|
#spectrum-root.show {
|
|
|
|
height: 100%;
|
|
|
|
visibility: visible;
|
|
|
|
}
|
|
|
|
|
2021-09-08 10:40:25 +02:00
|
|
|
#app-root {
|
|
|
|
overflow: hidden;
|
|
|
|
height: 100%;
|
|
|
|
width: 100%;
|
2021-11-26 14:25:02 +01:00
|
|
|
display: flex;
|
|
|
|
flex-direction: column;
|
|
|
|
justify-content: flex-start;
|
|
|
|
align-items: stretch;
|
|
|
|
}
|
2022-04-06 14:40:07 +02:00
|
|
|
|
2021-11-26 14:25:02 +01:00
|
|
|
#app-body {
|
|
|
|
flex: 1 1 auto;
|
|
|
|
display: flex;
|
|
|
|
flex-direction: row;
|
|
|
|
justify-content: flex-start;
|
|
|
|
align-items: stretch;
|
2022-02-24 16:36:21 +01:00
|
|
|
overflow: hidden;
|
2021-07-30 11:06:16 +02:00
|
|
|
}
|
2021-09-08 10:40:25 +02:00
|
|
|
|
2021-07-07 12:29:35 +02:00
|
|
|
.error {
|
|
|
|
width: 100%;
|
|
|
|
height: 100%;
|
|
|
|
display: grid;
|
|
|
|
place-items: center;
|
|
|
|
z-index: 1;
|
|
|
|
text-align: center;
|
|
|
|
padding: 20px;
|
|
|
|
}
|
|
|
|
.error :global(svg) {
|
|
|
|
fill: var(--spectrum-global-color-gray-500);
|
|
|
|
width: 80px;
|
|
|
|
height: 80px;
|
|
|
|
}
|
|
|
|
.error :global(h1),
|
|
|
|
.error :global(p) {
|
|
|
|
color: var(--spectrum-global-color-gray-800);
|
|
|
|
}
|
|
|
|
.error :global(p) {
|
|
|
|
font-style: italic;
|
|
|
|
margin-top: -0.5em;
|
|
|
|
}
|
|
|
|
.error :global(h1) {
|
|
|
|
font-weight: 400;
|
|
|
|
}
|
2021-09-08 10:40:25 +02:00
|
|
|
|
|
|
|
/* Preview styles */
|
2021-09-08 11:28:19 +02:00
|
|
|
#clip-root.preview {
|
2024-07-31 16:03:29 +02:00
|
|
|
padding: 6px;
|
2021-09-08 10:40:25 +02:00
|
|
|
}
|
2021-09-08 11:28:19 +02:00
|
|
|
#clip-root.tablet-preview {
|
2024-07-31 16:03:29 +02:00
|
|
|
width: calc(1024px + 12px);
|
|
|
|
height: calc(768px + 12px);
|
2021-09-08 10:40:25 +02:00
|
|
|
}
|
2021-09-08 11:28:19 +02:00
|
|
|
#clip-root.mobile-preview {
|
2024-07-31 16:03:29 +02:00
|
|
|
width: calc(390px + 12px);
|
|
|
|
height: calc(844px + 12px);
|
2021-09-08 10:40:25 +02:00
|
|
|
}
|
2022-03-10 12:54:15 +01:00
|
|
|
|
2022-03-10 12:56:31 +01:00
|
|
|
/* Print styles */
|
2022-03-10 12:54:15 +01:00
|
|
|
@media print {
|
|
|
|
#spectrum-root,
|
|
|
|
#clip-root,
|
2022-05-12 11:44:36 +02:00
|
|
|
#app-root,
|
|
|
|
#app-body {
|
2022-03-10 12:54:15 +01:00
|
|
|
overflow: visible !important;
|
|
|
|
}
|
|
|
|
}
|
2021-05-13 17:32:52 +02:00
|
|
|
</style>
|