Merge branch 'master' into feature/filter-component

This commit is contained in:
deanhannigan 2025-05-06 10:51:01 +01:00 committed by GitHub
commit 380562a1dd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 135 additions and 97 deletions

View File

@ -11,7 +11,7 @@
export let hoverColor: string | undefined = undefined export let hoverColor: string | undefined = undefined
export let tooltip: string | undefined = undefined export let tooltip: string | undefined = undefined
export let tooltipPosition: TooltipPosition = TooltipPosition.Bottom export let tooltipPosition: TooltipPosition = TooltipPosition.Bottom
export let tooltipType = TooltipType.Default export let tooltipType: TooltipType = TooltipType.Default
export let tooltipColor: string | undefined = undefined export let tooltipColor: string | undefined = undefined
export let tooltipWrap: boolean = true export let tooltipWrap: boolean = true
export let newStyles: boolean = false export let newStyles: boolean = false

View File

@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
export let horizontal: boolean = false export let horizontal: boolean = false
export let paddingX: "S" | "M" | "L" | "XL" | "XXL" = "M" export let paddingX: "S" | "M" | "L" | "XL" | "XXL" = "M"
export let paddingY: "S" | "M" | "L" | "XL" | "XXL" = "M" export let paddingY: "none" | "S" | "M" | "L" | "XL" | "XXL" = "M"
export let noPadding: boolean = false export let noPadding: boolean = false
export let gap: "XXS" | "XS" | "S" | "M" | "L" | "XL" = "M" export let gap: "XXS" | "XS" | "S" | "M" | "L" | "XL" = "M"
export let noGap: boolean = false export let noGap: boolean = false

View File

@ -1,9 +1,9 @@
import { ActionMenu } from "./types" import { ActionMenu, ModalContext, ScrollContext } from "./types"
import { ModalContext } from "./types"
declare module "svelte" { declare module "svelte" {
export function getContext(key: "actionMenu"): ActionMenu | undefined export function getContext(key: "actionMenu"): ActionMenu | undefined
export function getContext(key: "bbui-modal"): ModalContext export function getContext(key: "bbui-modal"): ModalContext
export function getContext(key: "scroll"): ScrollContext
} }
export const Modal = "bbui-modal" export const Modal = "bbui-modal"

View File

@ -1,3 +1,4 @@
export * from "./actionMenu" export * from "./actionMenu"
export * from "./envDropdown" export * from "./envDropdown"
export * from "./modalContext" export * from "./modalContext"
export * from "./scrollContext"

View File

@ -0,0 +1,3 @@
export interface ScrollContext {
scrollTo: (bounds: DOMRect) => void
}

View File

