Merge pull request #2576 from Budibase/responsive-preview
Responsive preview
This commit is contained in:
commit
10182e19c3
|
@ -43,6 +43,7 @@ const INITIAL_FRONTEND_STATE = {
|
||||||
deviceAwareness: false,
|
deviceAwareness: false,
|
||||||
state: false,
|
state: false,
|
||||||
customThemes: false,
|
customThemes: false,
|
||||||
|
devicePreview: false,
|
||||||
},
|
},
|
||||||
currentFrontEndType: "none",
|
currentFrontEndType: "none",
|
||||||
selectedScreenId: "",
|
selectedScreenId: "",
|
||||||
|
@ -56,6 +57,7 @@ const INITIAL_FRONTEND_STATE = {
|
||||||
clientLibPath: "",
|
clientLibPath: "",
|
||||||
theme: "",
|
theme: "",
|
||||||
customTheme: {},
|
customTheme: {},
|
||||||
|
previewDevice: "desktop",
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getFrontendStore = () => {
|
export const getFrontendStore = () => {
|
||||||
|
@ -230,6 +232,12 @@ export const getFrontendStore = () => {
|
||||||
await store.actions.layouts.save(selectedAsset)
|
await store.actions.layouts.save(selectedAsset)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
setDevice: device => {
|
||||||
|
store.update(state => {
|
||||||
|
state.previewDevice = device
|
||||||
|
return state
|
||||||
|
})
|
||||||
|
},
|
||||||
},
|
},
|
||||||
layouts: {
|
layouts: {
|
||||||
select: layoutId => {
|
select: layoutId => {
|
||||||
|
|
|
@ -83,10 +83,11 @@
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
margin-top: -10px;
|
|
||||||
}
|
}
|
||||||
.components :global(> *) {
|
.components :global(> *) {
|
||||||
margin-top: 10px;
|
height: 32px;
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.buttonContent {
|
.buttonContent {
|
||||||
|
|
|
@ -50,6 +50,7 @@
|
||||||
previewType: $store.currentFrontEndType,
|
previewType: $store.currentFrontEndType,
|
||||||
theme: $store.theme,
|
theme: $store.theme,
|
||||||
customTheme: $store.customTheme,
|
customTheme: $store.customTheme,
|
||||||
|
previewDevice: $store.previewDevice,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Saving pages and screens to the DB causes them to have _revs.
|
// Saving pages and screens to the DB causes them to have _revs.
|
||||||
|
@ -140,11 +141,12 @@
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<iframe
|
<iframe
|
||||||
style="height: 100%; width: 100%"
|
|
||||||
title="componentPreview"
|
title="componentPreview"
|
||||||
bind:this={iframe}
|
bind:this={iframe}
|
||||||
srcdoc={template}
|
srcdoc={template}
|
||||||
class:hidden={loading || error}
|
class:hidden={loading || error}
|
||||||
|
class:tablet={$store.previewDevice === "tablet"}
|
||||||
|
class:mobile={$store.previewDevice === "mobile"}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<ConfirmDialog
|
<ConfirmDialog
|
||||||
|
@ -160,6 +162,8 @@
|
||||||
.component-container {
|
.component-container {
|
||||||
grid-row-start: middle;
|
grid-row-start: middle;
|
||||||
grid-column-start: middle;
|
grid-column-start: middle;
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
|
@ -200,4 +204,9 @@
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
iframe {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
<script>
|
||||||
|
import { ActionGroup, ActionButton } from "@budibase/bbui"
|
||||||
|
import { store } from "builderStore"
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<ActionGroup compact>
|
||||||
|
<ActionButton
|
||||||
|
icon="DeviceDesktop"
|
||||||
|
selected={$store.previewDevice === "desktop"}
|
||||||
|
on:click={() => store.actions.preview.setDevice("desktop")}
|
||||||
|
/>
|
||||||
|
<ActionButton
|
||||||
|
icon="DeviceTablet"
|
||||||
|
selected={$store.previewDevice === "tablet"}
|
||||||
|
on:click={() => store.actions.preview.setDevice("tablet")}
|
||||||
|
/>
|
||||||
|
<ActionButton
|
||||||
|
icon="DevicePhone"
|
||||||
|
selected={$store.previewDevice === "mobile"}
|
||||||
|
on:click={() => store.actions.preview.setDevice("mobile")}
|
||||||
|
/>
|
||||||
|
</ActionGroup>
|
|
@ -17,18 +17,14 @@ export default `
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
html {
|
html {
|
||||||
height: calc(100% - 16px);
|
height: 100%;
|
||||||
width: calc(100% - 16px);
|
width: 100%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
margin: 8px;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
}
|
}
|
||||||
html.loaded {
|
|
||||||
box-shadow: 0 2px 8px -2px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
body {
|
body {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
@ -67,7 +63,8 @@ export default `
|
||||||
previewType,
|
previewType,
|
||||||
appId,
|
appId,
|
||||||
theme,
|
theme,
|
||||||
customTheme
|
customTheme,
|
||||||
|
previewDevice
|
||||||
} = parsed
|
} = parsed
|
||||||
|
|
||||||
// Set some flags so the app knows we're in the builder
|
// Set some flags so the app knows we're in the builder
|
||||||
|
@ -80,6 +77,7 @@ export default `
|
||||||
window["##BUDIBASE_PREVIEW_TYPE##"] = previewType
|
window["##BUDIBASE_PREVIEW_TYPE##"] = previewType
|
||||||
window["##BUDIBASE_PREVIEW_THEME##"] = theme
|
window["##BUDIBASE_PREVIEW_THEME##"] = theme
|
||||||
window["##BUDIBASE_PREVIEW_CUSTOM_THEME##"] = customTheme
|
window["##BUDIBASE_PREVIEW_CUSTOM_THEME##"] = customTheme
|
||||||
|
window["##BUDIBASE_PREVIEW_DEVICE##"] = previewDevice
|
||||||
|
|
||||||
// Initialise app
|
// Initialise app
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
import { get } from "svelte/store"
|
import { get } from "svelte/store"
|
||||||
import AppThemeSelect from "components/design/AppPreview/AppThemeSelect.svelte"
|
import AppThemeSelect from "components/design/AppPreview/AppThemeSelect.svelte"
|
||||||
import ThemeEditor from "components/design/AppPreview/ThemeEditor.svelte"
|
import ThemeEditor from "components/design/AppPreview/ThemeEditor.svelte"
|
||||||
|
import DevicePreviewSelect from "components/design/AppPreview/DevicePreviewSelect.svelte"
|
||||||
|
|
||||||
// Cache previous values so we don't update the URL more than necessary
|
// Cache previous values so we don't update the URL more than necessary
|
||||||
let previousType
|
let previousType
|
||||||
|
@ -151,6 +152,9 @@
|
||||||
{#if $currentAsset}
|
{#if $currentAsset}
|
||||||
<div class="preview-header">
|
<div class="preview-header">
|
||||||
<ComponentSelectionList />
|
<ComponentSelectionList />
|
||||||
|
{#if $store.clientFeatures.devicePreview}
|
||||||
|
<DevicePreviewSelect />
|
||||||
|
{/if}
|
||||||
{#if $store.clientFeatures.customThemes}
|
{#if $store.clientFeatures.customThemes}
|
||||||
<ThemeEditor />
|
<ThemeEditor />
|
||||||
{:else if $store.clientFeatures.spectrumThemes}
|
{:else if $store.clientFeatures.spectrumThemes}
|
||||||
|
@ -208,7 +212,8 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
align-items: center;
|
align-items: flex-start;
|
||||||
|
gap: 1rem;
|
||||||
}
|
}
|
||||||
.preview-header > :global(*) {
|
.preview-header > :global(*) {
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
|
|
|
@ -4,7 +4,8 @@
|
||||||
"intelligentLoading": true,
|
"intelligentLoading": true,
|
||||||
"deviceAwareness": true,
|
"deviceAwareness": true,
|
||||||
"state": true,
|
"state": true,
|
||||||
"customThemes": true
|
"customThemes": true,
|
||||||
|
"devicePreview": true
|
||||||
},
|
},
|
||||||
"layout": {
|
"layout": {
|
||||||
"name": "Layout",
|
"name": "Layout",
|
||||||
|
|
|
@ -85,22 +85,36 @@
|
||||||
<UserBindingsProvider>
|
<UserBindingsProvider>
|
||||||
<DeviceBindingsProvider>
|
<DeviceBindingsProvider>
|
||||||
<StateBindingsProvider>
|
<StateBindingsProvider>
|
||||||
<CustomThemeWrapper>
|
<!-- Settings bar can be rendered outside of device preview -->
|
||||||
<div id="app-root" class:preview={$builderStore.inBuilder}>
|
|
||||||
{#key $screenStore.activeLayout._id}
|
|
||||||
<Component instance={$screenStore.activeLayout.props} />
|
|
||||||
{/key}
|
|
||||||
</div>
|
|
||||||
</CustomThemeWrapper>
|
|
||||||
<NotificationDisplay />
|
|
||||||
<ConfirmationDisplay />
|
|
||||||
<PeekScreenDisplay />
|
|
||||||
<!-- Key block needs to be outside the if statement or it breaks -->
|
<!-- Key block needs to be outside the if statement or it breaks -->
|
||||||
{#key $builderStore.selectedComponentId}
|
{#key $builderStore.selectedComponentId}
|
||||||
{#if $builderStore.inBuilder}
|
{#if $builderStore.inBuilder}
|
||||||
<SettingsBar />
|
<SettingsBar />
|
||||||
{/if}
|
{/if}
|
||||||
{/key}
|
{/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">
|
||||||
|
<CustomThemeWrapper>
|
||||||
|
{#key $screenStore.activeLayout._id}
|
||||||
|
<Component instance={$screenStore.activeLayout.props} />
|
||||||
|
{/key}
|
||||||
|
|
||||||
|
<!-- Layers on top of app -->
|
||||||
|
<NotificationDisplay />
|
||||||
|
<ConfirmationDisplay />
|
||||||
|
<PeekScreenDisplay />
|
||||||
|
</CustomThemeWrapper>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Selection indicators should be bounded by device -->
|
||||||
<!--
|
<!--
|
||||||
We don't want to key these by componentID as they control their own
|
We don't want to key these by componentID as they control their own
|
||||||
re-mounting to avoid flashes.
|
re-mounting to avoid flashes.
|
||||||
|
@ -109,6 +123,7 @@
|
||||||
<SelectionIndicator />
|
<SelectionIndicator />
|
||||||
<HoverIndicator />
|
<HoverIndicator />
|
||||||
{/if}
|
{/if}
|
||||||
|
</div>
|
||||||
</StateBindingsProvider>
|
</StateBindingsProvider>
|
||||||
</DeviceBindingsProvider>
|
</DeviceBindingsProvider>
|
||||||
</UserBindingsProvider>
|
</UserBindingsProvider>
|
||||||
|
@ -117,20 +132,33 @@
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
#spectrum-root,
|
#spectrum-root {
|
||||||
#app-root {
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
background: transparent;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
#clip-root {
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
background-color: transparent;
|
||||||
}
|
}
|
||||||
#app-root {
|
#app-root {
|
||||||
position: relative;
|
overflow: hidden;
|
||||||
}
|
height: 100%;
|
||||||
#app-root.preview {
|
width: 100%;
|
||||||
border: 1px solid var(--spectrum-global-color-gray-300);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.error {
|
.error {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -157,4 +185,22 @@
|
||||||
.error :global(h1) {
|
.error :global(h1) {
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Preview styles */
|
||||||
|
/* The additional 6px of size is to account for 4px padding and 2px border */
|
||||||
|
#clip-root.preview {
|
||||||
|
padding: 2px;
|
||||||
|
}
|
||||||
|
#clip-root.tablet-preview {
|
||||||
|
width: calc(1024px + 6px);
|
||||||
|
height: calc(768px + 6px);
|
||||||
|
}
|
||||||
|
#clip-root.mobile-preview {
|
||||||
|
width: calc(390px + 6px);
|
||||||
|
height: calc(844px + 6px);
|
||||||
|
}
|
||||||
|
.preview #app-root {
|
||||||
|
border: 1px solid var(--spectrum-global-color-gray-300);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
|
|
||||||
const { routeStore, styleable, linkable, builderStore } = getContext("sdk")
|
const { routeStore, styleable, linkable, builderStore } = getContext("sdk")
|
||||||
const component = getContext("component")
|
const component = getContext("component")
|
||||||
|
const context = getContext("context")
|
||||||
|
|
||||||
export let title
|
export let title
|
||||||
export let hideTitle = false
|
export let hideTitle = false
|
||||||
|
@ -58,9 +59,19 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="layout layout--{typeClass}" use:styleable={$component.styles}>
|
<div
|
||||||
|
class="layout layout--{typeClass}"
|
||||||
|
use:styleable={$component.styles}
|
||||||
|
class:desktop={!$context.device.mobile && !$context.device.tablet}
|
||||||
|
class:mobile={!!$context.device.mobile}
|
||||||
|
>
|
||||||
{#if typeClass !== "none"}
|
{#if typeClass !== "none"}
|
||||||
<div class="nav-wrapper" class:sticky class:hidden={isPeeking}>
|
<div
|
||||||
|
class="nav-wrapper"
|
||||||
|
class:sticky
|
||||||
|
class:hidden={isPeeking}
|
||||||
|
style={`--height:${$context.device.height}px; --width:${$context.device.width}px;`}
|
||||||
|
>
|
||||||
<div class="nav nav--{typeClass} size--{widthClass}">
|
<div class="nav nav--{typeClass} size--{widthClass}">
|
||||||
<div class="nav-header">
|
<div class="nav-header">
|
||||||
{#if validLinks?.length}
|
{#if validLinks?.length}
|
||||||
|
@ -286,35 +297,32 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Desktop nav overrides */
|
/* Desktop nav overrides */
|
||||||
@media (min-width: 600px) {
|
.desktop.layout--left {
|
||||||
.layout--left {
|
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
.layout--left .main-wrapper {
|
.desktop.layout--left .main-wrapper {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav--left {
|
.desktop .nav--left {
|
||||||
width: 250px;
|
width: 250px;
|
||||||
padding: var(--spacing-xl);
|
padding: var(--spacing-xl);
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav--left .links {
|
.desktop .nav--left .links {
|
||||||
margin-top: var(--spacing-m);
|
margin-top: var(--spacing-m);
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
}
|
}
|
||||||
.nav--left .link {
|
.desktop .nav--left .link {
|
||||||
font-size: var(--spectrum-global-dimension-font-size-150);
|
font-size: var(--spectrum-global-dimension-font-size-150);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/* Mobile nav overrides */
|
/* Mobile nav overrides */
|
||||||
@media (max-width: 600px) {
|
.mobile .nav-wrapper {
|
||||||
.nav-wrapper {
|
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
|
@ -322,40 +330,40 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Show close button in drawer */
|
/* Show close button in drawer */
|
||||||
.close {
|
.mobile .close {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Force standard top bar */
|
/* Force standard top bar */
|
||||||
.nav {
|
.mobile .nav {
|
||||||
padding: var(--spacing-m) 16px;
|
padding: var(--spacing-m) 16px;
|
||||||
}
|
}
|
||||||
.burger {
|
.mobile .burger {
|
||||||
display: grid;
|
display: grid;
|
||||||
place-items: center;
|
place-items: center;
|
||||||
}
|
}
|
||||||
.logo {
|
.mobile .logo {
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
}
|
}
|
||||||
.logo :global(h1) {
|
.mobile .logo :global(h1) {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Reduce padding */
|
/* Reduce padding */
|
||||||
.main {
|
.mobile .main {
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Transform links into drawer */
|
/* Transform links into drawer */
|
||||||
.links {
|
.mobile .links {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
position: fixed;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: -250px;
|
left: -250px;
|
||||||
transform: translateX(0);
|
transform: translateX(0);
|
||||||
width: 250px;
|
width: 250px;
|
||||||
transition: transform 0.26s ease-in-out, opacity 0.26s ease-in-out;
|
transition: transform 0.26s ease-in-out, opacity 0.26s ease-in-out;
|
||||||
height: 100vh;
|
height: var(--height);
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
background: var(--navBackground);
|
background: var(--navBackground);
|
||||||
z-index: 999;
|
z-index: 999;
|
||||||
|
@ -364,23 +372,22 @@
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
padding: var(--spacing-xl);
|
padding: var(--spacing-xl);
|
||||||
}
|
}
|
||||||
.link {
|
.mobile .link {
|
||||||
width: calc(100% - 30px);
|
width: calc(100% - 30px);
|
||||||
font-size: 120%;
|
font-size: 120%;
|
||||||
}
|
}
|
||||||
.links.visible {
|
.mobile .links.visible {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
transform: translateX(250px);
|
transform: translateX(250px);
|
||||||
box-shadow: 0 0 80px 20px rgba(0, 0, 0, 0.3);
|
box-shadow: 0 0 80px 20px rgba(0, 0, 0, 0.3);
|
||||||
}
|
}
|
||||||
.mobile-click-handler.visible {
|
.mobile .mobile-click-handler.visible {
|
||||||
position: fixed;
|
position: absolute;
|
||||||
display: block;
|
display: block;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 100vw;
|
width: var(--width);
|
||||||
height: 100vh;
|
height: var(--height);
|
||||||
z-index: 998;
|
z-index: 998;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -3,21 +3,25 @@
|
||||||
import { onMount } from "svelte"
|
import { onMount } from "svelte"
|
||||||
|
|
||||||
let width = window.innerWidth
|
let width = window.innerWidth
|
||||||
|
let height = window.innerHeight
|
||||||
const tabletBreakpoint = 768
|
const tabletBreakpoint = 768
|
||||||
const desktopBreakpoint = 1280
|
const desktopBreakpoint = 1280
|
||||||
const resizeObserver = new ResizeObserver(entries => {
|
const resizeObserver = new ResizeObserver(entries => {
|
||||||
if (entries?.[0]) {
|
if (entries?.[0]) {
|
||||||
width = entries[0].contentRect?.width
|
width = entries[0].contentRect?.width
|
||||||
|
height = entries[0].contentRect?.height
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
$: data = {
|
$: data = {
|
||||||
mobile: width && width < tabletBreakpoint,
|
mobile: width && width < tabletBreakpoint,
|
||||||
tablet: width && width >= tabletBreakpoint && width < desktopBreakpoint,
|
tablet: width && width >= tabletBreakpoint && width < desktopBreakpoint,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
const doc = document.documentElement
|
const doc = document.getElementById("app-root")
|
||||||
resizeObserver.observe(doc)
|
resizeObserver.observe(doc)
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
|
|
|
@ -18,10 +18,11 @@
|
||||||
}}
|
}}
|
||||||
out:fade={{ duration: transition ? 130 : 0 }}
|
out:fade={{ duration: transition ? 130 : 0 }}
|
||||||
class="indicator"
|
class="indicator"
|
||||||
|
class:flipped={top < 20}
|
||||||
style="top: {top}px; left: {left}px; width: {width}px; height: {height}px; --color: {color}; --zIndex: {zIndex};"
|
style="top: {top}px; left: {left}px; width: {width}px; height: {height}px; --color: {color}; --zIndex: {zIndex};"
|
||||||
>
|
>
|
||||||
{#if text}
|
{#if text}
|
||||||
<div class="text" class:flipped={top < 22}>
|
<div class="text" class:flipped={top < 20}>
|
||||||
{text}
|
{text}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -29,11 +30,17 @@
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.indicator {
|
.indicator {
|
||||||
position: fixed;
|
position: absolute;
|
||||||
z-index: var(--zIndex);
|
z-index: var(--zIndex);
|
||||||
border: 2px solid var(--color);
|
border: 2px solid var(--color);
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
border-radius: 4px;
|
border-top-right-radius: 4px;
|
||||||
|
border-top-left-radius: 0;
|
||||||
|
border-bottom-left-radius: 4px;
|
||||||
|
border-bottom-right-radius: 4px;
|
||||||
|
}
|
||||||
|
.indicator.flipped {
|
||||||
|
border-top-left-radius: 4px;
|
||||||
}
|
}
|
||||||
.text {
|
.text {
|
||||||
background-color: var(--color);
|
background-color: var(--color);
|
||||||
|
@ -45,16 +52,17 @@
|
||||||
padding: 0 8px 2px 8px;
|
padding: 0 8px 2px 8px;
|
||||||
transform: translateY(-100%);
|
transform: translateY(-100%);
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
border-top-left-radius: 2px;
|
border-top-left-radius: 4px;
|
||||||
border-top-right-radius: 2px;
|
border-top-right-radius: 4px;
|
||||||
border-bottom-right-radius: 2px;
|
border-bottom-right-radius: 4px;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
.flipped {
|
.text.flipped {
|
||||||
|
border-top-left-radius: 4px;
|
||||||
transform: translateY(0%);
|
transform: translateY(0%);
|
||||||
top: -2px;
|
top: -2px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,17 +70,22 @@
|
||||||
updating = false
|
updating = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const device = document.getElementById("app-root")
|
||||||
|
const deviceBounds = device.getBoundingClientRect()
|
||||||
children.forEach((child, idx) => {
|
children.forEach((child, idx) => {
|
||||||
const callback = createIntersectionCallback(idx)
|
const callback = createIntersectionCallback(idx)
|
||||||
const threshold = children.length > 1 ? 1 : 0
|
const threshold = children.length > 1 ? 1 : 0
|
||||||
const observer = new IntersectionObserver(callback, { threshold })
|
const observer = new IntersectionObserver(callback, {
|
||||||
|
threshold,
|
||||||
|
root: device,
|
||||||
|
})
|
||||||
observer.observe(child)
|
observer.observe(child)
|
||||||
observers.push(observer)
|
observers.push(observer)
|
||||||
|
|
||||||
const elBounds = child.getBoundingClientRect()
|
const elBounds = child.getBoundingClientRect()
|
||||||
nextIndicators.push({
|
nextIndicators.push({
|
||||||
top: elBounds.top + scrollY - 2,
|
top: elBounds.top + scrollY - deviceBounds.top,
|
||||||
left: elBounds.left + scrollX - 2,
|
left: elBounds.left + scrollX - deviceBounds.left,
|
||||||
width: elBounds.width + 4,
|
width: elBounds.width + 4,
|
||||||
height: elBounds.height + 4,
|
height: elBounds.height + 4,
|
||||||
visible: false,
|
visible: false,
|
||||||
|
|
|
@ -26,8 +26,16 @@
|
||||||
const id = $builderStore.selectedComponentId
|
const id = $builderStore.selectedComponentId
|
||||||
const parent = document.getElementsByClassName(id)?.[0]
|
const parent = document.getElementsByClassName(id)?.[0]
|
||||||
const element = parent?.childNodes?.[0]
|
const element = parent?.childNodes?.[0]
|
||||||
|
|
||||||
|
// The settings bar is higher in the dom tree than the selection indicators
|
||||||
|
// as we want to be able to render the settings bar wider than the screen,
|
||||||
|
// or outside the screen.
|
||||||
|
// Therefore we use the clip root rather than the app root to determine
|
||||||
|
// its position.
|
||||||
|
const device = document.getElementById("clip-root")
|
||||||
if (element && self) {
|
if (element && self) {
|
||||||
// Batch reads to minimize reflow
|
// Batch reads to minimize reflow
|
||||||
|
const deviceBounds = device.getBoundingClientRect()
|
||||||
const elBounds = element.getBoundingClientRect()
|
const elBounds = element.getBoundingClientRect()
|
||||||
const width = self.offsetWidth
|
const width = self.offsetWidth
|
||||||
const height = self.offsetHeight
|
const height = self.offsetHeight
|
||||||
|
@ -35,9 +43,16 @@
|
||||||
|
|
||||||
// Vertically, always render above unless no room, then render inside
|
// Vertically, always render above unless no room, then render inside
|
||||||
let newTop = elBounds.top + scrollY - verticalOffset - height
|
let newTop = elBounds.top + scrollY - verticalOffset - height
|
||||||
|
if (newTop < deviceBounds.top - 50) {
|
||||||
|
newTop = deviceBounds.top - 50
|
||||||
|
}
|
||||||
if (newTop < 0) {
|
if (newTop < 0) {
|
||||||
newTop = 0
|
newTop = 0
|
||||||
}
|
}
|
||||||
|
const deviceBottom = deviceBounds.top + deviceBounds.height
|
||||||
|
if (newTop > deviceBottom - 44) {
|
||||||
|
newTop = deviceBottom - 44
|
||||||
|
}
|
||||||
|
|
||||||
// Horizontally, try to center first.
|
// Horizontally, try to center first.
|
||||||
// Failing that, render to left edge of component.
|
// Failing that, render to left edge of component.
|
||||||
|
|
|
@ -18,6 +18,7 @@ const loadBudibase = () => {
|
||||||
previewType: window["##BUDIBASE_PREVIEW_TYPE##"],
|
previewType: window["##BUDIBASE_PREVIEW_TYPE##"],
|
||||||
theme: window["##BUDIBASE_PREVIEW_THEME##"],
|
theme: window["##BUDIBASE_PREVIEW_THEME##"],
|
||||||
customTheme: window["##BUDIBASE_PREVIEW_CUSTOM_THEME##"],
|
customTheme: window["##BUDIBASE_PREVIEW_CUSTOM_THEME##"],
|
||||||
|
previewDevice: window["##BUDIBASE_PREVIEW_DEVICE##"],
|
||||||
})
|
})
|
||||||
|
|
||||||
// Set app ID - this window flag is set by both the preview and the real
|
// Set app ID - this window flag is set by both the preview and the real
|
||||||
|
|
|
@ -20,6 +20,9 @@ const createBuilderStore = () => {
|
||||||
previewId: null,
|
previewId: null,
|
||||||
previewType: null,
|
previewType: null,
|
||||||
selectedPath: [],
|
selectedPath: [],
|
||||||
|
theme: null,
|
||||||
|
customTheme: null,
|
||||||
|
previewDevice: "desktop",
|
||||||
}
|
}
|
||||||
const writableStore = writable(initialState)
|
const writableStore = writable(initialState)
|
||||||
const derivedStore = derived(writableStore, $state => {
|
const derivedStore = derived(writableStore, $state => {
|
||||||
|
|
|
@ -7,7 +7,10 @@
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
<meta charset="utf8" />
|
<meta charset="utf8" />
|
||||||
<meta name="viewport" content="width=device-width" />
|
<meta
|
||||||
|
name="viewport"
|
||||||
|
content="width=device-width, initial-scale=1.0, viewport-fit=cover"
|
||||||
|
/>
|
||||||
|
|
||||||
<title>{title}</title>
|
<title>{title}</title>
|
||||||
<link rel="icon" type="image/png" href={favicon} />
|
<link rel="icon" type="image/png" href={favicon} />
|
||||||
|
|
Loading…
Reference in New Issue