budibase/packages/builder/src/components/common/bindings/snippets/SidePanelContent.svelte

192 lines
4.6 KiB
Svelte

<script lang="ts">
import { Icon, Popover, Body, Button } from "@budibase/bbui"
import CodeEditor from "@/components/common/CodeEditor/CodeEditor.svelte"
import { EditorModes } from "@/components/common/CodeEditor"
import SnippetDrawer from "./SnippetDrawer.svelte"
import { licensing } from "@/stores/portal"
import UpgradeButton from "@/pages/builder/portal/_components/UpgradeButton.svelte"
import type { Snippet } from "@budibase/types"
export let addSnippet
export let snippets
export let search
let popover: Popover
let popoverAnchor: HTMLElement | null
let hoverTarget: {
type: "binding" | "helper" | "snippet"
code: string
description?: string
} | null
let hideTimeout: ReturnType<typeof setTimeout> | null
let snippetDrawer: SnippetDrawer
let editableSnippet: Snippet | null
$: enableSnippets = !$licensing.isFreePlan
$: filteredSnippets = getFilteredSnippets(enableSnippets, snippets, search)
const showSnippet = (snippet: Snippet, target: HTMLElement) => {
stopHidingPopover()
if (!snippet.code) {
return
}
popoverAnchor = target
hoverTarget = {
type: "snippet",
code: snippet.code,
}
popover.show()
}
const hidePopover = () => {
hideTimeout = setTimeout(() => {
popover.hide()
popoverAnchor = null
hoverTarget = null
hideTimeout = null
}, 100)
}
const stopHidingPopover = () => {
if (hideTimeout) {
clearTimeout(hideTimeout)
hideTimeout = null
}
}
const createSnippet = () => {
editableSnippet = null
snippetDrawer.show()
}
const editSnippet = (e: Event, snippet: Snippet) => {
e.preventDefault()
e.stopPropagation()
editableSnippet = snippet
snippetDrawer.show()
}
const getFilteredSnippets = (
enableSnippets: boolean,
snippets: Snippet[],
search: string
) => {
if (!enableSnippets || !snippets.length) {
return []
}
if (!search?.length) {
return snippets
}
return snippets.filter(snippet =>
snippet.name.toLowerCase().includes(search.toLowerCase())
)
}
</script>
<!-- svelte-ignore a11y-no-static-element-interactions -->
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div class="snippet-list">
{#if enableSnippets}
{#each filteredSnippets as snippet}
<div
class="snippet"
on:mouseenter={e => showSnippet(snippet, e.currentTarget)}
on:mouseleave={hidePopover}
on:click={() => addSnippet(snippet)}
>
{snippet.name}
<Icon
name="Edit"
hoverable
newStyles
size="S"
on:click={e => editSnippet(e, snippet)}
/>
</div>
{/each}
{:else if !search}
<div class="upgrade">
<Body size="S">
Snippets let you create reusable JS functions and values that can all be
managed in one place
</Body>
{#if enableSnippets}
<Button cta on:click={createSnippet}>Create snippet</Button>
{:else}
<UpgradeButton />
{/if}
</div>
{/if}
</div>
{#if hoverTarget && popoverAnchor}
<Popover
align="left-outside"
bind:this={popover}
anchor={popoverAnchor}
minWidth={0}
maxWidth={480}
maxHeight={480}
dismissible={false}
on:mouseenter={stopHidingPopover}
on:mouseleave={hidePopover}
>
<div class="snippet-popover">
{#key hoverTarget}
<CodeEditor
value={hoverTarget.code?.trim()}
mode={EditorModes.JS}
readonly
/>
{/key}
</div>
</Popover>
{/if}
<SnippetDrawer bind:this={snippetDrawer} snippet={editableSnippet} />
<style>
/* Upgrade */
.upgrade {
display: flex;
flex-direction: column;
align-items: center;
gap: var(--spacing-l);
}
.upgrade :global(p) {
text-align: center;
align-self: center;
}
/* List */
.snippet-list {
padding: 0 var(--spacing-l);
padding-bottom: var(--spacing-l);
display: flex;
flex-direction: column;
gap: var(--spacing-s);
}
.snippet {
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-out, color 130ms ease-out,
border-color 130ms ease-out;
word-wrap: break-word;
display: flex;
justify-content: space-between;
}
.snippet:hover {
color: var(--spectrum-global-color-gray-900);
background-color: var(--spectrum-global-color-gray-50);
cursor: pointer;
}
/* Popover */
.snippet-popover {
width: 400px;
}
</style>