Add resizable screen/component sections and remove redundant /components route

This commit is contained in:
Andrew Kingston 2023-08-23 10:27:56 +01:00
parent a83e987dcd
commit a54c5b7222
39 changed files with 117 additions and 89 deletions

View File

@ -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
}) })
}, },

View File

@ -0,0 +1 @@
<!-- Required to make Routify happy -->

View File

@ -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"

View File

@ -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}

View File

@ -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`)
} }
} }

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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)

View File

@ -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>

View File

@ -1,4 +0,0 @@
<!--
Placeholder file so that routify works.
No unique content is needed in this index page.
-->

View File

@ -1,8 +0,0 @@
<script>
import { onMount } from "svelte"
import { redirect } from "@roxi/routify"
onMount(() => {
$redirect(`./screen`)
})
</script>

View File

@ -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>

View File

@ -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")
} }