1
0
Fork 0
mirror of synced 2024-05-02 19:42:42 +12:00
budibase/packages/builder/src/components/design/settings/controls/ButtonActionEditor/ButtonActionDrawer.svelte

286 lines
7.4 KiB
Svelte

<script>
import { flip } from "svelte/animate"
import { dndzone } from "svelte-dnd-action"
import {
Icon,
Button,
Layout,
DrawerContent,
ActionButton,
Search,
} from "@budibase/bbui"
import { getAvailableActions } from "./index"
import { generate } from "shortid"
import { getEventContextBindings } from "builderStore/dataBinding"
import { currentAsset, store } from "builderStore"
const flipDurationMs = 150
const EVENT_TYPE_KEY = "##eventHandlerType"
const actionTypes = getAvailableActions()
export let key
export let actions
export let bindings = []
export let nested
let actionQuery
let selectedAction = actions?.length ? actions[0] : null
$: {
// Ensure parameters object is never null
if (selectedAction && !selectedAction.parameters) {
selectedAction.parameters = {}
}
}
$: parsedQuery =
typeof actionQuery === "string" ? actionQuery.toLowerCase().trim() : ""
$: showAvailableActions = !actions?.length
$: mappedActionTypes = actionTypes.reduce((acc, action) => {
let parsedName = action.name.toLowerCase().trim()
if (parsedQuery.length && parsedName.indexOf(parsedQuery) < 0) {
return acc
}
acc[action.type] = acc[action.type] || []
acc[action.type].push(action)
return acc
}, {})
// These are ephemeral bindings which only exist while executing actions
$: eventContexBindings = getEventContextBindings(
$currentAsset,
$store.selectedComponentId,
key,
actions,
selectedAction?.id
)
$: allBindings = eventContexBindings.concat(bindings)
$: {
// Ensure each action has a unique ID
if (actions) {
actions.forEach(action => {
if (!action.id) {
action.id = generate()
}
})
}
}
$: selectedActionComponent =
selectedAction &&
actionTypes.find(t => t.name === selectedAction[EVENT_TYPE_KEY])?.component
$: {
// Select the first action if we delete an action
if (selectedAction && !actions?.includes(selectedAction)) {
selectedAction = actions?.[0]
}
}
const deleteAction = index => {
actions.splice(index, 1)
actions = actions
}
const toggleActionList = () => {
actionQuery = null
showAvailableActions = !showAvailableActions
}
const addAction = actionType => {
const newAction = {
parameters: {},
[EVENT_TYPE_KEY]: actionType.name,
id: generate(),
}
if (!actions) {
actions = []
}
actions = [...actions, newAction]
selectedAction = newAction
}
const selectAction = action => () => {
selectedAction = action
}
const onAddAction = actionType => {
addAction(actionType)
toggleActionList()
}
function handleDndConsider(e) {
actions = e.detail.items
}
function handleDndFinalize(e) {
actions = e.detail.items
}
</script>
<DrawerContent>
<Layout noPadding gap="S" slot="sidebar">
{#if showAvailableActions || !actions?.length}
<div class="actions-list">
{#if actions?.length > 0}
<div>
<ActionButton
secondary
icon={"ArrowLeft"}
on:click={toggleActionList}
>
Back
</ActionButton>
</div>
{/if}
<div class="search-wrap">
<Search placeholder="Search" bind:value={actionQuery} />
</div>
{#each Object.entries(mappedActionTypes) as [categoryId, category], idx}
<div class="heading" class:top-entry={idx === 0}>{categoryId}</div>
<ul>
{#each category as actionType}
<li on:click={onAddAction(actionType)}>
<span class="action-name">{actionType.name}</span>
</li>
{/each}
</ul>
{/each}
</div>
{/if}
{#if actions && actions.length > 0 && !showAvailableActions}
<div>
<Button secondary on:click={toggleActionList}>Add Action</Button>
</div>
<div
class="actions"
use:dndzone={{
items: actions,
flipDurationMs,
dropTargetStyle: { outline: "none" },
}}
on:consider={handleDndConsider}
on:finalize={handleDndFinalize}
>
{#each actions as action, index (action.id)}
<div
class="action-container"
animate:flip={{ duration: flipDurationMs }}
class:selected={action === selectedAction}
on:click={selectAction(action)}
>
<Icon name="DragHandle" size="XL" />
<div class="action-header">
{index + 1}.&nbsp;{action[EVENT_TYPE_KEY]}
</div>
<Icon
name="Close"
hoverable
size="S"
on:click={() => deleteAction(index)}
/>
</div>
{/each}
</div>
{/if}
</Layout>
<Layout noPadding>
{#if selectedActionComponent && !showAvailableActions}
{#key selectedAction.id}
<div class="selected-action-container">
<svelte:component
this={selectedActionComponent}
parameters={selectedAction.parameters}
bindings={allBindings}
{nested}
/>
</div>
{/key}
{/if}
</Layout>
</DrawerContent>
<style>
.actions {
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: stretch;
gap: var(--spacing-s);
}
.action-header {
color: var(--spectrum-global-color-gray-700);
flex: 1 1 auto;
}
.action-container {
background-color: var(--background);
padding: var(--spacing-s) var(--spacing-m);
border-radius: 4px;
border: var(--border-light);
transition: background-color 130ms ease-in-out, color 130ms ease-in-out,
border-color 130ms ease-in-out;
gap: var(--spacing-m);
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
}
.action-container:hover,
.action-container.selected {
background-color: var(--spectrum-global-color-gray-50);
border-color: var(--spectrum-global-color-gray-500);
cursor: pointer;
}
.action-container:hover .action-header,
.action-container.selected .action-header {
color: var(--spectrum-global-color-gray-900);
}
.actions-list > * {
padding-bottom: var(--spectrum-global-dimension-static-size-200);
}
.actions-list .heading {
padding-bottom: var(--spectrum-global-dimension-static-size-100);
padding-top: var(--spectrum-global-dimension-static-size-50);
}
.actions-list .heading.top-entry {
padding-top: 0px;
}
ul {
list-style: none;
padding: 0;
margin: 0;
}
li {
font-size: var(--font-size-s);
padding: var(--spacing-m);
border-radius: 4px;
background-color: var(--spectrum-global-color-gray-200);
transition: background-color 130ms ease-in-out, color 130ms ease-in-out,
border-color 130ms ease-in-out;
word-wrap: break-word;
}
li:not(:last-of-type) {
margin-bottom: var(--spacing-s);
}
li :global(*) {
transition: color 130ms ease-in-out;
}
li:hover {
color: var(--spectrum-global-color-gray-900);
background-color: var(--spectrum-global-color-gray-50);
cursor: pointer;
}
.action-name {
font-weight: 600;
text-transform: capitalize;
}
.heading {
font-size: var(--font-size-s);
font-weight: 600;
text-transform: uppercase;
color: var(--spectrum-global-color-gray-600);
}
</style>