@ -1,15 +1,15 @@
<script> <script lang="ts">
import { tick } from "svelte" import { tick } from "svelte"
import { Icon, Body } from "@budibase/bbui" import { Icon, Body } from "@budibase/bbui"
import { keyUtils } from "@/helpers/keyUtils" import { keyUtils } from "@/helpers/keyUtils"
export let title export let title: string
export let placeholder export let placeholder: string
export let value export let value: string
export let onAdd export let onAdd: () => void
export let search export let search: boolean
let searchInput let searchInput: HTMLInputElement
const openSearch = async () => { const openSearch = async () => {
search = true search = true
@ -22,7 +22,7 @@
value = "" value = ""
} }
const onKeyDown = e => { const onKeyDown = (e: KeyboardEvent) => {
if (e.key === "Escape") { if (e.key === "Escape") {
closeSearch() closeSearch()
} }

View File

@ -1,46 +1,46 @@
<script> <script lang="ts">
import { Icon, TooltipType, TooltipPosition } from "@budibase/bbui" import { Icon, TooltipType, TooltipPosition } from "@budibase/bbui"
import { createEventDispatcher, getContext } from "svelte" import { createEventDispatcher, getContext } from "svelte"
import { helpers } from "@budibase/shared-core"
import { UserAvatars } from "@budibase/frontend-core" import { UserAvatars } from "@budibase/frontend-core"
import type { UIUser } from "@budibase/types"
export let icon export let icon: string | null
export let iconTooltip export let iconTooltip: string = ""
export let withArrow = false export let withArrow: boolean = false
export let withActions = true export let withActions: boolean = true
export let showActions = false export let showActions: boolean = false
export let indentLevel = 0 export let indentLevel: number = 0
export let text export let text: string
export let border = true export let border: boolean = true
export let selected = false export let selected: boolean = false
export let opened = false export let opened: boolean = false
export let draggable = false export let draggable: boolean = false
export let iconText export let iconText: string = ""
export let iconColor export let iconColor: string = ""
export let scrollable = false export let scrollable: boolean = false
export let highlighted = false export let highlighted: boolean = false
export let rightAlignIcon = false export let rightAlignIcon: boolean = false
export let id export let id: string = ""
export let showTooltip = false export let showTooltip: boolean = false
export let selectedBy = null export let selectedBy: UIUser[] | null = null
export let compact = false export let compact: boolean = false
export let hovering = false export let hovering: boolean = false
export let disabled = false export let disabled: boolean = false
const scrollApi = getContext("scroll") const scrollApi = getContext("scroll")
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
let contentRef let contentRef: HTMLDivElement
$: selected && contentRef && scrollToView() $: selected && contentRef && scrollToView()
$: style = getStyle(indentLevel, selectedBy) $: style = getStyle(indentLevel)
const onClick = () => { const onClick = () => {
scrollToView() scrollToView()
dispatch("click") dispatch("click")
} }
const onIconClick = e => { const onIconClick = (e: Event) => {
e.stopPropagation() e.stopPropagation()
dispatch("iconClick") dispatch("iconClick")
} }
@ -53,11 +53,8 @@
scrollApi.scrollTo(bounds) scrollApi.scrollTo(bounds)
} }
const getStyle = (indentLevel, selectedBy) => { const getStyle = (indentLevel: number) => {
let style = `padding-left:calc(${indentLevel * 14}px);` let style = `padding-left:calc(${indentLevel * 14}px);`
if (selectedBy) {
style += `--selected-by-color:${helpers.getUserColor(selectedBy)};`
}
return style return style
} }
</script> </script>

View File

@ -1,25 +1,25 @@
<script> <script lang="ts">
import { ModalContent, Input } from "@budibase/bbui" import { ModalContent, Input } from "@budibase/bbui"
import sanitizeUrl from "@/helpers/sanitizeUrl" import sanitizeUrl from "@/helpers/sanitizeUrl"
import { get } from "svelte/store" import { get } from "svelte/store"
import { screenStore } from "@/stores/builder" import { screenStore } from "@/stores/builder"
export let onConfirm export let onConfirm: (_data: { route: string }) => Promise<void>
export let onCancel export let onCancel: (() => Promise<void>) | undefined = undefined
export let route export let route: string
export let role export let role: string | undefined
export let confirmText = "Continue" export let confirmText = "Continue"
const appPrefix = "/app" const appPrefix = "/app"
let touched = false let touched = false
let error let error: string | undefined
let modal let modal: ModalContent
$: appUrl = route $: appUrl = route
? `${window.location.origin}${appPrefix}${route}` ? `${window.location.origin}${appPrefix}${route}`
: `${window.location.origin}${appPrefix}` : `${window.location.origin}${appPrefix}`
const routeChanged = event => { const routeChanged = (event: { detail: string }) => {
if (!event.detail.startsWith("/")) { if (!event.detail.startsWith("/")) {
route = "/" + event.detail route = "/" + event.detail
} }
@ -28,11 +28,11 @@
if (routeExists(route)) { if (routeExists(route)) {
error = "This URL is already taken for this access role" error = "This URL is already taken for this access role"
} else { } else {
error = null error = undefined
} }
} }
const routeExists = url => { const routeExists = (url: string) => {
if (!role) { if (!role) {
return false return false
} }
@ -58,7 +58,7 @@
onConfirm={confirmScreenDetails} onConfirm={confirmScreenDetails}
{onCancel} {onCancel}
cancelText={"Back"} cancelText={"Back"}
disabled={!route || error || !touched} disabled={!route || !!error || !touched}
> >
<form on:submit|preventDefault={() => modal.confirm()}> <form on:submit|preventDefault={() => modal.confirm()}>
<Input <Input

View File

@ -11,7 +11,10 @@
$componentStore.selectedComponentId, $componentStore.selectedComponentId,
"RefreshDatasource", "RefreshDatasource",
{ includeSelf: nested } { includeSelf: nested }
) ).concat({
readableBinding: "All data providers",
runtimeBinding: "all",
})
</script> </script>
<div class="root"> <div class="root">

