Add resizable screen/component sections and remove redundant /components route
This commit is contained in:
parent
a83e987dcd
commit
a54c5b7222
|
@ -225,7 +225,7 @@ export const getFrontendStore = () => {
|
||||||
// Select new screen
|
// Select new screen
|
||||||
store.update(state => {
|
store.update(state => {
|
||||||
state.selectedScreenId = screen._id
|
state.selectedScreenId = screen._id
|
||||||
state.selectedComponentId = screen.props?._id
|
state.selectedComponentId = "screen"
|
||||||
return state
|
return state
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
<!-- Required to make Routify happy -->
|
|
@ -1,6 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import Panel from "components/design/Panel.svelte"
|
import Panel from "components/design/Panel.svelte"
|
||||||
import { goto } from "@roxi/routify"
|
import { goto, url } from "@roxi/routify"
|
||||||
import { Layout, Search, Icon, Body, notifications } from "@budibase/bbui"
|
import { Layout, Search, Icon, Body, notifications } from "@budibase/bbui"
|
||||||
import structure from "./componentStructure.json"
|
import structure from "./componentStructure.json"
|
||||||
import { store, selectedComponent, selectedScreen } from "builderStore"
|
import { store, selectedComponent, selectedScreen } from "builderStore"
|
|
@ -9,9 +9,7 @@
|
||||||
<div class="app-panel">
|
<div class="app-panel">
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<div class="header-left">
|
<div class="header-left">
|
||||||
{#if $isActive("./screens") || $isActive("./components")}
|
|
||||||
<UndoRedoControl store={screenHistoryStore} />
|
<UndoRedoControl store={screenHistoryStore} />
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
<div class="header-right">
|
<div class="header-right">
|
||||||
{#if $store.clientFeatures.devicePreview}
|
{#if $store.clientFeatures.devicePreview}
|
||||||
|
|
|
@ -70,9 +70,7 @@
|
||||||
$: refreshContent(json)
|
$: refreshContent(json)
|
||||||
|
|
||||||
// Determine if the add component menu is active
|
// Determine if the add component menu is active
|
||||||
$: isAddingComponent = $isActive(
|
$: isAddingComponent = $isActive(`./${$selectedComponent?._id}/new`)
|
||||||
`./components/${$selectedComponent?._id}/new`
|
|
||||||
)
|
|
||||||
|
|
||||||
// Register handler to send custom to the preview
|
// Register handler to send custom to the preview
|
||||||
$: sendPreviewEvent = (name, payload) => {
|
$: sendPreviewEvent = (name, payload) => {
|
||||||
|
@ -215,9 +213,9 @@
|
||||||
|
|
||||||
const toggleAddComponent = () => {
|
const toggleAddComponent = () => {
|
||||||
if (isAddingComponent) {
|
if (isAddingComponent) {
|
||||||
$goto(`./components/${$selectedComponent?._id}`)
|
$goto(`./${$selectedComponent?._id}`)
|
||||||
} else {
|
} else {
|
||||||
$goto(`./components/${$selectedComponent?._id}/new`)
|
$goto(`./${$selectedComponent?._id}/new`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,10 +14,10 @@
|
||||||
let scrolling = false
|
let scrolling = false
|
||||||
|
|
||||||
const toNewComponentRoute = () => {
|
const toNewComponentRoute = () => {
|
||||||
if ($isActive("./new")) {
|
if ($isActive(`./${$store.selectedComponentId}/new`)) {
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
$goto(`./new`)
|
$goto(`./${$store.selectedComponentId}/new`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,11 +31,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleScroll = e => {
|
const handleScroll = e => {
|
||||||
if (e.target.scrollTop === 0) {
|
scrolling = e.target.scrollTop !== 0
|
||||||
scrolling = false
|
|
||||||
} else {
|
|
||||||
scrolling = true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
<script>
|
||||||
|
import ScreenList from "./ScreenList/index.svelte"
|
||||||
|
import ComponentList from "./ComponentList/index.svelte"
|
||||||
|
import { isActive } from "@roxi/routify"
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="panel">
|
||||||
|
<ScreenList />
|
||||||
|
{#if $isActive("./:componentId")}
|
||||||
|
<ComponentList />
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.panel {
|
||||||
|
width: 310px;
|
||||||
|
height: 100%;
|
||||||
|
border-right: var(--border-light);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
background: var(--background);
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -9,29 +9,42 @@
|
||||||
|
|
||||||
let newScreen = false
|
let newScreen = false
|
||||||
let search = false
|
let search = false
|
||||||
|
let resizing = false
|
||||||
let searchValue = ""
|
let searchValue = ""
|
||||||
let searchInput
|
let searchInput
|
||||||
let screensContainer
|
let screensContainer
|
||||||
let scrolling = false
|
let scrolling = false
|
||||||
|
let height = "210px"
|
||||||
|
let previousHeight = null
|
||||||
|
let dragOffset
|
||||||
|
|
||||||
$: filteredScreens = getFilteredScreens($sortedScreens, searchValue)
|
$: filteredScreens = getFilteredScreens($sortedScreens, searchValue)
|
||||||
|
|
||||||
|
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))
|
||||||
|
|
||||||
const openSearch = async () => {
|
const openSearch = async () => {
|
||||||
search = true
|
search = true
|
||||||
await tick()
|
await tick()
|
||||||
searchInput.focus()
|
searchInput.focus()
|
||||||
screensContainer.scroll({ top: 0, behavior: "smooth" })
|
screensContainer.scroll({ top: 0, behavior: "smooth" })
|
||||||
|
previousHeight = height
|
||||||
|
height = "calc(100% + 1px)"
|
||||||
}
|
}
|
||||||
|
|
||||||
const closeSearch = () => {
|
const closeSearch = async () => {
|
||||||
|
if (previousHeight) {
|
||||||
|
// Restore previous height and wait for animation
|
||||||
|
height = previousHeight
|
||||||
|
previousHeight = null
|
||||||
|
await sleep(300)
|
||||||
|
}
|
||||||
search = false
|
search = false
|
||||||
searchValue = ""
|
searchValue = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
const getFilteredScreens = (screens, search) => {
|
const getFilteredScreens = (screens, search) => {
|
||||||
return screens.filter(screen => {
|
return screens.filter(screen => {
|
||||||
const searchMatch = !search || screen.routing.route.includes(search)
|
return !search || screen.routing.route.includes(search)
|
||||||
return searchMatch
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,17 +67,33 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleScroll = e => {
|
const handleScroll = e => {
|
||||||
if (e.target.scrollTop === 0) {
|
scrolling = e.target.scrollTop !== 0
|
||||||
scrolling = false
|
|
||||||
} else {
|
|
||||||
scrolling = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const startResizing = e => {
|
||||||
|
resizing = true
|
||||||
|
dragOffset = parseInt(height) - e.clientY
|
||||||
|
document.addEventListener("mousemove", resize)
|
||||||
|
document.addEventListener("mouseup", stopResizing)
|
||||||
|
}
|
||||||
|
|
||||||
|
const resize = e => {
|
||||||
|
const newHeight = Math.max(0, e.clientY + dragOffset)
|
||||||
|
if (newHeight == null || isNaN(newHeight)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
height = `${newHeight}px`
|
||||||
|
}
|
||||||
|
|
||||||
|
const stopResizing = () => {
|
||||||
|
resizing = false
|
||||||
|
document.removeEventListener("mousemove", resize)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:window on:keydown={onKeyDown} />
|
<svelte:window on:keydown={onKeyDown} />
|
||||||
<div class="screens" class:screenSearch={search}>
|
<div class="screens" class:search class:resizing style={`height:${height};`}>
|
||||||
<div class="header" class:headerScrolling={scrolling}>
|
<div class="header" class:scrolling>
|
||||||
<input
|
<input
|
||||||
readonly={!search}
|
readonly={!search}
|
||||||
bind:value={searchValue}
|
bind:value={searchValue}
|
||||||
|
@ -113,6 +142,8 @@
|
||||||
</Layout>
|
</Layout>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="divider" on:mousedown={startResizing} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="newScreen" class:newScreenVisible={newScreen}>
|
<div class="newScreen" class:newScreenVisible={newScreen}>
|
||||||
|
@ -129,20 +160,24 @@
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
background-color: var(--background);
|
background-color: var(--background);
|
||||||
}
|
}
|
||||||
|
|
||||||
.newScreenVisible {
|
.newScreenVisible {
|
||||||
display: initial;
|
display: initial;
|
||||||
}
|
}
|
||||||
|
|
||||||
.screens {
|
.screens {
|
||||||
height: 210px;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
transition: height 300ms ease-out;
|
min-height: 147px;
|
||||||
|
max-height: calc(100% - 147px);
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
.screens.search {
|
||||||
.screenSearch {
|
transition: height 300ms ease-out;
|
||||||
height: 100%;
|
max-height: none;
|
||||||
|
}
|
||||||
|
.screens.resizing {
|
||||||
|
user-select: none;
|
||||||
|
cursor: row-resize;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
|
@ -154,11 +189,10 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
border-bottom: 2px solid transparent;
|
border-bottom: 2px solid transparent;
|
||||||
transition: border-bottom 300ms ease-out;
|
transition: border-bottom 130ms ease-out;
|
||||||
}
|
}
|
||||||
|
.header.scrolling {
|
||||||
.headerScrolling {
|
border-bottom: var(--border-light);
|
||||||
border-bottom: 2px solid var(--grey-2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.input {
|
.input {
|
||||||
|
@ -178,8 +212,7 @@
|
||||||
.input::placeholder {
|
.input::placeholder {
|
||||||
color: var(--spectrum-global-color-gray-600);
|
color: var(--spectrum-global-color-gray-600);
|
||||||
}
|
}
|
||||||
|
.screens.search input {
|
||||||
.screenSearch input {
|
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -197,6 +230,9 @@
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
|
.screens.resizing .content {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
.screens :global(.nav-item) {
|
.screens :global(.nav-item) {
|
||||||
padding-right: 8px !important;
|
padding-right: 8px !important;
|
||||||
|
@ -208,7 +244,6 @@
|
||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.searchButton:hover {
|
.searchButton:hover {
|
||||||
color: var(--ink);
|
color: var(--ink);
|
||||||
}
|
}
|
||||||
|
@ -223,15 +258,14 @@
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: transform 300ms ease-out;
|
transition: transform 300ms ease-out;
|
||||||
}
|
}
|
||||||
|
.addButton:hover {
|
||||||
|
color: var(--ink);
|
||||||
|
}
|
||||||
|
|
||||||
.closeButton {
|
.closeButton {
|
||||||
transform: rotate(45deg);
|
transform: rotate(45deg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.addButton:hover {
|
|
||||||
color: var(--ink);
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
margin-left: 4px;
|
margin-left: 4px;
|
||||||
margin-right: 4px;
|
margin-right: 4px;
|
||||||
|
@ -240,4 +274,24 @@
|
||||||
.no-results {
|
.no-results {
|
||||||
color: var(--spectrum-global-color-gray-600);
|
color: var(--spectrum-global-color-gray-600);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.divider {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
transform: translateY(50%);
|
||||||
|
height: 16px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.divider:after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
background: var(--spectrum-global-color-gray-200);
|
||||||
|
height: 2px;
|
||||||
|
width: 100%;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
}
|
||||||
|
.divider:hover {
|
||||||
|
cursor: row-resize;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
|
@ -4,7 +4,7 @@
|
||||||
import { syncURLToState } from "helpers/urlStateSync"
|
import { syncURLToState } from "helpers/urlStateSync"
|
||||||
import { store } from "builderStore"
|
import { store } from "builderStore"
|
||||||
import { onDestroy } from "svelte"
|
import { onDestroy } from "svelte"
|
||||||
import LeftPanel from "./components/[componentId]/_components/LeftPanel/index.svelte"
|
import LeftPanel from "./_components/LeftPanel.svelte"
|
||||||
|
|
||||||
$: screenId = $store.selectedScreenId
|
$: screenId = $store.selectedScreenId
|
||||||
$: store.actions.websocket.selectResource(screenId)
|
$: store.actions.websocket.selectResource(screenId)
|
||||||
|
|
|
@ -1,28 +0,0 @@
|
||||||
<script>
|
|
||||||
import Screens from "./Screens/index.svelte"
|
|
||||||
import Components from "./Components/index.svelte"
|
|
||||||
import { selectedScreen } from "builderStore"
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="panel">
|
|
||||||
<Screens />
|
|
||||||
<div class="divider" />
|
|
||||||
{#if $selectedScreen}
|
|
||||||
<Components />
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.panel {
|
|
||||||
width: 310px;
|
|
||||||
height: 100%;
|
|
||||||
border-right: var(--border-light);
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
background: var(--background);
|
|
||||||
}
|
|
||||||
|
|
||||||
.divider {
|
|
||||||
border-bottom: var(--border-light);
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,4 +0,0 @@
|
||||||
<!--
|
|
||||||
Placeholder file so that routify works.
|
|
||||||
No unique content is needed in this index page.
|
|
||||||
-->
|
|
|
@ -1,8 +0,0 @@
|
||||||
<script>
|
|
||||||
import { onMount } from "svelte"
|
|
||||||
import { redirect } from "@roxi/routify"
|
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
$redirect(`./screen`)
|
|
||||||
})
|
|
||||||
</script>
|
|
|
@ -1,8 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import { onMount } from "svelte"
|
|
||||||
import { redirect } from "@roxi/routify"
|
import { redirect } from "@roxi/routify"
|
||||||
|
|
||||||
onMount(() => {
|
$redirect("./screen")
|
||||||
$redirect("./components/screen")
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
$: {
|
$: {
|
||||||
if ($frontendStore.screens.length > 0) {
|
if ($frontendStore.screens.length > 0) {
|
||||||
$redirect(`./${$frontendStore.screens[0]._id}/components/screen`)
|
$redirect(`./${$frontendStore.screens[0]._id}/screen`)
|
||||||
} else {
|
} else {
|
||||||
$redirect("./new")
|
$redirect("./new")
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue