Merge branch 'master' into feature/filter-component
This commit is contained in:
commit
380562a1dd
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
export interface ScrollContext {
|
||||||
|
scrollTo: (bounds: DOMRect) => void
|
||||||
|
}
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)}
|
||||||
>
|
>
|
||||||
|
|
|
@ -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}
|
||||||
|
|
Loading…
Reference in New Issue