View File

@ -109,13 +109,12 @@
/> />
{/each} {/each}
</List> </List>
{/if}
<div> <div>
<Button secondary icon="Add" on:click={addOAuth2Configuration} <Button secondary icon="Add" on:click={addOAuth2Configuration}
>Add OAuth2</Button >Add OAuth2</Button
> >
</div> </div>
{/if}
</DetailPopover> </DetailPopover>
<style> <style>

View File

@ -1,4 +1,4 @@
<script> <script lang="ts">
import { Modal, Helpers, notifications, Icon } from "@budibase/bbui" import { Modal, Helpers, notifications, Icon } from "@budibase/bbui"
import { import {
navigationStore, navigationStore,
@ -14,13 +14,14 @@
import { makeComponentUnique } from "@/helpers/components" import { makeComponentUnique } from "@/helpers/components"
import { capitalise } from "@/helpers" import { capitalise } from "@/helpers"
import ConfirmDialog from "@/components/common/ConfirmDialog.svelte" import ConfirmDialog from "@/components/common/ConfirmDialog.svelte"
import type { Screen } from "@budibase/types"
export let screen export let screen
let confirmDeleteDialog let confirmDeleteDialog: ConfirmDialog
let screenDetailsModal let screenDetailsModal: Modal
const createDuplicateScreen = async ({ route }) => { const createDuplicateScreen = async ({ route }: { route: string }) => {
// Create a dupe and ensure it is unique // Create a dupe and ensure it is unique
let duplicateScreen = Helpers.cloneDeep(screen) let duplicateScreen = Helpers.cloneDeep(screen)
delete duplicateScreen._id delete duplicateScreen._id
@ -57,7 +58,7 @@
$: noPaste = !$componentStore.componentToPaste $: noPaste = !$componentStore.componentToPaste
const pasteComponent = mode => { const pasteComponent = (mode: "inside") => {
try { try {
componentStore.paste(screen.props, mode, screen) componentStore.paste(screen.props, mode, screen)
} catch (error) { } catch (error) {
@ -65,7 +66,7 @@
} }
} }
const openContextMenu = (e, screen) => { const openContextMenu = (e: MouseEvent, screen: Screen) => {
e.preventDefault() e.preventDefault()
e.stopPropagation() e.stopPropagation()
@ -96,7 +97,7 @@
}, },
] ]
contextMenuStore.open(screen._id, items, { x: e.clientX, y: e.clientY }) contextMenuStore.open(screen._id!, items, { x: e.clientX, y: e.clientY })
} }
</script> </script>

View File

@ -1,16 +1,17 @@
<script> <script lang="ts">
import { Layout } from "@budibase/bbui" import { Layout } from "@budibase/bbui"
import { sortedScreens } from "@/stores/builder" import { sortedScreens } from "@/stores/builder"
import ScreenNavItem from "./ScreenNavItem.svelte" import ScreenNavItem from "./ScreenNavItem.svelte"
import { goto } from "@roxi/routify" import { goto } from "@roxi/routify"
import { getVerticalResizeActions } from "@/components/common/resizable" import { getVerticalResizeActions } from "@/components/common/resizable"
import NavHeader from "@/components/common/NavHeader.svelte" import NavHeader from "@/components/common/NavHeader.svelte"
import type { Screen } from "@budibase/types"
const [resizable, resizableHandle] = getVerticalResizeActions() const [resizable, resizableHandle] = getVerticalResizeActions()
let searching = false let searching = false
let searchValue = "" let searchValue = ""
let screensContainer let screensContainer: HTMLDivElement
let scrolling = false let scrolling = false
$: filteredScreens = getFilteredScreens($sortedScreens, searchValue) $: filteredScreens = getFilteredScreens($sortedScreens, searchValue)
@ -25,13 +26,13 @@
} }
} }
const getFilteredScreens = (screens, searchValue) => { const getFilteredScreens = (screens: Screen[], searchValue: string) => {
return screens.filter(screen => { return screens.filter(screen => {
return !searchValue || screen.routing.route.includes(searchValue) return !searchValue || screen.routing.route.includes(searchValue)
}) })
} }
const handleScroll = e => { const handleScroll = (e: any) => {
scrolling = e.target.scrollTop !== 0 scrolling = e.target.scrollTop !== 0
} }
</script> </script>
@ -62,7 +63,6 @@
<div <div
role="separator" role="separator"
disabled={searching}
class="divider" class="divider"
class:disabled={searching} class:disabled={searching}
use:resizableHandle use:resizableHandle

