Merge pull request #15403 from Budibase/bindings-panel
Bindings explorer panel
This commit is contained in:
commit
5139485eb9
|
@ -80,7 +80,7 @@
|
|||
"dayjs": "^1.10.8",
|
||||
"easymde": "^2.16.1",
|
||||
"svelte-dnd-action": "^0.9.8",
|
||||
"svelte-portal": "^1.0.0"
|
||||
"svelte-portal": "^2.2.1"
|
||||
},
|
||||
"resolutions": {
|
||||
"loader-utils": "1.4.1"
|
||||
|
|
|
@ -1,3 +1,17 @@
|
|||
type ClickOutsideCallback = (event: MouseEvent) => void | undefined
|
||||
|
||||
interface ClickOutsideOpts {
|
||||
callback?: ClickOutsideCallback
|
||||
anchor?: HTMLElement
|
||||
}
|
||||
|
||||
interface Handler {
|
||||
id: number
|
||||
element: HTMLElement
|
||||
anchor: HTMLElement
|
||||
callback?: ClickOutsideCallback
|
||||
}
|
||||
|
||||
// These class names will never trigger a callback if clicked, no matter what
|
||||
const ignoredClasses = [
|
||||
".download-js-link",
|
||||
|
@ -14,18 +28,20 @@ const conditionallyIgnoredClasses = [
|
|||
".drawer-wrapper",
|
||||
".spectrum-Popover",
|
||||
]
|
||||
let clickHandlers = []
|
||||
let candidateTarget
|
||||
let clickHandlers: Handler[] = []
|
||||
let candidateTarget: HTMLElement | undefined
|
||||
|
||||
// Processes a "click outside" event and invokes callbacks if our source element
|
||||
// is valid
|
||||
const handleClick = event => {
|
||||
const handleClick = (e: MouseEvent) => {
|
||||
const target = e.target as HTMLElement
|
||||
|
||||
// Ignore click if this is an ignored class
|
||||
if (event.target.closest('[data-ignore-click-outside="true"]')) {
|
||||
if (target.closest('[data-ignore-click-outside="true"]')) {
|
||||
return
|
||||
}
|
||||
for (let className of ignoredClasses) {
|
||||
if (event.target.closest(className)) {
|
||||
if (target.closest(className)) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
@ -33,41 +49,41 @@ const handleClick = event => {
|
|||
// Process handlers
|
||||
clickHandlers.forEach(handler => {
|
||||
// Check that the click isn't inside the target
|
||||
if (handler.element.contains(event.target)) {
|
||||
if (handler.element.contains(target)) {
|
||||
return
|
||||
}
|
||||
|
||||
// Ignore clicks for certain classes unless we're nested inside them
|
||||
for (let className of conditionallyIgnoredClasses) {
|
||||
const sourceInside = handler.anchor.closest(className) != null
|
||||
const clickInside = event.target.closest(className) != null
|
||||
const clickInside = target.closest(className) != null
|
||||
if (clickInside && !sourceInside) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
handler.callback?.(event)
|
||||
handler.callback?.(e)
|
||||
})
|
||||
}
|
||||
|
||||
// On mouse up we only trigger a "click outside" callback if we targetted the
|
||||
// same element that we did on mouse down. This fixes all sorts of issues where
|
||||
// we get annoying callbacks firing when we drag to select text.
|
||||
const handleMouseUp = e => {
|
||||
const handleMouseUp = (e: MouseEvent) => {
|
||||
if (candidateTarget === e.target) {
|
||||
handleClick(e)
|
||||
}
|
||||
candidateTarget = null
|
||||
candidateTarget = undefined
|
||||
}
|
||||
|
||||
// On mouse down we store which element was targetted for comparison later
|
||||
const handleMouseDown = e => {
|
||||
const handleMouseDown = (e: MouseEvent) => {
|
||||
// Only handle the primary mouse button here.
|
||||
// We handle context menu (right click) events in another handler.
|
||||
if (e.button !== 0) {
|
||||
return
|
||||
}
|
||||
candidateTarget = e.target
|
||||
candidateTarget = e.target as HTMLElement
|
||||
|
||||
// Clear any previous listeners in case of multiple down events, and register
|
||||
// a single mouse up listener
|
||||
|
@ -82,7 +98,12 @@ document.addEventListener("contextmenu", handleClick)
|
|||
/**
|
||||
* Adds or updates a click handler
|
||||
*/
|
||||
const updateHandler = (id, element, anchor, callback) => {
|
||||
const updateHandler = (
|
||||
id: number,
|
||||
element: HTMLElement,
|
||||
anchor: HTMLElement,
|
||||
callback: ClickOutsideCallback | undefined
|
||||
) => {
|
||||
let existingHandler = clickHandlers.find(x => x.id === id)
|
||||
if (!existingHandler) {
|
||||
clickHandlers.push({ id, element, anchor, callback })
|
||||
|
@ -94,27 +115,52 @@ const updateHandler = (id, element, anchor, callback) => {
|
|||
/**
|
||||
* Removes a click handler
|
||||
*/
|
||||
const removeHandler = id => {
|
||||
const removeHandler = (id: number) => {
|
||||
clickHandlers = clickHandlers.filter(x => x.id !== id)
|
||||
}
|
||||
|
||||
/**
|
||||
* Svelte action to apply a click outside handler for a certain element
|
||||
* Svelte action to apply a click outside handler for a certain element.
|
||||
* opts.anchor is an optional param specifying the real root source of the
|
||||
* component being observed. This is required for things like popovers, where
|
||||
* the element using the clickoutside action is the popover, but the popover is
|
||||
* rendered at the root of the DOM somewhere, whereas the popover anchor is the
|
||||
* element we actually want to consider when determining the source component.
|
||||
*/
|
||||
export default (element, opts) => {
|
||||
export default (
|
||||
element: HTMLElement,
|
||||
opts?: ClickOutsideOpts | ClickOutsideCallback
|
||||
) => {
|
||||
const id = Math.random()
|
||||
const update = newOpts => {
|
||||
const callback =
|
||||
newOpts?.callback || (typeof newOpts === "function" ? newOpts : null)
|
||||
const anchor = newOpts?.anchor || element
|
||||
|
||||
const isCallback = (
|
||||
opts?: ClickOutsideOpts | ClickOutsideCallback
|
||||
): opts is ClickOutsideCallback => {
|
||||
return typeof opts === "function"
|
||||
}
|
||||
|
||||
const isOpts = (
|
||||
opts?: ClickOutsideOpts | ClickOutsideCallback
|
||||
): opts is ClickOutsideOpts => {
|
||||
return opts != null && typeof opts === "object"
|
||||
}
|
||||
|
||||
const update = (newOpts?: ClickOutsideOpts | ClickOutsideCallback) => {
|
||||
let callback: ClickOutsideCallback | undefined
|
||||
let anchor = element
|
||||
if (isCallback(newOpts)) {
|
||||
callback = newOpts
|
||||
} else if (isOpts(newOpts)) {
|
||||
callback = newOpts.callback
|
||||
if (newOpts.anchor) {
|
||||
anchor = newOpts.anchor
|
||||
}
|
||||
}
|
||||
updateHandler(id, element, anchor, callback)
|
||||
}
|
||||
|
||||
update(opts)
|
||||
|
||||
return {
|
||||
update,
|
||||
destroy: () => removeHandler(id),
|
|
@ -1,10 +1,4 @@
|
|||
/**
|
||||
* Valid alignment options are
|
||||
* - left
|
||||
* - right
|
||||
* - left-outside
|
||||
* - right-outside
|
||||
**/
|
||||
import { PopoverAlignment } from "../constants"
|
||||
|
||||
// Strategies are defined as [Popover]To[Anchor].
|
||||
// They can apply for both horizontal and vertical alignment.
|
||||
|
@ -149,20 +143,29 @@ export default function positionDropdown(element, opts) {
|
|||
}
|
||||
|
||||
// Determine X strategy
|
||||
if (align === "right") {
|
||||
if (align === PopoverAlignment.Right) {
|
||||
applyXStrategy(Strategies.EndToEnd)
|
||||
} else if (align === "right-outside" || align === "right-context-menu") {
|
||||
} else if (
|
||||
align === PopoverAlignment.RightOutside ||
|
||||
align === PopoverAlignment.RightContextMenu
|
||||
) {
|
||||
applyXStrategy(Strategies.StartToEnd)
|
||||
} else if (align === "left-outside" || align === "left-context-menu") {
|
||||
} else if (
|
||||
align === PopoverAlignment.LeftOutside ||
|
||||
align === PopoverAlignment.LeftContextMenu
|
||||
) {
|
||||
applyXStrategy(Strategies.EndToStart)
|
||||
} else if (align === "center") {
|
||||
} else if (align === PopoverAlignment.Center) {
|
||||
applyXStrategy(Strategies.MidPoint)
|
||||
} else {
|
||||
applyXStrategy(Strategies.StartToStart)
|
||||
}
|
||||
|
||||
// Determine Y strategy
|
||||
if (align === "right-outside" || align === "left-outside") {
|
||||
if (
|
||||
align === PopoverAlignment.RightOutside ||
|
||||
align === PopoverAlignment.LeftOutside
|
||||
) {
|
||||
applyYStrategy(Strategies.MidPoint)
|
||||
} else if (
|
||||
align === "right-context-menu" ||
|
||||
|
|
|
@ -1,13 +1,10 @@
|
|||
<script lang="ts">
|
||||
import {
|
||||
default as AbsTooltip,
|
||||
TooltipPosition,
|
||||
TooltipType,
|
||||
} from "../Tooltip/AbsTooltip.svelte"
|
||||
import AbsTooltip from "../Tooltip/AbsTooltip.svelte"
|
||||
import { TooltipPosition, TooltipType } from "../constants"
|
||||
|
||||
export let name: string = "Add"
|
||||
export let size: "XS" | "S" | "M" | "L" | "XL" = "M"
|
||||
export let hidden: boolean = false
|
||||
export let size = "M"
|
||||
export let hoverable: boolean = false
|
||||
export let disabled: boolean = false
|
||||
export let color: string | undefined = undefined
|
||||
|
@ -81,17 +78,6 @@
|
|||
color: var(--spectrum-global-color-gray-500) !important;
|
||||
pointer-events: none !important;
|
||||
}
|
||||
|
||||
.tooltip {
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
left: 50%;
|
||||
bottom: calc(100% + 4px);
|
||||
transform: translateX(-50%);
|
||||
text-align: center;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.spectrum-Icon--sizeXS {
|
||||
width: var(--spectrum-global-dimension-size-150);
|
||||
height: var(--spectrum-global-dimension-size-150);
|
||||
|
|
|
@ -9,8 +9,8 @@
|
|||
export let primary = false
|
||||
export let secondary = false
|
||||
export let overBackground = false
|
||||
export let target
|
||||
export let download
|
||||
export let target = undefined
|
||||
export let download = undefined
|
||||
export let disabled = false
|
||||
export let tooltip = null
|
||||
|
||||
|
|
|
@ -1,4 +1,11 @@
|
|||
<script>
|
||||
<script context="module" lang="ts">
|
||||
export interface PopoverAPI {
|
||||
show: () => void
|
||||
hide: () => void
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import "@spectrum-css/popover/dist/index-vars.css"
|
||||
import Portal from "svelte-portal"
|
||||
import { createEventDispatcher, getContext, onDestroy } from "svelte"
|
||||
|
@ -6,34 +13,39 @@
|
|||
import clickOutside from "../Actions/click_outside"
|
||||
import { fly } from "svelte/transition"
|
||||
import Context from "../context"
|
||||
import { PopoverAlignment } from "../constants"
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
export let anchor
|
||||
export let align = "right"
|
||||
export let portalTarget
|
||||
export let minWidth
|
||||
export let maxWidth
|
||||
export let maxHeight
|
||||
export let open = false
|
||||
export let useAnchorWidth = false
|
||||
export let dismissible = true
|
||||
export let offset = 4
|
||||
export let customHeight
|
||||
export let animate = true
|
||||
export let customZindex
|
||||
export let handlePostionUpdate
|
||||
export let showPopover = true
|
||||
export let clickOutsideOverride = false
|
||||
export let resizable = true
|
||||
export let wrap = false
|
||||
export let anchor: HTMLElement | undefined = undefined
|
||||
export let align: PopoverAlignment = PopoverAlignment.Right
|
||||
export let portalTarget: string | undefined = undefined
|
||||
export let minWidth: number | undefined = undefined
|
||||
export let maxWidth: number | undefined = undefined
|
||||
export let maxHeight: number | undefined = undefined
|
||||
export let open: boolean = false
|
||||
export let useAnchorWidth: boolean = false
|
||||
export let dismissible: boolean = true
|
||||
export let offset: number = 4
|
||||
export let customHeight: string | undefined = undefined
|
||||
export let animate: boolean = true
|
||||
export let customZIndex: number | undefined = undefined
|
||||
export let handlePositionUpdate: Function | undefined = undefined
|
||||
export let showPopover: boolean = true
|
||||
export let clickOutsideOverride: boolean = false
|
||||
export let resizable: boolean = true
|
||||
export let wrap: boolean = false
|
||||
|
||||
const animationDuration = 260
|
||||
|
||||
let timeout
|
||||
let timeout: ReturnType<typeof setTimeout> | undefined
|
||||
let blockPointerEvents = false
|
||||
|
||||
$: target = portalTarget || getContext(Context.PopoverRoot) || ".spectrum"
|
||||
// Portal library lacks types, so we have to type this as any even though it's
|
||||
// actually a string
|
||||
$: target = (portalTarget ||
|
||||
getContext(Context.PopoverRoot) ||
|
||||
".spectrum") as any
|
||||
$: {
|
||||
// Disable pointer events for the initial part of the animation, because we
|
||||
// fly from top to bottom and initially can be positioned under the cursor,
|
||||
|
@ -65,13 +77,13 @@
|
|||
}
|
||||
}
|
||||
|
||||
const handleOutsideClick = e => {
|
||||
const handleOutsideClick = (e: MouseEvent) => {
|
||||
if (clickOutsideOverride) {
|
||||
return
|
||||
}
|
||||
if (open) {
|
||||
// Stop propagation if the source is the anchor
|
||||
let node = e.target
|
||||
let node = e.target as Node
|
||||
let fromAnchor = false
|
||||
while (!fromAnchor && node && node.parentNode) {
|
||||
fromAnchor = node === anchor
|
||||
|
@ -86,7 +98,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
function handleEscape(e) {
|
||||
function handleEscape(e: KeyboardEvent) {
|
||||
if (!clickOutsideOverride) {
|
||||
return
|
||||
}
|
||||
|
@ -113,7 +125,7 @@
|
|||
minWidth,
|
||||
useAnchorWidth,
|
||||
offset,
|
||||
customUpdate: handlePostionUpdate,
|
||||
customUpdate: handlePositionUpdate,
|
||||
resizable,
|
||||
wrap,
|
||||
}}
|
||||
|
@ -123,11 +135,11 @@
|
|||
}}
|
||||
on:keydown={handleEscape}
|
||||
class="spectrum-Popover is-open"
|
||||
class:customZindex
|
||||
class:customZIndex
|
||||
class:hidden={!showPopover}
|
||||
class:blockPointerEvents
|
||||
role="presentation"
|
||||
style="height: {customHeight}; --customZindex: {customZindex};"
|
||||
style="height: {customHeight}; --customZIndex: {customZIndex};"
|
||||
transition:fly|local={{
|
||||
y: -20,
|
||||
duration: animate ? animationDuration : 0,
|
||||
|
@ -157,7 +169,7 @@
|
|||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
.customZindex {
|
||||
z-index: var(--customZindex) !important;
|
||||
.customZIndex {
|
||||
z-index: var(--customZIndex) !important;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,38 +1,24 @@
|
|||
<script context="module">
|
||||
export const TooltipPosition = {
|
||||
Top: "top",
|
||||
Right: "right",
|
||||
Bottom: "bottom",
|
||||
Left: "left",
|
||||
}
|
||||
export const TooltipType = {
|
||||
Default: "default",
|
||||
Info: "info",
|
||||
Positive: "positive",
|
||||
Negative: "negative",
|
||||
}
|
||||
</script>
|
||||
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import Portal from "svelte-portal"
|
||||
import { fade } from "svelte/transition"
|
||||
import "@spectrum-css/tooltip/dist/index-vars.css"
|
||||
import { onDestroy } from "svelte"
|
||||
import { TooltipPosition, TooltipType } from "../constants"
|
||||
|
||||
export let position = TooltipPosition.Top
|
||||
export let type = TooltipType.Default
|
||||
export let text = ""
|
||||
export let fixed = false
|
||||
export let color = ""
|
||||
export let noWrap = false
|
||||
export let position: TooltipPosition = TooltipPosition.Top
|
||||
export let type: TooltipType = TooltipType.Default
|
||||
export let text: string = ""
|
||||
export let fixed: boolean = false
|
||||
export let color: string | undefined = undefined
|
||||
export let noWrap: boolean = false
|
||||
|
||||
let wrapper
|
||||
let wrapper: HTMLElement | undefined
|
||||
let hovered = false
|
||||
let left
|
||||
let top
|
||||
let left: number | undefined
|
||||
let top: number | undefined
|
||||
let visible = false
|
||||
let timeout
|
||||
let interval
|
||||
let timeout: ReturnType<typeof setTimeout> | undefined
|
||||
let interval: ReturnType<typeof setInterval> | undefined
|
||||
|
||||
$: {
|
||||
if (hovered || fixed) {
|
||||
|
@ -49,8 +35,8 @@
|
|||
const updateTooltipPosition = () => {
|
||||
const node = wrapper?.children?.[0]
|
||||
if (!node) {
|
||||
left = null
|
||||
top = null
|
||||
left = undefined
|
||||
top = undefined
|
||||
return
|
||||
}
|
||||
const bounds = node.getBoundingClientRect()
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
export enum PopoverAlignment {
|
||||
Left = "left",
|
||||
Right = "right",
|
||||
LeftOutside = "left-outside",
|
||||
RightOutside = "right-outside",
|
||||
Center = "center",
|
||||
RightContextMenu = "right-context-menu",
|
||||
LeftContextMenu = "left-context-menu",
|
||||
}
|
||||
|
||||
export enum TooltipPosition {
|
||||
Top = "top",
|
||||
Right = "right",
|
||||
Bottom = "bottom",
|
||||
Left = "left",
|
||||
}
|
||||
|
||||
export enum TooltipType {
|
||||
Default = "default",
|
||||
Info = "info",
|
||||
Positive = "positive",
|
||||
Negative = "negative",
|
||||
}
|
|
@ -1,8 +1,9 @@
|
|||
import "./bbui.css"
|
||||
|
||||
// Spectrum icons
|
||||
import "@spectrum-css/icon/dist/index-vars.css"
|
||||
|
||||
// Constants
|
||||
export * from "./constants"
|
||||
|
||||
// Form components
|
||||
export { default as Input } from "./Form/Input.svelte"
|
||||
export { default as Stepper } from "./Form/Stepper.svelte"
|
||||
|
@ -45,7 +46,7 @@ export { default as ClearButton } from "./ClearButton/ClearButton.svelte"
|
|||
export { default as Icon } from "./Icon/Icon.svelte"
|
||||
export { default as IconAvatar } from "./Icon/IconAvatar.svelte"
|
||||
export { default as DetailSummary } from "./DetailSummary/DetailSummary.svelte"
|
||||
export { default as Popover } from "./Popover/Popover.svelte"
|
||||
export { default as Popover, type PopoverAPI } from "./Popover/Popover.svelte"
|
||||
export { default as ProgressBar } from "./ProgressBar/ProgressBar.svelte"
|
||||
export { default as ProgressCircle } from "./ProgressCircle/ProgressCircle.svelte"
|
||||
export { default as Label } from "./Label/Label.svelte"
|
||||
|
@ -92,7 +93,6 @@ export { default as IconSideNav } from "./IconSideNav/IconSideNav.svelte"
|
|||
export { default as IconSideNavItem } from "./IconSideNav/IconSideNavItem.svelte"
|
||||
export { default as Accordion } from "./Accordion/Accordion.svelte"
|
||||
export { default as AbsTooltip } from "./Tooltip/AbsTooltip.svelte"
|
||||
export { TooltipPosition, TooltipType } from "./Tooltip/AbsTooltip.svelte"
|
||||
|
||||
// Renderers
|
||||
export { default as BoldRenderer } from "./Table/BoldRenderer.svelte"
|
||||
|
|
|
@ -16,4 +16,4 @@
|
|||
},
|
||||
"include": ["./src/**/*"],
|
||||
"exclude": ["node_modules", "**/*.json", "**/*.spec.ts", "**/*.spec.js"]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -81,7 +81,7 @@
|
|||
"shortid": "2.2.15",
|
||||
"svelte-dnd-action": "^0.9.8",
|
||||
"svelte-loading-spinners": "^0.1.1",
|
||||
"svelte-portal": "1.0.0",
|
||||
"svelte-portal": "^2.2.1",
|
||||
"yup": "^0.32.11"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
@ -1,14 +1,20 @@
|
|||
<script>
|
||||
import { Popover, Icon } from "@budibase/bbui"
|
||||
<script lang="ts">
|
||||
import {
|
||||
Popover,
|
||||
PopoverAlignment,
|
||||
Icon,
|
||||
type PopoverAPI,
|
||||
} from "@budibase/bbui"
|
||||
|
||||
export let title
|
||||
export let align = "left"
|
||||
export let showPopover
|
||||
export let width
|
||||
export let title: string = ""
|
||||
export let subtitle: string | undefined = undefined
|
||||
export let align: PopoverAlignment = PopoverAlignment.Left
|
||||
export let showPopover: boolean = true
|
||||
export let width: number | undefined = undefined
|
||||
|
||||
let popover
|
||||
let anchor
|
||||
let open
|
||||
let popover: PopoverAPI | undefined
|
||||
let anchor: HTMLElement | undefined
|
||||
let open: boolean = false
|
||||
|
||||
export const show = () => popover?.show()
|
||||
export const hide = () => popover?.hide()
|
||||
|
@ -30,20 +36,24 @@
|
|||
{showPopover}
|
||||
on:open
|
||||
on:close
|
||||
customZindex={100}
|
||||
customZIndex={100}
|
||||
>
|
||||
<div class="detail-popover">
|
||||
<div class="detail-popover__header">
|
||||
<div class="detail-popover__title">
|
||||
{title}
|
||||
<Icon
|
||||
name="Close"
|
||||
hoverable
|
||||
color="var(--spectrum-global-color-gray-600)"
|
||||
hoverColor="var(--spectrum-global-color-gray-900)"
|
||||
on:click={hide}
|
||||
size="S"
|
||||
/>
|
||||
</div>
|
||||
<Icon
|
||||
name="Close"
|
||||
hoverable
|
||||
color="var(--spectrum-global-color-gray-600)"
|
||||
hoverColor="var(--spectum-global-color-gray-900)"
|
||||
on:click={hide}
|
||||
/>
|
||||
{#if subtitle}
|
||||
<div class="detail-popover__subtitle">{subtitle}</div>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="detail-popover__body">
|
||||
<slot />
|
||||
|
@ -56,14 +66,18 @@
|
|||
background-color: var(--spectrum-alias-background-color-primary);
|
||||
}
|
||||
.detail-popover__header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
border-bottom: 1px solid var(--spectrum-global-color-gray-300);
|
||||
padding: var(--spacing-l) var(--spacing-xl);
|
||||
gap: var(--spacing-s);
|
||||
}
|
||||
.detail-popover__title {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid var(--spectrum-global-color-gray-300);
|
||||
padding: var(--spacing-l) var(--spacing-xl);
|
||||
}
|
||||
.detail-popover__title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,282 @@
|
|||
<script context="module" lang="ts">
|
||||
interface JSONViewerClickContext {
|
||||
label: string | undefined
|
||||
value: any
|
||||
path: (string | number)[]
|
||||
}
|
||||
export interface JSONViewerClickEvent {
|
||||
detail: JSONViewerClickContext
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { Icon } from "@budibase/bbui"
|
||||
import { createEventDispatcher } from "svelte"
|
||||
|
||||
export let label: string | undefined = undefined
|
||||
export let value: any = undefined
|
||||
export let root: boolean = true
|
||||
export let path: (string | number)[] = []
|
||||
export let showCopyIcon: boolean = false
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const Colors = {
|
||||
Array: "var(--spectrum-global-color-gray-600)",
|
||||
Object: "var(--spectrum-global-color-gray-600)",
|
||||
Other: "var(--spectrum-global-color-blue-700)",
|
||||
Undefined: "var(--spectrum-global-color-gray-600)",
|
||||
Null: "var(--spectrum-global-color-yellow-700)",
|
||||
String: "var(--spectrum-global-color-orange-700)",
|
||||
Number: "var(--spectrum-global-color-purple-700)",
|
||||
True: "var(--spectrum-global-color-celery-700)",
|
||||
False: "var(--spectrum-global-color-red-700)",
|
||||
Date: "var(--spectrum-global-color-green-700)",
|
||||
}
|
||||
|
||||
let expanded = false
|
||||
let valueExpanded = false
|
||||
let clickContext: JSONViewerClickContext
|
||||
|
||||
$: isArray = Array.isArray(value)
|
||||
$: isObject = value?.toString?.() === "[object Object]"
|
||||
$: primitive = !(isArray || isObject)
|
||||
$: keys = getKeys(isArray, isObject, value)
|
||||
$: expandable = keys.length > 0
|
||||
$: displayValue = getDisplayValue(isArray, isObject, keys, value)
|
||||
$: style = getStyle(isArray, isObject, value)
|
||||
$: clickContext = { value, label, path }
|
||||
|
||||
const getKeys = (isArray: boolean, isObject: boolean, value: any) => {
|
||||
if (isArray) {
|
||||
return [...value.keys()]
|
||||
}
|
||||
if (isObject) {
|
||||
return Object.keys(value).sort()
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
const pluralise = (text: string, number: number) => {
|
||||
return number === 1 ? text : text + "s"
|
||||
}
|
||||
|
||||
const getDisplayValue = (
|
||||
isArray: boolean,
|
||||
isObject: boolean,
|
||||
keys: any[],
|
||||
value: any
|
||||
) => {
|
||||
if (isArray) {
|
||||
return `[] ${keys.length} ${pluralise("item", keys.length)}`
|
||||
}
|
||||
if (isObject) {
|
||||
return `{} ${keys.length} ${pluralise("key", keys.length)}`
|
||||
}
|
||||
if (typeof value === "object" && typeof value?.toString === "function") {
|
||||
return value.toString()
|
||||
} else {
|
||||
return JSON.stringify(value, null, 2)
|
||||
}
|
||||
}
|
||||
|
||||
const getStyle = (isArray: boolean, isObject: boolean, value: any) => {
|
||||
return `color:${getColor(isArray, isObject, value)};`
|
||||
}
|
||||
|
||||
const getColor = (isArray: boolean, isObject: boolean, value: any) => {
|
||||
if (isArray) {
|
||||
return Colors.Array
|
||||
}
|
||||
if (isObject) {
|
||||
return Colors.Object
|
||||
}
|
||||
if (value instanceof Date) {
|
||||
return Colors.Date
|
||||
}
|
||||
switch (value) {
|
||||
case undefined:
|
||||
return Colors.Undefined
|
||||
case null:
|
||||
return Colors.Null
|
||||
case true:
|
||||
return Colors.True
|
||||
case false:
|
||||
return Colors.False
|
||||
}
|
||||
switch (typeof value) {
|
||||
case "string":
|
||||
return Colors.String
|
||||
case "number":
|
||||
return Colors.Number
|
||||
}
|
||||
return Colors.Other
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div class="binding-node">
|
||||
{#if label != null}
|
||||
<div class="binding-text">
|
||||
<div class="binding-arrow" class:expanded>
|
||||
{#if expandable}
|
||||
<Icon
|
||||
name="Play"
|
||||
hoverable
|
||||
color="var(--spectrum-global-color-gray-600)"
|
||||
hoverColor="var(--spectrum-global-color-gray-900)"
|
||||
on:click={() => (expanded = !expanded)}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
<div
|
||||
class="binding-label"
|
||||
class:primitive
|
||||
class:expandable
|
||||
on:click={() => (expanded = !expanded)}
|
||||
on:click={() => dispatch("click-label", clickContext)}
|
||||
>
|
||||
{label}
|
||||
</div>
|
||||
<div
|
||||
class="binding-value"
|
||||
class:primitive
|
||||
class:expanded={valueExpanded}
|
||||
{style}
|
||||
on:click={() => (valueExpanded = !valueExpanded)}
|
||||
on:click={() => dispatch("click-value", clickContext)}
|
||||
>
|
||||
{displayValue}
|
||||
</div>
|
||||
{#if showCopyIcon}
|
||||
<div class="copy-value-icon">
|
||||
<Icon
|
||||
name="Copy"
|
||||
size="XS"
|
||||
hoverable
|
||||
color="var(--spectrum-global-color-gray-600)"
|
||||
hoverColor="var(--spectrum-global-color-gray-900)"
|
||||
on:click={() => dispatch("click-copy", clickContext)}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
{#if expandable && (expanded || label == null)}
|
||||
<div class="binding-children" class:root>
|
||||
{#each keys as key}
|
||||
<svelte:self
|
||||
label={key}
|
||||
value={value[key]}
|
||||
root={false}
|
||||
path={[...path, key]}
|
||||
{showCopyIcon}
|
||||
on:click-label
|
||||
on:click-value
|
||||
on:click-copy
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.binding-node {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Expand arrow */
|
||||
.binding-arrow {
|
||||
margin: -3px 6px -2px 4px;
|
||||
flex: 0 0 9px;
|
||||
transition: transform 130ms ease-out;
|
||||
}
|
||||
.binding-arrow :global(svg) {
|
||||
width: 9px;
|
||||
}
|
||||
.binding-arrow.expanded {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
/* Main text wrapper */
|
||||
.binding-text {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
font-family: monospace;
|
||||
font-size: 12px;
|
||||
align-items: flex-start;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Size label and value according to type */
|
||||
.binding-label {
|
||||
flex: 0 1 auto;
|
||||
margin-right: 8px;
|
||||
transition: color 130ms ease-out;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.binding-label.expandable:hover {
|
||||
cursor: pointer;
|
||||
color: var(--spectrum-global-color-gray-900);
|
||||
}
|
||||
.binding-value {
|
||||
flex: 0 0 auto;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
transition: filter 130ms ease-out;
|
||||
user-select: none;
|
||||
}
|
||||
.binding-value.primitive:hover {
|
||||
filter: brightness(1.25);
|
||||
cursor: pointer;
|
||||
}
|
||||
.binding-value.expanded {
|
||||
word-break: break-all;
|
||||
white-space: wrap;
|
||||
}
|
||||
.binding-label.primitive {
|
||||
flex: 0 0 auto;
|
||||
max-width: 75%;
|
||||
}
|
||||
.binding-value.primitive {
|
||||
flex: 0 1 auto;
|
||||
}
|
||||
|
||||
/* Trim spans in the highlighted HTML */
|
||||
.binding-value :global(span) {
|
||||
overflow: hidden !important;
|
||||
text-overflow: ellipsis !important;
|
||||
white-space: nowrap !important;
|
||||
}
|
||||
|
||||
/* Copy icon for value */
|
||||
.copy-value-icon {
|
||||
display: none;
|
||||
margin-left: 8px;
|
||||
}
|
||||
.binding-text:hover .copy-value-icon {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Children wrapper */
|
||||
.binding-children {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
border-left: 1px solid var(--spectrum-global-color-gray-400);
|
||||
margin-left: 20px;
|
||||
padding-left: 3px;
|
||||
}
|
||||
.binding-children.root {
|
||||
border-left: none;
|
||||
margin-left: 0;
|
||||
padding-left: 0;
|
||||
}
|
||||
</style>
|
|
@ -25,7 +25,7 @@
|
|||
</div>
|
||||
|
||||
<Popover
|
||||
customZindex={998}
|
||||
customZIndex={998}
|
||||
bind:this={formPopover}
|
||||
align="center"
|
||||
anchor={formPopoverAnchor}
|
||||
|
|
|
@ -96,8 +96,8 @@
|
|||
maxWidth={300}
|
||||
dismissible={false}
|
||||
offset={12}
|
||||
handlePostionUpdate={tourStep?.positionHandler}
|
||||
customZindex={3}
|
||||
handlePositionUpdate={tourStep?.positionHandler}
|
||||
customZIndex={3}
|
||||
>
|
||||
<div class="tour-content">
|
||||
<Layout noPadding gap="M">
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
if (id === `${$screenStore.selectedScreenId}-screen`) return true
|
||||
if (id === `${$screenStore.selectedScreenId}-navigation`) return true
|
||||
|
||||
return !!findComponent($selectedScreen.props, id)
|
||||
return !!findComponent($selectedScreen?.props, id)
|
||||
}
|
||||
|
||||
// Keep URL and state in sync for selected component ID
|
||||
|
|
|
@ -3,9 +3,6 @@
|
|||
import AppPreview from "./AppPreview.svelte"
|
||||
import { screenStore, appStore } from "@/stores/builder"
|
||||
import UndoRedoControl from "@/components/common/UndoRedoControl.svelte"
|
||||
import { ActionButton } from "@budibase/bbui"
|
||||
import BindingsPanel from "./BindingsPanel.svelte"
|
||||
import StatePanel from "./StatePanel.svelte"
|
||||
</script>
|
||||
|
||||
<div class="app-panel">
|
||||
|
@ -13,14 +10,12 @@
|
|||
<div class="header">
|
||||
<div class="header-left">
|
||||
<UndoRedoControl store={screenStore.history} />
|
||||
</div>
|
||||
<div class="header-right">
|
||||
{#if $appStore.clientFeatures.devicePreview}
|
||||
<DevicePreviewSelect />
|
||||
{/if}
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<BindingsPanel />
|
||||
<StatePanel />
|
||||
</div>
|
||||
</div>
|
||||
<div class="content">
|
||||
{#key $appStore.version}
|
||||
|
|
|
@ -1,14 +1,99 @@
|
|||
<script>
|
||||
import { ActionButton, Modal, ModalContent } from "@budibase/bbui"
|
||||
<script lang="ts">
|
||||
import { onMount } from "svelte"
|
||||
import { Link, Body, Helpers, Layout, notifications } from "@budibase/bbui"
|
||||
import { processObjectSync } from "@budibase/string-templates"
|
||||
import {
|
||||
previewStore,
|
||||
selectedScreen,
|
||||
componentStore,
|
||||
snippets,
|
||||
} from "@/stores/builder"
|
||||
import { getBindableProperties } from "@/dataBinding"
|
||||
import JSONViewer, {
|
||||
type JSONViewerClickEvent,
|
||||
} from "@/components/common/JSONViewer.svelte"
|
||||
|
||||
let visible = false
|
||||
let modal
|
||||
// Minimal typing for the real data binding structure, as none exists
|
||||
type DataBinding = {
|
||||
category: string
|
||||
runtimeBinding: string
|
||||
readableBinding: string
|
||||
}
|
||||
|
||||
$: previewContext = $previewStore.selectedComponentContext || {}
|
||||
$: selectedComponentId = $componentStore.selectedComponentId
|
||||
$: context = makeContext(previewContext, bindings)
|
||||
$: bindings = getBindableProperties($selectedScreen, selectedComponentId)
|
||||
|
||||
const makeContext = (
|
||||
previewContext: Record<string, any>,
|
||||
bindings: DataBinding[]
|
||||
) => {
|
||||
// Create a single big array to enrich in one go
|
||||
const bindingStrings = bindings.map(binding => {
|
||||
if (binding.runtimeBinding.startsWith('trim "')) {
|
||||
// Account for nasty hardcoded HBS bindings for roles, for legacy
|
||||
// compatibility
|
||||
return `{{ ${binding.runtimeBinding} }}`
|
||||
} else {
|
||||
return `{{ literal ${binding.runtimeBinding} }}`
|
||||
}
|
||||
})
|
||||
const bindingEvaluations = processObjectSync(bindingStrings, {
|
||||
...previewContext,
|
||||
snippets: $snippets,
|
||||
}) as any[]
|
||||
|
||||
// Deeply set values for all readable bindings
|
||||
const enrichedBindings: any[] = bindings.map((binding, idx) => {
|
||||
return {
|
||||
...binding,
|
||||
value: bindingEvaluations[idx],
|
||||
}
|
||||
})
|
||||
let context = {}
|
||||
for (let binding of enrichedBindings) {
|
||||
Helpers.deepSet(context, binding.readableBinding, binding.value)
|
||||
}
|
||||
return context
|
||||
}
|
||||
|
||||
const copyBinding = (e: JSONViewerClickEvent) => {
|
||||
const readableBinding = `{{ ${e.detail.path.join(".")} }}`
|
||||
Helpers.copyToClipboard(readableBinding)
|
||||
notifications.success("Binding copied to clipboard")
|
||||
}
|
||||
|
||||
onMount(previewStore.requestComponentContext)
|
||||
</script>
|
||||
|
||||
<ActionButton on:click={modal.show}>Bindings</ActionButton>
|
||||
<div class="bindings-panel">
|
||||
<Layout noPadding gap="S">
|
||||
<div class="text">
|
||||
<Body size="S">Showing all available bindings.</Body>
|
||||
<Link
|
||||
target="_blank"
|
||||
href="https://docs.budibase.com/docs/introduction-to-bindings"
|
||||
>
|
||||
Learn more.
|
||||
</Link>
|
||||
</div>
|
||||
<JSONViewer value={context} showCopyIcon on:click-copy={copyBinding} />
|
||||
</Layout>
|
||||
</div>
|
||||
|
||||
<Modal bind:this={modal}>
|
||||
<ModalContent title="Bindings" showConfirmButton={false} cancelText="Close">
|
||||
Some awesome bindings content.
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
<style>
|
||||
.bindings-panel {
|
||||
flex: 1 1 auto;
|
||||
height: 0;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
padding: var(--spacing-l);
|
||||
}
|
||||
.text {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 4px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
<script>
|
||||
import { notifications, Icon, Body } from "@budibase/bbui"
|
||||
import { isActive, goto } from "@roxi/routify"
|
||||
import { notifications, Icon } from "@budibase/bbui"
|
||||
import {
|
||||
selectedScreen,
|
||||
screenStore,
|
||||
|
@ -13,23 +12,12 @@
|
|||
import ComponentTree from "./ComponentTree.svelte"
|
||||
import { dndStore, DropPosition } from "./dndStore.js"
|
||||
import DNDPositionIndicator from "./DNDPositionIndicator.svelte"
|
||||
import ComponentKeyHandler from "./ComponentKeyHandler.svelte"
|
||||
import ComponentScrollWrapper from "./ComponentScrollWrapper.svelte"
|
||||
import getScreenContextMenuItems from "./getScreenContextMenuItems"
|
||||
|
||||
let scrolling = false
|
||||
|
||||
$: screenComponentId = `${$screenStore.selectedScreenId}-screen`
|
||||
$: navComponentId = `${$screenStore.selectedScreenId}-navigation`
|
||||
|
||||
const toNewComponentRoute = () => {
|
||||
if ($isActive(`./:componentId/new`)) {
|
||||
$goto(`./:componentId`)
|
||||
} else {
|
||||
$goto(`./:componentId/new`)
|
||||
}
|
||||
}
|
||||
|
||||
const onDrop = async () => {
|
||||
try {
|
||||
await dndStore.actions.drop()
|
||||
|
@ -39,10 +27,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
const handleScroll = e => {
|
||||
scrolling = e.target.scrollTop !== 0
|
||||
}
|
||||
|
||||
const hover = hoverStore.hover
|
||||
|
||||
// showCopy is used to hide the copy button when the user right-clicks the empty
|
||||
|
@ -72,17 +56,9 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div class="components">
|
||||
<div class="header" class:scrolling>
|
||||
<Body size="S">Components</Body>
|
||||
<div on:click={toNewComponentRoute} class="addButton">
|
||||
<Icon name="Add" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-panel">
|
||||
<ComponentScrollWrapper on:scroll={handleScroll}>
|
||||
<ComponentScrollWrapper>
|
||||
<ul
|
||||
class="componentTree"
|
||||
on:contextmenu={e => openScreenContextMenu(e, false)}
|
||||
|
@ -159,7 +135,6 @@
|
|||
</ul>
|
||||
</ComponentScrollWrapper>
|
||||
</div>
|
||||
<ComponentKeyHandler />
|
||||
</div>
|
||||
|
||||
<style>
|
||||
|
@ -168,35 +143,13 @@
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.header {
|
||||
height: 50px;
|
||||
box-sizing: border-box;
|
||||
padding: var(--spacing-l);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-bottom: 2px solid transparent;
|
||||
transition: border-bottom 130ms ease-out;
|
||||
}
|
||||
.header.scrolling {
|
||||
border-bottom: var(--border-light);
|
||||
padding-top: var(--spacing-l);
|
||||
}
|
||||
|
||||
.components :global(.nav-item) {
|
||||
padding-right: 8px !important;
|
||||
}
|
||||
|
||||
.addButton {
|
||||
margin-left: auto;
|
||||
color: var(--grey-7);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.addButton:hover {
|
||||
color: var(--ink);
|
||||
}
|
||||
|
||||
.list-panel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
|
|
@ -3,13 +3,15 @@
|
|||
import ComponentList from "./ComponentList/index.svelte"
|
||||
import { getHorizontalResizeActions } from "@/components/common/resizable"
|
||||
import { ActionButton } from "@budibase/bbui"
|
||||
import BindingsPanel from "./BindingsPanel.svelte"
|
||||
import ComponentKeyHandler from "./ComponentKeyHandler.svelte"
|
||||
|
||||
const [resizable, resizableHandle] = getHorizontalResizeActions()
|
||||
|
||||
enum Tabs {
|
||||
Components = "Components",
|
||||
Bindings = "Bindings",
|
||||
State = "State",
|
||||
const Tabs = {
|
||||
Components: "Components",
|
||||
Bindings: "Bindings",
|
||||
State: "State",
|
||||
}
|
||||
|
||||
let activeTab = Tabs.Components
|
||||
|
@ -32,7 +34,7 @@
|
|||
{#if activeTab === Tabs.Components}
|
||||
<ComponentList />
|
||||
{:else if activeTab === Tabs.Bindings}
|
||||
<div class="tab-content">Bindings</div>
|
||||
<BindingsPanel />
|
||||
{:else if activeTab === Tabs.State}
|
||||
<div class="tab-content">State</div>
|
||||
{/if}
|
||||
|
@ -41,6 +43,7 @@
|
|||
<div class="dividerClickExtender" role="separator" use:resizableHandle />
|
||||
</div>
|
||||
</div>
|
||||
<ComponentKeyHandler />
|
||||
|
||||
<style>
|
||||
.panel {
|
||||
|
@ -68,6 +71,10 @@
|
|||
border-bottom: var(--border-light);
|
||||
}
|
||||
.tab-content {
|
||||
flex: 1 1 auto;
|
||||
height: 0;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
padding: var(--spacing-l);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
<script>
|
||||
import { ActionButton, Modal, ModalContent } from "@budibase/bbui"
|
||||
|
||||
let visible = false
|
||||
let modal
|
||||
</script>
|
||||
|
||||
|
|
|
@ -40,26 +40,33 @@ function setupEnv(hosting, features = {}, flags = {}) {
|
|||
describe("AISettings", () => {
|
||||
let instance = null
|
||||
|
||||
const setupDOM = () => {
|
||||
instance = render(AISettings, {})
|
||||
const modalContainer = document.createElement("div")
|
||||
modalContainer.classList.add("modal-container")
|
||||
instance.baseElement.appendChild(modalContainer)
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks()
|
||||
})
|
||||
|
||||
it("that the AISettings is rendered", () => {
|
||||
instance = render(AISettings, {})
|
||||
setupDOM()
|
||||
expect(instance).toBeDefined()
|
||||
})
|
||||
|
||||
describe("Licensing", () => {
|
||||
it("should show the premium label on self host for custom configs", async () => {
|
||||
setupEnv(Hosting.Self)
|
||||
instance = render(AISettings, {})
|
||||
setupDOM()
|
||||
const premiumTag = instance.queryByText("Premium")
|
||||
expect(premiumTag).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("should show the enterprise label on cloud for custom configs", async () => {
|
||||
setupEnv(Hosting.Cloud)
|
||||
instance = render(AISettings, {})
|
||||
setupDOM()
|
||||
const enterpriseTag = instance.queryByText("Enterprise")
|
||||
expect(enterpriseTag).toBeInTheDocument()
|
||||
})
|
||||
|
@ -69,7 +76,7 @@ describe("AISettings", () => {
|
|||
let configModal
|
||||
|
||||
setupEnv(Hosting.Cloud)
|
||||
instance = render(AISettings)
|
||||
setupDOM()
|
||||
addConfigurationButton = instance.queryByText("Add configuration")
|
||||
expect(addConfigurationButton).toBeInTheDocument()
|
||||
await fireEvent.click(addConfigurationButton)
|
||||
|
@ -86,7 +93,7 @@ describe("AISettings", () => {
|
|||
{ customAIConfigsEnabled: true },
|
||||
{ AI_CUSTOM_CONFIGS: true }
|
||||
)
|
||||
instance = render(AISettings)
|
||||
setupDOM()
|
||||
addConfigurationButton = instance.queryByText("Add configuration")
|
||||
expect(addConfigurationButton).toBeInTheDocument()
|
||||
await fireEvent.click(addConfigurationButton)
|
||||
|
@ -103,7 +110,7 @@ describe("AISettings", () => {
|
|||
{ customAIConfigsEnabled: true },
|
||||
{ AI_CUSTOM_CONFIGS: true }
|
||||
)
|
||||
instance = render(AISettings)
|
||||
setupDOM()
|
||||
addConfigurationButton = instance.queryByText("Add configuration")
|
||||
expect(addConfigurationButton).toBeInTheDocument()
|
||||
await fireEvent.click(addConfigurationButton)
|
||||
|
|
|
@ -18997,10 +18997,10 @@ svelte-loading-spinners@^0.1.1:
|
|||
resolved "https://registry.yarnpkg.com/svelte-loading-spinners/-/svelte-loading-spinners-0.1.7.tgz#3fa6fa0ef67ab635773bf20b07d0b071debbadaa"
|
||||
integrity sha512-EKCId1DjVL2RSUVJJsvtNcqQHox03XIgh4xh/4p7r6ST7d8mut6INY9/LqK4A17PFU64+3quZmqiSfOlf480CA==
|
||||
|
||||
svelte-portal@1.0.0, svelte-portal@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/svelte-portal/-/svelte-portal-1.0.0.tgz#36a47c5578b1a4d9b4dc60fa32a904640ec4cdd3"
|
||||
integrity sha512-nHf+DS/jZ6jjnZSleBMSaZua9JlG5rZv9lOGKgJuaZStfevtjIlUJrkLc3vbV8QdBvPPVmvcjTlazAzfKu0v3Q==
|
||||
svelte-portal@^2.2.1:
|
||||
version "2.2.1"
|
||||
resolved "https://registry.yarnpkg.com/svelte-portal/-/svelte-portal-2.2.1.tgz#b1d7bed78e56318db245996beb5483d8de6b9740"
|
||||
integrity sha512-uF7is5sM4aq5iN7QF/67XLnTUvQCf2iiG/B1BHTqLwYVY1dsVmTeXZ/LeEyU6dLjApOQdbEG9lkqHzxiQtOLEQ==
|
||||
|
||||
svelte-spa-router@^4.0.1:
|
||||
version "4.0.1"
|
||||
|
|
Loading…
Reference in New Issue