This commit is contained in:
Adria Navarro 2025-02-20 15:58:23 +01:00
parent 97706e5109
commit ab6907691f
3 changed files with 246 additions and 100 deletions

View File

@ -7,7 +7,8 @@
import { BindingMode } from "@budibase/types"
import { EditorModes } from "../CodeEditor"
import CodeEditor from "../CodeEditor/CodeEditor.svelte"
import SnippetDrawer from "./SnippetDrawer.svelte"
import SnippetsSidePanelHeader from "./snippets/SidePanelHeader.svelte"
import SnippetsSidePanelContent from "./snippets/SidePanelContent.svelte"
export let addHelper: (_helper: Helper, _js?: boolean) => void
export let addBinding: (_binding: EnrichedBinding) => void
@ -24,15 +25,13 @@
let popover: Popover
let popoverAnchor: HTMLElement | null
let hoverTarget: {
type: "binding" | "helper" | "snippet"
type: "binding" | "helper"
code: string
description?: string
} | null
let helpers = handlebarsCompletions()
let selectedCategory: string | null
let hideTimeout: ReturnType<typeof setTimeout> | null
let snippetDrawer: SnippetDrawer
let editableSnippet: Snippet | null
$: bindingIcons = bindings?.reduce<Record<string, string>>((acc, ele) => {
if (ele.icon) {
@ -47,13 +46,10 @@
} as Record<string, string>
$: categories = Object.entries(groupBy("category", bindings))
$: filteredSnippets = getFilteredSnippets(
mode,
allowSnippets,
snippets || [],
search
$: categoryNames = getCategoryNames(
categories,
allowSnippets && mode === BindingMode.JavaScript
)
$: categoryNames = getCategoryNames(categories, filteredSnippets.length > 0)
$: searchRgx = new RegExp(search, "ig")
$: filteredCategories = categories
.map(([name, categoryBindings]) => ({
@ -82,26 +78,6 @@
}
$: onModeChange(mode)
const getFilteredSnippets = (
mode: BindingMode,
enableSnippets: boolean,
snippets: Snippet[],
search: string
) => {
if (mode !== BindingMode.JavaScript) {
return []
}
if (!enableSnippets || !snippets.length) {
return []
}
if (!search?.length) {
return snippets
}
return snippets.filter(snippet =>
snippet.name.toLowerCase().includes(search.toLowerCase())
)
}
const getHelperExample = (helper: Helper, js: boolean) => {
let example = helper.example || ""
if (js) {
@ -157,19 +133,6 @@
popover.show()
}
const showSnippetPopover = (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()
@ -196,18 +159,6 @@
searching = false
search = ""
}
const createSnippet = () => {
editableSnippet = null
snippetDrawer.show()
}
const editSnippet = (e: Event, snippet: Snippet) => {
e.preventDefault()
e.stopPropagation()
editableSnippet = snippet
snippetDrawer.show()
}
</script>
{#if popoverAnchor && hoverTarget}
@ -262,15 +213,8 @@
/>
{selectedCategory}
{#if selectedCategory === "Snippets"}
<div class="add-snippet-button">
<Icon
size="S"
name="Add"
hoverable
newStyles
on:click={createSnippet}
/>
</div>{/if}
<SnippetsSidePanelHeader />
{/if}
</div>
{/if}
@ -389,43 +333,12 @@
{/if}
{#if selectedCategory === "Snippets" || search}
{#if filteredSnippets?.length}
<div class="sub-section">
<ul>
{#each filteredSnippets as snippet}
<li
class="binding"
on:mouseenter={e =>
showSnippetPopover(snippet, e.currentTarget)}
on:mouseleave={hidePopover}
on:click={() => addSnippet(snippet)}
>
<span class="binding__label">{snippet.name}</span>
{#if search}
<span class="binding__typeWrap">
<span class="binding__type">snippet</span>
</span>
{:else}
<Icon
name="Edit"
hoverable
newStyles
size="S"
on:click={e => editSnippet(e, snippet)}
/>
{/if}
</li>
{/each}
</ul>
</div>
{/if}
<SnippetsSidePanelContent {snippets} {addSnippet} {search} />
{/if}
{/if}
</Layout>
</div>
<SnippetDrawer bind:this={snippetDrawer} snippet={editableSnippet} />
<style>
.binding-side-panel {
border-left: var(--border-light);
@ -585,8 +498,4 @@
.binding-popover.has-code :global(.cm-content) {
padding: 0;
}
.add-snippet-button {
margin-left: auto;
}
</style>

View File

@ -0,0 +1,191 @@
<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>

View File

@ -0,0 +1,46 @@
<script lang="ts">
import { Icon, Tags, Tag } from "@budibase/bbui"
import { licensing } from "@/stores/portal"
import SnippetDrawer from "../SnippetDrawer.svelte"
import type { Snippet } from "@budibase/types"
$: enableSnippets = !$licensing.isFreePlan
let snippetDrawer: SnippetDrawer
let editableSnippet: Snippet | null
const createSnippet = () => {
editableSnippet = null
snippetDrawer.show()
}
</script>
{#if enableSnippets}
<div class="add-button">
<Icon size="S" name="Add" hoverable newStyles on:click={createSnippet} />
</div>
{:else}
<div class="title">
<Tags>
<Tag icon="LockClosed">Premium</Tag>
</Tags>
</div>
{/if}
<SnippetDrawer bind:this={snippetDrawer} snippet={editableSnippet} />
<style>
.title {
flex: 1 1 auto;
}
.title {
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
gap: var(--spacing-m);
}
.add-button {
margin-left: auto;
}
</style>