View File

@ -6,9 +6,12 @@ interface Position {
} }
interface MenuItem { interface MenuItem {
label: string
icon?: string icon?: string
action: () => void name: string
keyBind: string | null
visible: boolean
disabled: boolean
callback: () => void
} }
interface ContextMenuState { interface ContextMenuState {

View File

@ -13,6 +13,7 @@ import {
UnsavedUser, UnsavedUser,
} from "@budibase/types" } from "@budibase/types"
import { BudiStore } from "../BudiStore" import { BudiStore } from "../BudiStore"
import { notifications } from "@budibase/bbui"
interface UserInfo { interface UserInfo {
email: string email: string
@ -43,6 +44,7 @@ class UserStore extends BudiStore<UserState> {
try { try {
return await API.getUser(userId) return await API.getUser(userId)
} catch (err) { } catch (err) {
notifications.error("Error fetching user")
return null return null
} }
} }

View File

@ -22,6 +22,7 @@
environmentStore, environmentStore,
sidePanelStore, sidePanelStore,
modalStore, modalStore,
dataSourceStore,
} from "@/stores" } from "@/stores"
import NotificationDisplay from "./overlay/NotificationDisplay.svelte" import NotificationDisplay from "./overlay/NotificationDisplay.svelte"
import ConfirmationDisplay from "./overlay/ConfirmationDisplay.svelte" import ConfirmationDisplay from "./overlay/ConfirmationDisplay.svelte"
@ -47,11 +48,18 @@
import SnippetsProvider from "./context/SnippetsProvider.svelte" import SnippetsProvider from "./context/SnippetsProvider.svelte"
import EmbedProvider from "./context/EmbedProvider.svelte" import EmbedProvider from "./context/EmbedProvider.svelte"
import DNDSelectionIndicators from "./preview/DNDSelectionIndicators.svelte" import DNDSelectionIndicators from "./preview/DNDSelectionIndicators.svelte"
import { ActionTypes } from "@/constants"
// Provide contexts // Provide contexts
const context = createContextStore()
setContext("sdk", SDK) setContext("sdk", SDK)
setContext("component", writable({ id: null, ancestors: [] })) setContext("component", writable({ id: null, ancestors: [] }))
setContext("context", createContextStore()) setContext("context", context)
// Seed context with an action to refresh all datasources
context.actions.provideAction("all", ActionTypes.RefreshDatasource, () => {
dataSourceStore.actions.refreshAll()
})
let dataLoaded = false let dataLoaded = false
let permissionError = false let permissionError = false

View File

@ -42,13 +42,15 @@
const goToPortal = () => { const goToPortal = () => {
window.location.href = isBuilder ? "/builder/portal/apps" : "/builder/apps" window.location.href = isBuilder ? "/builder/portal/apps" : "/builder/apps"
} }
$: user = $authStore as User
</script> </script>
{#if $authStore} {#if $authStore}
<ActionMenu align={compact ? "right" : "left"}> <ActionMenu align={compact ? "right" : "left"}>
<svelte:fragment slot="control"> <svelte:fragment slot="control">
<div class="container"> <div class="container">
<UserAvatar user={$authStore} size="M" showTooltip={false} /> <UserAvatar {user} size="M" showTooltip={false} />
{#if !compact} {#if !compact}
<div class="text"> <div class="text">
<div class="name"> <div class="name">

View File

@ -115,9 +115,18 @@ export const createDataSourceStore = () => {
}) })
} }
const refreshAll = () => {
get(store).forEach(instance => instance.refresh())
}
return { return {
subscribe: store.subscribe, subscribe: store.subscribe,
actions: { registerDataSource, unregisterInstance, invalidateDataSource }, actions: {
registerDataSource,
unregisterInstance,
invalidateDataSource,
refreshAll,
},
} }
} }

