Merge branch 'master' of github.com:Budibase/budibase into global-bindings
This commit is contained in:
commit
8bc0aec2c1
|
@ -20,7 +20,7 @@
|
||||||
let focus = false
|
let focus = false
|
||||||
|
|
||||||
const updateValue = newValue => {
|
const updateValue = newValue => {
|
||||||
if (readonly) {
|
if (readonly || disabled) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (type === "number") {
|
if (type === "number") {
|
||||||
|
@ -31,14 +31,14 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
const onFocus = () => {
|
const onFocus = () => {
|
||||||
if (readonly) {
|
if (readonly || disabled) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
focus = true
|
focus = true
|
||||||
}
|
}
|
||||||
|
|
||||||
const onBlur = event => {
|
const onBlur = event => {
|
||||||
if (readonly) {
|
if (readonly || disabled) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
focus = false
|
focus = false
|
||||||
|
@ -46,14 +46,14 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
const onInput = event => {
|
const onInput = event => {
|
||||||
if (readonly || !updateOnChange) {
|
if (readonly || !updateOnChange || disabled) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
updateValue(event.target.value)
|
updateValue(event.target.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateValueOnEnter = event => {
|
const updateValueOnEnter = event => {
|
||||||
if (readonly) {
|
if (readonly || disabled) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (event.key === "Enter") {
|
if (event.key === "Enter") {
|
||||||
|
@ -69,6 +69,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
|
if (disabled) return
|
||||||
focus = autofocus
|
focus = autofocus
|
||||||
if (focus) field.focus()
|
if (focus) field.focus()
|
||||||
})
|
})
|
||||||
|
@ -108,4 +109,16 @@
|
||||||
.spectrum-Textfield {
|
.spectrum-Textfield {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input::placeholder {
|
||||||
|
color: var(--grey-7);
|
||||||
|
}
|
||||||
|
|
||||||
|
input:hover::placeholder {
|
||||||
|
color: var(--grey-7) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:focus::placeholder {
|
||||||
|
color: var(--grey-7) !important;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { store } from "./index"
|
import { store } from "./index"
|
||||||
|
import { get } from "svelte/store"
|
||||||
import { Helpers } from "@budibase/bbui"
|
import { Helpers } from "@budibase/bbui"
|
||||||
import {
|
import {
|
||||||
decodeJSBinding,
|
decodeJSBinding,
|
||||||
|
@ -245,6 +246,10 @@ export const makeComponentUnique = component => {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getComponentText = component => {
|
export const getComponentText = component => {
|
||||||
|
if (component == null) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
if (component?._instanceName) {
|
if (component?._instanceName) {
|
||||||
return component._instanceName
|
return component._instanceName
|
||||||
}
|
}
|
||||||
|
@ -253,3 +258,16 @@ export const getComponentText = component => {
|
||||||
"component"
|
"component"
|
||||||
return capitalise(type)
|
return capitalise(type)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const getComponentName = component => {
|
||||||
|
if (component == null) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
const components = get(store)?.components || {}
|
||||||
|
const componentDefinition = components[component._component] || {}
|
||||||
|
const name =
|
||||||
|
componentDefinition.friendlyName || componentDefinition.name || ""
|
||||||
|
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { getTemporalStore } from "./store/temporal"
|
||||||
import { getThemeStore } from "./store/theme"
|
import { getThemeStore } from "./store/theme"
|
||||||
import { getUserStore } from "./store/users"
|
import { getUserStore } from "./store/users"
|
||||||
import { getDeploymentStore } from "./store/deployments"
|
import { getDeploymentStore } from "./store/deployments"
|
||||||
import { derived, writable, get } from "svelte/store"
|
import { derived, get } from "svelte/store"
|
||||||
import { findComponent, findComponentPath } from "./componentUtils"
|
import { findComponent, findComponentPath } from "./componentUtils"
|
||||||
import { RoleUtils } from "@budibase/frontend-core"
|
import { RoleUtils } from "@budibase/frontend-core"
|
||||||
import { createHistoryStore } from "builderStore/store/history"
|
import { createHistoryStore } from "builderStore/store/history"
|
||||||
|
@ -146,5 +146,3 @@ export const userSelectedResourceMap = derived(userStore, $userStore => {
|
||||||
export const isOnlyUser = derived(userStore, $userStore => {
|
export const isOnlyUser = derived(userStore, $userStore => {
|
||||||
return $userStore.length < 2
|
return $userStore.length < 2
|
||||||
})
|
})
|
||||||
|
|
||||||
export const screensHeight = writable("210px")
|
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
<script>
|
<script>
|
||||||
import { Icon } from "@budibase/bbui"
|
import { AbsTooltip, Icon } from "@budibase/bbui"
|
||||||
import { createEventDispatcher, getContext } from "svelte"
|
import { createEventDispatcher, getContext } from "svelte"
|
||||||
import { helpers } from "@budibase/shared-core"
|
import { helpers } from "@budibase/shared-core"
|
||||||
import { UserAvatars } from "@budibase/frontend-core"
|
import { UserAvatars } from "@budibase/frontend-core"
|
||||||
|
|
||||||
export let icon
|
export let icon
|
||||||
|
export let iconTooltip
|
||||||
export let withArrow = false
|
export let withArrow = false
|
||||||
export let withActions = true
|
export let withActions = true
|
||||||
export let indentLevel = 0
|
export let indentLevel = 0
|
||||||
|
@ -77,7 +78,11 @@
|
||||||
{style}
|
{style}
|
||||||
{draggable}
|
{draggable}
|
||||||
>
|
>
|
||||||
<div class="nav-item-content" bind:this={contentRef}>
|
<div
|
||||||
|
class="nav-item-content"
|
||||||
|
bind:this={contentRef}
|
||||||
|
class:right={rightAlignIcon}
|
||||||
|
>
|
||||||
{#if withArrow}
|
{#if withArrow}
|
||||||
<div
|
<div
|
||||||
class:opened
|
class:opened
|
||||||
|
@ -98,7 +103,9 @@
|
||||||
</div>
|
</div>
|
||||||
{:else if icon}
|
{:else if icon}
|
||||||
<div class="icon" class:right={rightAlignIcon}>
|
<div class="icon" class:right={rightAlignIcon}>
|
||||||
|
<AbsTooltip type="info" position="right" text={iconTooltip}>
|
||||||
<Icon color={iconColor} size="S" name={icon} />
|
<Icon color={iconColor} size="S" name={icon} />
|
||||||
|
</AbsTooltip>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<div class="text" title={showTooltip ? text : null}>
|
<div class="text" title={showTooltip ? text : null}>
|
||||||
|
@ -166,6 +173,11 @@
|
||||||
width: max-content;
|
width: max-content;
|
||||||
position: relative;
|
position: relative;
|
||||||
padding-left: var(--spacing-l);
|
padding-left: var(--spacing-l);
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item-content.right {
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Needed to fully display the actions icon */
|
/* Needed to fully display the actions icon */
|
||||||
|
@ -264,6 +276,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.right {
|
.right {
|
||||||
|
margin-left: auto;
|
||||||
order: 10;
|
order: 10;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -0,0 +1,119 @@
|
||||||
|
const getResizeActions = (
|
||||||
|
cssProperty,
|
||||||
|
mouseMoveEventProperty,
|
||||||
|
elementProperty,
|
||||||
|
initialValue,
|
||||||
|
setValue = () => {}
|
||||||
|
) => {
|
||||||
|
let element = null
|
||||||
|
|
||||||
|
const elementAction = node => {
|
||||||
|
element = node
|
||||||
|
|
||||||
|
if (initialValue != null) {
|
||||||
|
element.style[cssProperty] = `${initialValue}px`
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
destroy() {
|
||||||
|
element = null
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const dragHandleAction = node => {
|
||||||
|
let startProperty = null
|
||||||
|
let startPosition = null
|
||||||
|
|
||||||
|
const handleMouseMove = e => {
|
||||||
|
e.preventDefault() // Prevent highlighting while dragging
|
||||||
|
const change = e[mouseMoveEventProperty] - startPosition
|
||||||
|
element.style[cssProperty] = `${startProperty + change}px`
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleMouseUp = e => {
|
||||||
|
e.preventDefault() // Prevent highlighting while dragging
|
||||||
|
window.removeEventListener("mousemove", handleMouseMove)
|
||||||
|
window.removeEventListener("mouseup", handleMouseUp)
|
||||||
|
|
||||||
|
element.style.removeProperty("transition") // remove temporary transition override
|
||||||
|
for (let item of document.getElementsByTagName("iframe")) {
|
||||||
|
item.style.removeProperty("pointer-events")
|
||||||
|
}
|
||||||
|
|
||||||
|
setValue(element[elementProperty])
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleMouseDown = e => {
|
||||||
|
if (e.detail > 1) {
|
||||||
|
// e.detail is the number of rapid clicks, so e.detail = 2 is
|
||||||
|
// a double click. We want to prevent default behaviour in
|
||||||
|
// this case as it highlights nearby selectable elements, which
|
||||||
|
// then interferes with the resizing mousemove.
|
||||||
|
// Putting this on the double click handler doesn't seem to
|
||||||
|
// work, so it must go here.
|
||||||
|
e.preventDefault()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
e.target.hasAttribute("disabled") &&
|
||||||
|
e.target.getAttribute("disabled") !== "false"
|
||||||
|
) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
element.style.transition = `${cssProperty} 0ms` // temporarily override any height transitions
|
||||||
|
|
||||||
|
// iframes swallow mouseup events if your cursor ends up over it during a drag, so make them
|
||||||
|
// temporarily non-interactive
|
||||||
|
for (let item of document.getElementsByTagName("iframe")) {
|
||||||
|
item.style.pointerEvents = "none"
|
||||||
|
}
|
||||||
|
|
||||||
|
startProperty = element[elementProperty]
|
||||||
|
startPosition = e[mouseMoveEventProperty]
|
||||||
|
|
||||||
|
window.addEventListener("mousemove", handleMouseMove)
|
||||||
|
window.addEventListener("mouseup", handleMouseUp)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDoubleClick = () => {
|
||||||
|
element.style.removeProperty(cssProperty)
|
||||||
|
}
|
||||||
|
|
||||||
|
node.addEventListener("mousedown", handleMouseDown)
|
||||||
|
node.addEventListener("dblclick", handleDoubleClick)
|
||||||
|
|
||||||
|
return {
|
||||||
|
destroy() {
|
||||||
|
node.removeEventListener("mousedown", handleMouseDown)
|
||||||
|
node.removeEventListener("dblclick", handleDoubleClick)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [elementAction, dragHandleAction]
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getVerticalResizeActions = (initialValue, setValue = () => {}) => {
|
||||||
|
return getResizeActions(
|
||||||
|
"height",
|
||||||
|
"pageY",
|
||||||
|
"clientHeight",
|
||||||
|
initialValue,
|
||||||
|
setValue
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getHorizontalResizeActions = (
|
||||||
|
initialValue,
|
||||||
|
setValue = () => {}
|
||||||
|
) => {
|
||||||
|
return getResizeActions(
|
||||||
|
"width",
|
||||||
|
"pageX",
|
||||||
|
"clientWidth",
|
||||||
|
initialValue,
|
||||||
|
setValue
|
||||||
|
)
|
||||||
|
}
|
|
@ -1,8 +1,9 @@
|
||||||
<script>
|
<script>
|
||||||
import { Icon, Body } from "@budibase/bbui"
|
import { AbsTooltip, Icon, Body } from "@budibase/bbui"
|
||||||
|
|
||||||
export let title
|
export let title
|
||||||
export let icon
|
export let icon
|
||||||
|
export let iconTooltip
|
||||||
export let showAddButton = false
|
export let showAddButton = false
|
||||||
export let showBackButton = false
|
export let showBackButton = false
|
||||||
export let showCloseButton = false
|
export let showCloseButton = false
|
||||||
|
@ -36,7 +37,9 @@
|
||||||
<Icon name="ArrowLeft" hoverable on:click={onClickBackButton} />
|
<Icon name="ArrowLeft" hoverable on:click={onClickBackButton} />
|
||||||
{/if}
|
{/if}
|
||||||
{#if icon}
|
{#if icon}
|
||||||
|
<AbsTooltip type="info" text={iconTooltip}>
|
||||||
<Icon name={icon} />
|
<Icon name={icon} />
|
||||||
|
</AbsTooltip>
|
||||||
{/if}
|
{/if}
|
||||||
<div class="title">
|
<div class="title">
|
||||||
{#if customTitleContent}
|
{#if customTitleContent}
|
||||||
|
@ -68,6 +71,7 @@
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.panel {
|
.panel {
|
||||||
|
min-width: 260px;
|
||||||
width: 260px;
|
width: 260px;
|
||||||
flex: 0 0 260px;
|
flex: 0 0 260px;
|
||||||
background: var(--background);
|
background: var(--background);
|
||||||
|
@ -85,6 +89,7 @@
|
||||||
border-right: var(--border-light);
|
border-right: var(--border-light);
|
||||||
}
|
}
|
||||||
.panel.wide {
|
.panel.wide {
|
||||||
|
min-width: 310px;
|
||||||
width: 310px;
|
width: 310px;
|
||||||
flex: 0 0 310px;
|
flex: 0 0 310px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
import Panel from "components/design/Panel.svelte"
|
import Panel from "components/design/Panel.svelte"
|
||||||
import { store, selectedComponent, selectedScreen } from "builderStore"
|
import { store, selectedComponent, selectedScreen } from "builderStore"
|
||||||
import { getComponentText } from "builderStore/componentUtils"
|
import { getComponentName } from "builderStore/componentUtils"
|
||||||
import ComponentSettingsSection from "./ComponentSettingsSection.svelte"
|
import ComponentSettingsSection from "./ComponentSettingsSection.svelte"
|
||||||
import DesignSection from "./DesignSection.svelte"
|
import DesignSection from "./DesignSection.svelte"
|
||||||
import CustomStylesSection from "./CustomStylesSection.svelte"
|
import CustomStylesSection from "./CustomStylesSection.svelte"
|
||||||
|
@ -43,17 +43,25 @@
|
||||||
|
|
||||||
$: id = $selectedComponent?._id
|
$: id = $selectedComponent?._id
|
||||||
$: id, (section = tabs[0])
|
$: id, (section = tabs[0])
|
||||||
|
|
||||||
|
$: componentName = getComponentName(componentInstance)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if $selectedComponent}
|
{#if $selectedComponent}
|
||||||
{#key $selectedComponent._id}
|
{#key $selectedComponent._id}
|
||||||
<Panel {title} icon={componentDefinition?.icon} borderLeft wide>
|
<Panel
|
||||||
|
{title}
|
||||||
|
icon={componentDefinition?.icon}
|
||||||
|
iconTooltip={componentName}
|
||||||
|
borderLeft
|
||||||
|
wide
|
||||||
|
>
|
||||||
<span class="panel-title-content" slot="panel-title-content">
|
<span class="panel-title-content" slot="panel-title-content">
|
||||||
<input
|
<input
|
||||||
class="input"
|
class="input"
|
||||||
value={title}
|
value={title}
|
||||||
{title}
|
{title}
|
||||||
placeholder={getComponentText(componentInstance)}
|
placeholder={componentName}
|
||||||
on:keypress={e => {
|
on:keypress={e => {
|
||||||
if (e.key.toLowerCase() === "enter") {
|
if (e.key.toLowerCase() === "enter") {
|
||||||
e.target.blur()
|
e.target.blur()
|
||||||
|
|
|
@ -25,6 +25,7 @@
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.app-panel {
|
.app-panel {
|
||||||
|
min-width: 410px;
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
import {
|
import {
|
||||||
findComponentPath,
|
findComponentPath,
|
||||||
getComponentText,
|
getComponentText,
|
||||||
|
getComponentName,
|
||||||
} from "builderStore/componentUtils"
|
} from "builderStore/componentUtils"
|
||||||
import { get } from "svelte/store"
|
import { get } from "svelte/store"
|
||||||
import { dndStore } from "./dndStore"
|
import { dndStore } from "./dndStore"
|
||||||
|
@ -110,6 +111,7 @@
|
||||||
on:drop={onDrop}
|
on:drop={onDrop}
|
||||||
text={getComponentText(component)}
|
text={getComponentText(component)}
|
||||||
icon={getComponentIcon(component)}
|
icon={getComponentIcon(component)}
|
||||||
|
iconTooltip={getComponentName(component)}
|
||||||
withArrow={componentHasChildren(component)}
|
withArrow={componentHasChildren(component)}
|
||||||
indentLevel={level}
|
indentLevel={level}
|
||||||
selected={$store.selectedComponentId === component._id}
|
selected={$store.selectedComponentId === component._id}
|
||||||
|
|
|
@ -1,21 +1,55 @@
|
||||||
<script>
|
<script>
|
||||||
import ScreenList from "./ScreenList/index.svelte"
|
import ScreenList from "./ScreenList/index.svelte"
|
||||||
import ComponentList from "./ComponentList/index.svelte"
|
import ComponentList from "./ComponentList/index.svelte"
|
||||||
|
import { getHorizontalResizeActions } from "components/common/resizable"
|
||||||
|
|
||||||
|
const [resizable, resizableHandle] = getHorizontalResizeActions()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="panel">
|
<div class="panel" use:resizable>
|
||||||
|
<div class="content">
|
||||||
<ScreenList />
|
<ScreenList />
|
||||||
<ComponentList />
|
<ComponentList />
|
||||||
</div>
|
</div>
|
||||||
|
<div class="divider">
|
||||||
|
<div class="dividerClickExtender" role="separator" use:resizableHandle />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.panel {
|
.panel {
|
||||||
|
display: flex;
|
||||||
|
min-width: 270px;
|
||||||
width: 310px;
|
width: 310px;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
border-right: var(--border-light);
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
overflow: hidden;
|
||||||
|
flex-grow: 1;
|
||||||
|
height: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
background: var(--background);
|
background: var(--background);
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.divider {
|
||||||
|
position: relative;
|
||||||
|
height: 100%;
|
||||||
|
width: 2px;
|
||||||
|
background: var(--spectrum-global-color-gray-200);
|
||||||
|
transition: background 130ms ease-out;
|
||||||
|
}
|
||||||
|
.divider:hover {
|
||||||
|
background: var(--spectrum-global-color-gray-300);
|
||||||
|
cursor: row-resize;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dividerClickExtender {
|
||||||
|
position: absolute;
|
||||||
|
cursor: col-resize;
|
||||||
|
height: 100%;
|
||||||
|
width: 12px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,108 +1,50 @@
|
||||||
<script>
|
<script>
|
||||||
import { Layout } from "@budibase/bbui"
|
import { Layout } from "@budibase/bbui"
|
||||||
import {
|
import { store, sortedScreens, userSelectedResourceMap } from "builderStore"
|
||||||
store,
|
|
||||||
sortedScreens,
|
|
||||||
userSelectedResourceMap,
|
|
||||||
screensHeight,
|
|
||||||
} from "builderStore"
|
|
||||||
import NavItem from "components/common/NavItem.svelte"
|
import NavItem from "components/common/NavItem.svelte"
|
||||||
import RoleIndicator from "./RoleIndicator.svelte"
|
import RoleIndicator from "./RoleIndicator.svelte"
|
||||||
import DropdownMenu from "./DropdownMenu.svelte"
|
import DropdownMenu from "./DropdownMenu.svelte"
|
||||||
import { onMount } from "svelte"
|
|
||||||
import { goto } from "@roxi/routify"
|
import { goto } from "@roxi/routify"
|
||||||
|
import { getVerticalResizeActions } from "components/common/resizable"
|
||||||
import NavHeader from "components/common/NavHeader.svelte"
|
import NavHeader from "components/common/NavHeader.svelte"
|
||||||
|
|
||||||
let search = false
|
const [resizable, resizableHandle] = getVerticalResizeActions()
|
||||||
let resizing = false
|
|
||||||
let searchValue = ""
|
|
||||||
|
|
||||||
let container
|
let searching = false
|
||||||
|
let searchValue = ""
|
||||||
let screensContainer
|
let screensContainer
|
||||||
let scrolling = false
|
let scrolling = false
|
||||||
let previousHeight = null
|
|
||||||
let dragOffset
|
|
||||||
|
|
||||||
$: filteredScreens = getFilteredScreens($sortedScreens, searchValue)
|
$: filteredScreens = getFilteredScreens($sortedScreens, searchValue)
|
||||||
|
|
||||||
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))
|
const handleOpenSearch = async () => {
|
||||||
|
|
||||||
$: search ? openSearch() : closeSearch()
|
|
||||||
|
|
||||||
const openSearch = async () => {
|
|
||||||
screensContainer.scroll({ top: 0, behavior: "smooth" })
|
screensContainer.scroll({ top: 0, behavior: "smooth" })
|
||||||
previousHeight = $screensHeight
|
|
||||||
$screensHeight = "calc(100% + 1px)"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const closeSearch = async () => {
|
$: {
|
||||||
if (previousHeight) {
|
if (searching) {
|
||||||
// Restore previous height and wait for animation
|
handleOpenSearch()
|
||||||
$screensHeight = previousHeight
|
|
||||||
previousHeight = null
|
|
||||||
await sleep(300)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const getFilteredScreens = (screens, search) => {
|
const getFilteredScreens = (screens, searchValue) => {
|
||||||
return screens.filter(screen => {
|
return screens.filter(screen => {
|
||||||
return !search || screen.routing.route.includes(search)
|
return !searchValue || screen.routing.route.includes(searchValue)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleScroll = e => {
|
const handleScroll = e => {
|
||||||
scrolling = e.target.scrollTop !== 0
|
scrolling = e.target.scrollTop !== 0
|
||||||
}
|
}
|
||||||
|
|
||||||
const startResizing = e => {
|
|
||||||
// Reset the height store to match the true height
|
|
||||||
$screensHeight = `${container.getBoundingClientRect().height}px`
|
|
||||||
|
|
||||||
// Store an offset to easily compute new height when moving the mouse
|
|
||||||
dragOffset = parseInt($screensHeight) - e.clientY
|
|
||||||
|
|
||||||
// Add event listeners
|
|
||||||
resizing = true
|
|
||||||
document.addEventListener("mousemove", resize)
|
|
||||||
document.addEventListener("mouseup", stopResizing)
|
|
||||||
}
|
|
||||||
|
|
||||||
const resize = e => {
|
|
||||||
// Prevent negative heights as this screws with layout
|
|
||||||
const newHeight = Math.max(0, e.clientY + dragOffset)
|
|
||||||
if (newHeight == null || isNaN(newHeight)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
$screensHeight = `${newHeight}px`
|
|
||||||
}
|
|
||||||
|
|
||||||
const stopResizing = () => {
|
|
||||||
resizing = false
|
|
||||||
document.removeEventListener("mousemove", resize)
|
|
||||||
}
|
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
// Ensure we aren't stuck at 100% height from leaving while searching
|
|
||||||
if ($screensHeight == null || isNaN(parseInt($screensHeight))) {
|
|
||||||
$screensHeight = "210px"
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:window />
|
<div class="screens" class:searching use:resizable>
|
||||||
<div
|
|
||||||
class="screens"
|
|
||||||
class:search
|
|
||||||
class:resizing
|
|
||||||
style={`height:${$screensHeight};`}
|
|
||||||
bind:this={container}
|
|
||||||
>
|
|
||||||
<div class="header" class:scrolling>
|
<div class="header" class:scrolling>
|
||||||
<NavHeader
|
<NavHeader
|
||||||
title="Screens"
|
title="Screens"
|
||||||
placeholder="Search for screens"
|
placeholder="Search for screens"
|
||||||
bind:value={searchValue}
|
bind:value={searchValue}
|
||||||
bind:search
|
bind:search={searching}
|
||||||
onAdd={() => $goto("../new")}
|
onAdd={() => $goto("../new")}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -110,6 +52,7 @@
|
||||||
{#if filteredScreens?.length}
|
{#if filteredScreens?.length}
|
||||||
{#each filteredScreens as screen (screen._id)}
|
{#each filteredScreens as screen (screen._id)}
|
||||||
<NavItem
|
<NavItem
|
||||||
|
scrollable
|
||||||
icon={screen.routing.homeScreen ? "Home" : null}
|
icon={screen.routing.homeScreen ? "Home" : null}
|
||||||
indentLevel={0}
|
indentLevel={0}
|
||||||
selected={$store.selectedScreenId === screen._id}
|
selected={$store.selectedScreenId === screen._id}
|
||||||
|
@ -135,9 +78,11 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
role="separator"
|
||||||
|
disabled={searching}
|
||||||
class="divider"
|
class="divider"
|
||||||
on:mousedown={startResizing}
|
class:disabled={searching}
|
||||||
on:dblclick={() => screensHeight.set("210px")}
|
use:resizableHandle
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -148,14 +93,12 @@
|
||||||
min-height: 147px;
|
min-height: 147px;
|
||||||
max-height: calc(100% - 147px);
|
max-height: calc(100% - 147px);
|
||||||
position: relative;
|
position: relative;
|
||||||
transition: height 300ms ease-out;
|
transition: height 300ms ease-out, max-height 300ms ease-out;
|
||||||
|
height: 210px;
|
||||||
}
|
}
|
||||||
.screens.search {
|
.screens.searching {
|
||||||
max-height: none;
|
max-height: 100%;
|
||||||
}
|
height: 100% !important;
|
||||||
.screens.resizing {
|
|
||||||
user-select: none;
|
|
||||||
cursor: row-resize;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
|
@ -177,9 +120,6 @@
|
||||||
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;
|
||||||
|
@ -217,4 +157,10 @@
|
||||||
.divider:hover:after {
|
.divider:hover:after {
|
||||||
background: var(--spectrum-global-color-gray-300);
|
background: var(--spectrum-global-color-gray-300);
|
||||||
}
|
}
|
||||||
|
.divider.disabled {
|
||||||
|
cursor: auto;
|
||||||
|
}
|
||||||
|
.divider.disabled:after {
|
||||||
|
background: var(--spectrum-global-color-gray-200);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -40,6 +40,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
|
width: 100vw;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
|
|
Loading…
Reference in New Issue