View File

@ -1,16 +1,17 @@
<script> <script lang="ts">
import { Avatar, AbsTooltip, TooltipPosition } from "@budibase/bbui" import { Avatar, AbsTooltip, TooltipPosition } from "@budibase/bbui"
import { helpers } from "@budibase/shared-core" import { helpers } from "@budibase/shared-core"
import type { User } from "@budibase/types"
export let user export let user: User
export let size = "S" export let size: "XS" | "S" | "M" = "S"
export let tooltipPosition = TooltipPosition.Top export let tooltipPosition: TooltipPosition = TooltipPosition.Top
export let showTooltip = true export let showTooltip: boolean = true
</script> </script>
{#if user} {#if user}
<AbsTooltip <AbsTooltip
text={showTooltip ? helpers.getUserLabel(user) : null} text={showTooltip ? helpers.getUserLabel(user) : ""}
position={tooltipPosition} position={tooltipPosition}
color={helpers.getUserColor(user)} color={helpers.getUserColor(user)}
> >

View File

@ -1,27 +1,31 @@
<script> <script lang="ts">
import { UserAvatar } from "@budibase/frontend-core" import { UserAvatar } from "@budibase/frontend-core"
import { TooltipPosition, Avatar } from "@budibase/bbui" import { TooltipPosition, Avatar } from "@budibase/bbui"
import type { UIUser } from "@budibase/types"
export let users = [] type OrderType = "ltr" | "rtl"
export let order = "ltr"
export let size = "S"
export let tooltipPosition = TooltipPosition.Top
$: uniqueUsers = unique(users, order) export let users: UIUser[] = []
export let order: OrderType = "ltr"
export let size: "XS" | "S" = "S"
export let tooltipPosition: TooltipPosition = TooltipPosition.Top
$: uniqueUsers = unique(users)
$: avatars = getAvatars(uniqueUsers, order) $: avatars = getAvatars(uniqueUsers, order)
const unique = users => { const unique = (users: UIUser[]) => {
let uniqueUsers = {} const uniqueUsers: Record<string, UIUser> = {}
users?.forEach(user => { users.forEach(user => {
uniqueUsers[user.email] = user uniqueUsers[user.email] = user
}) })
return Object.values(uniqueUsers) return Object.values(uniqueUsers)
} }
const getAvatars = (users, order) => { type Overflow = { _id: "overflow"; label: string }
const avatars = users.slice(0, 3) const getAvatars = (users: UIUser[], order: OrderType) => {
const avatars: (Overflow | UIUser)[] = users.slice(0, 3)
if (users.length > 3) { if (users.length > 3) {
const overflow = { const overflow: Overflow = {
_id: "overflow", _id: "overflow",
label: `+${users.length - 3}`, label: `+${users.length - 3}`,
} }
@ -31,17 +35,22 @@
avatars.unshift(overflow) avatars.unshift(overflow)
} }
} }
return avatars.map((user, idx) => ({ return avatars.map((user, idx) => ({
...user, ...user,
zIndex: order === "ltr" ? idx : uniqueUsers.length - idx, zIndex: order === "ltr" ? idx : uniqueUsers.length - idx,
})) }))
} }
function isUser(value: Overflow | UIUser): value is UIUser {
return value._id !== "overflow"
}
</script> </script>
<div class="avatars"> <div class="avatars">
{#each avatars as user} {#each avatars as user}
<span style="z-index:{user.zIndex};"> <span style="z-index:{user.zIndex};">
{#if user._id === "overflow"} {#if !isUser(user)}
<Avatar <Avatar
{size} {size}
initials={user.label} initials={user.label}