Merge pull request #11176 from Budibase/cheeks-fixes

Grid + collab + tooltip improvements + preview in new tab
This commit is contained in:
Andrew Kingston 2023-07-17 10:17:31 +01:00 committed by GitHub
commit 0ca1850e4a
29 changed files with 484 additions and 286 deletions

View File

@ -1,6 +1,7 @@
<script>
import "@spectrum-css/button/dist/index-vars.css"
import Tooltip from "../Tooltip/Tooltip.svelte"
import AbsTooltip from "../Tooltip/AbsTooltip.svelte"
import { createEventDispatcher } from "svelte"
export let type
export let disabled = false
@ -16,48 +17,53 @@
export let tooltip = undefined
export let newStyles = true
export let id
const dispatch = createEventDispatcher()
</script>
<button
{id}
{type}
class:spectrum-Button--cta={cta}
class:spectrum-Button--primary={primary}
class:spectrum-Button--secondary={secondary}
class:spectrum-Button--warning={warning}
class:spectrum-Button--overBackground={overBackground}
class:spectrum-Button--quiet={quiet}
class:new-styles={newStyles}
class:active
class:disabled
class="spectrum-Button spectrum-Button--size{size.toUpperCase()}"
{disabled}
on:click|preventDefault
>
{#if icon}
<svg
class="spectrum-Icon spectrum-Icon--size{size.toUpperCase()}"
focusable="false"
aria-hidden="true"
aria-label={icon}
>
<use xlink:href="#spectrum-icon-18-{icon}" />
</svg>
{/if}
{#if $$slots}
<span class="spectrum-Button-label"><slot /></span>
{/if}
{#if tooltip}
<div class="tooltip">
<Tooltip textWrapping={true} direction={"bottom"} text={tooltip} />
</div>
{/if}
</button>
<AbsTooltip text={tooltip}>
<button
{id}
{type}
class:spectrum-Button--cta={cta}
class:spectrum-Button--primary={primary}
class:spectrum-Button--secondary={secondary}
class:spectrum-Button--warning={warning}
class:spectrum-Button--overBackground={overBackground}
class:spectrum-Button--quiet={quiet}
class:new-styles={newStyles}
class:active
class:is-disabled={disabled}
class="spectrum-Button spectrum-Button--size{size.toUpperCase()}"
on:click|preventDefault={() => {
if (!disabled) {
dispatch("click")
}
}}
>
{#if icon}
<svg
class="spectrum-Icon spectrum-Icon--size{size.toUpperCase()}"
focusable="false"
aria-hidden="true"
aria-label={icon}
>
<use xlink:href="#spectrum-icon-18-{icon}" />
</svg>
{/if}
{#if $$slots}
<span class="spectrum-Button-label"><slot /></span>
{/if}
</button>
</AbsTooltip>
<style>
button {
position: relative;
}
button.is-disabled {
cursor: default;
}
.spectrum-Button-label {
white-space: nowrap;
overflow: hidden;
@ -66,23 +72,6 @@
.active {
color: var(--spectrum-global-color-blue-600) !important;
}
.tooltip {
position: absolute;
display: flex;
justify-content: center;
z-index: 100;
width: 160px;
text-align: center;
transform: translateX(-50%);
left: 50%;
top: 100%;
opacity: 0;
transition: opacity 130ms ease-out;
pointer-events: none;
}
button:hover .tooltip {
opacity: 1;
}
.spectrum-Button--primary.new-styles {
background: var(--spectrum-global-color-gray-800);
border-color: transparent;
@ -96,10 +85,10 @@
border-color: transparent;
color: var(--spectrum-global-color-gray-900);
}
.spectrum-Button--secondary.new-styles:not(.disabled):hover {
.spectrum-Button--secondary.new-styles:not(.is-disabled):hover {
background: var(--spectrum-global-color-gray-300);
}
.spectrum-Button--secondary.new-styles.disabled {
.spectrum-Button--secondary.new-styles.is-disabled {
color: var(--spectrum-global-color-gray-500);
}
</style>

View File

@ -90,6 +90,6 @@
.spectrum-Popover {
min-width: var(--spectrum-global-dimension-size-2000);
border-color: var(--spectrum-global-color-gray-300);
overflow: visible;
overflow: auto;
}
</style>

View File

@ -0,0 +1,157 @@
<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>
import Portal from "svelte-portal"
import { fade } from "svelte/transition"
import "@spectrum-css/tooltip/dist/index-vars.css"
import { onDestroy } from "svelte"
export let position = TooltipPosition.Top
export let type = TooltipType.Default
export let text = ""
export let fixed = false
export let color = null
let wrapper
let hovered = false
let left
let top
let visible = false
let timeout
let interval
$: {
if (hovered || fixed) {
// Debounce showing by 200ms to avoid flashing tooltip
timeout = setTimeout(show, 200)
} else {
hide()
}
}
$: tooltipStyle = color ? `background:${color};` : null
$: tipStyle = color ? `border-top-color:${color};` : null
// Computes the position of the tooltip
const updateTooltipPosition = () => {
const node = wrapper?.children?.[0]
if (!node) {
left = null
top = null
return
}
const bounds = node.getBoundingClientRect()
// Determine where to render tooltip based on position prop
if (position === TooltipPosition.Top) {
left = bounds.left + bounds.width / 2
top = bounds.top
} else if (position === TooltipPosition.Right) {
left = bounds.left + bounds.width
top = bounds.top + bounds.height / 2
} else if (position === TooltipPosition.Bottom) {
left = bounds.left + bounds.width / 2
top = bounds.top + bounds.height
} else if (position === TooltipPosition.Left) {
left = bounds.left
top = bounds.top + bounds.height / 2
}
}
// Computes the position of the tooltip then shows it.
// We set up a poll to frequently update the position of the tooltip in case
// the target moves.
const show = () => {
updateTooltipPosition()
interval = setInterval(updateTooltipPosition, 100)
visible = true
}
// Hides the tooltip
const hide = () => {
clearTimeout(timeout)
clearInterval(interval)
visible = false
}
// Ensure we clean up interval and timeout
onDestroy(hide)
</script>
<div
bind:this={wrapper}
class="abs-tooltip"
on:focus={null}
on:mouseover={() => (hovered = true)}
on:mouseleave={() => (hovered = false)}
>
<slot />
</div>
{#if visible && text && left != null && top != null}
<Portal target=".spectrum">
<span
class="spectrum-Tooltip spectrum-Tooltip--{type} spectrum-Tooltip--{position} is-open"
style={`left:${left}px;top:${top}px;${tooltipStyle}`}
transition:fade|local={{ duration: 130 }}
>
<span class="spectrum-Tooltip-label">{text}</span>
<span class="spectrum-Tooltip-tip" style={tipStyle} />
</span>
</Portal>
{/if}
<style>
.abs-tooltip {
display: contents;
}
.spectrum-Tooltip {
position: absolute;
z-index: 9999;
pointer-events: none;
margin: 0;
max-width: 280px;
transition: top 130ms ease-out, left 130ms ease-out;
}
.spectrum-Tooltip-label {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
font-size: 12px;
font-weight: 600;
}
/* Colour overrides for default type */
.spectrum-Tooltip--default {
background: var(--spectrum-global-color-gray-500);
}
.spectrum-Tooltip--default .spectrum-Tooltip-tip {
border-top-color: var(--spectrum-global-color-gray-500);
}
/* Position styles */
.spectrum-Tooltip--top {
transform: translateX(-50%) translateY(calc(-100% - 8px));
}
.spectrum-Tooltip--right {
transform: translateX(8px) translateY(-50%);
}
.spectrum-Tooltip--bottom {
transform: translateX(-50%) translateY(8px);
}
.spectrum-Tooltip--left {
transform: translateX(calc(-100% - 8px)) translateY(-50%);
}
</style>

View File

@ -0,0 +1,39 @@
<script>
import AbsTooltip from "./AbsTooltip.svelte"
import { onDestroy } from "svelte"
export let text = null
export let condition = true
export let duration = 3000
export let position
export let type
let visible = false
let timeout
$: {
if (condition) {
showTooltip()
} else {
hideTooltip()
}
}
const showTooltip = () => {
visible = true
timeout = setTimeout(() => {
visible = false
}, duration)
}
const hideTooltip = () => {
visible = false
clearTimeout(timeout)
}
onDestroy(hideTooltip)
</script>
<AbsTooltip {position} {type} text={visible ? text : null} fixed={visible}>
<slot />
</AbsTooltip>

View File

@ -36,6 +36,12 @@ export { default as Layout } from "./Layout/Layout.svelte"
export { default as Page } from "./Layout/Page.svelte"
export { default as Link } from "./Link/Link.svelte"
export { default as Tooltip } from "./Tooltip/Tooltip.svelte"
export { default as TempTooltip } from "./Tooltip/TempTooltip.svelte"
export {
default as AbsTooltip,
TooltipPosition,
TooltipType,
} from "./Tooltip/AbsTooltip.svelte"
export { default as TooltipWrapper } from "./Tooltip/TooltipWrapper.svelte"
export { default as Menu } from "./Menu/Menu.svelte"
export { default as MenuSection } from "./Menu/Section.svelte"

View File

@ -127,8 +127,12 @@ export const selectedAutomation = derived(automationStore, $automationStore => {
export const userSelectedResourceMap = derived(userStore, $userStore => {
let map = {}
$userStore.forEach(user => {
if (user.builderMetadata?.selectedResourceId) {
map[user.builderMetadata?.selectedResourceId] = user
const resource = user.builderMetadata?.selectedResourceId
if (resource) {
if (!map[resource]) {
map[resource] = []
}
map[resource].push(user)
}
})
return map

View File

@ -2,6 +2,7 @@
import { Icon } from "@budibase/bbui"
import { createEventDispatcher, getContext } from "svelte"
import { helpers } from "@budibase/shared-core"
import UserAvatars from "../../pages/builder/app/[application]/_components/UserAvatars.svelte"
export let icon
export let withArrow = false
@ -98,21 +99,25 @@
<Icon color={iconColor} size="S" name={icon} />
</div>
{/if}
<div class="text" title={showTooltip ? text : null}>{text}</div>
<div class="text" title={showTooltip ? text : null}>
{text}
{#if selectedBy}
<UserAvatars size="XS" users={selectedBy} />
{/if}
</div>
{#if withActions}
<div class="actions">
<slot />
</div>
{/if}
{#if $$slots.right}
<div class="right">
<slot name="right" />
</div>
{/if}
</div>
{#if selectedBy}
<div class="selected-by-label">{helpers.getUserLabel(selectedBy)}</div>
{/if}
</div>
<style>
@ -136,13 +141,16 @@
}
.nav-item.highlighted {
background-color: var(--spectrum-global-color-gray-200);
--avatars-background: var(--spectrum-global-color-gray-200);
}
.nav-item.selected {
background-color: var(--spectrum-global-color-gray-300);
--avatars-background: var(--spectrum-global-color-gray-300);
color: var(--ink);
}
.nav-item:hover {
background-color: var(--spectrum-global-color-gray-300);
--avatars-background: var(--spectrum-global-color-gray-300);
}
.nav-item:hover .actions {
visibility: visible;
@ -159,37 +167,6 @@
padding-left: var(--spacing-l);
}
/* Selected user styles */
.nav-item.selectedBy:after {
content: "";
position: absolute;
width: calc(100% - 4px);
height: 28px;
border: 2px solid var(--selected-by-color);
left: 0;
top: 0;
border-radius: 2px;
pointer-events: none;
}
.selected-by-label {
position: absolute;
top: 0;
right: 0;
background: var(--selected-by-color);
padding: 2px 4px;
font-size: 12px;
color: white;
transform: translateY(calc(1px - 100%));
border-top-right-radius: 2px;
border-top-left-radius: 2px;
pointer-events: none;
opacity: 0;
transition: opacity 130ms ease-out;
}
.nav-item.selectedBy:hover .selected-by-label {
opacity: 1;
}
/* Needed to fully display the actions icon */
.nav-item.scrollable .nav-item-content {
padding-right: 1px;
@ -245,6 +222,9 @@
color: var(--spectrum-global-color-gray-900);
order: 2;
width: 0;
display: flex;
align-items: center;
gap: 8px;
}
.scrollable .text {
flex: 0 0 auto;

View File

@ -10,6 +10,7 @@
Link,
Modal,
StatusLight,
AbsTooltip,
} from "@budibase/bbui"
import RevertModal from "components/deploy/RevertModal.svelte"
import VersionModal from "components/deploy/VersionModal.svelte"
@ -250,15 +251,20 @@
<Link quiet on:click={unpublishApp}>Unpublish</Link>
</span>
<span class="revert-link">
<Link
disabled={!$isOnlyUser}
quiet
secondary
on:click={revertApp}
tooltip="Unavailable - another user is editing this app"
<AbsTooltip
text={$isOnlyUser
? null
: "Unavailable - another user is editing this app"}
>
Revert
</Link>
<Link
disabled={!$isOnlyUser}
quiet
secondary
on:click={revertApp}
>
Revert
</Link>
</AbsTooltip>
</span>
{:else}
<span class="status-text unpublished">Not published</span>

View File

@ -1,11 +1,8 @@
<script>
import { Tooltip } from "@budibase/bbui"
export let text
export let url
export let active = false
export let disabled = false
export let tooltip = null
</script>
<div class="side-nav-item">
@ -18,11 +15,6 @@
{text || ""}
</div>
{/if}
{#if tooltip}
<div class="tooltip">
<Tooltip textWrapping direction="right" text={tooltip} />
</div>
{/if}
</div>
<style>
@ -45,17 +37,4 @@
pointer-events: none;
color: var(--spectrum-global-color-gray-500) !important;
}
.tooltip {
position: absolute;
transform: translateY(-50%);
left: 100%;
top: 50%;
opacity: 0;
pointer-events: none;
transition: opacity 130ms ease-out;
z-index: 100;
}
.side-nav-item:hover .tooltip {
opacity: 1;
}
</style>

View File

@ -15,6 +15,7 @@
}
onMount(() => {
window.isBuilder = true
window.closePreview = () => {
store.update(state => ({
...state,

View File

@ -1,9 +1,14 @@
<script>
import { UserAvatar } from "@budibase/frontend-core"
import { TooltipPosition, Avatar } from "@budibase/bbui"
export let users = []
export let order = "ltr"
export let size = "S"
export let tooltipPosition = TooltipPosition.Top
$: uniqueUsers = unique(users)
$: uniqueUsers = unique(users, order)
$: avatars = getAvatars(uniqueUsers, order)
const unique = users => {
let uniqueUsers = {}
@ -12,17 +17,51 @@
})
return Object.values(uniqueUsers)
}
const getAvatars = (users, order) => {
const avatars = users.slice(0, 3)
if (users.length > 3) {
const overflow = {
_id: "overflow",
label: `+${users.length - 3}`,
}
if (order === "ltr") {
avatars.push(overflow)
} else {
avatars.unshift(overflow)
}
}
return avatars.map((user, idx) => ({
...user,
zIndex: order === "ltr" ? idx : uniqueUsers.length - idx,
}))
}
</script>
<div class="avatars">
{#each uniqueUsers as user}
<UserAvatar {user} tooltipDirection="bottom" />
{#each avatars as user}
<span style="z-index:{user.zIndex};">
{#if user._id === "overflow"}
<Avatar
{size}
initials={user.label}
color="var(--spectrum-global-color-gray-500)"
/>
{:else}
<UserAvatar {size} {user} {tooltipPosition} />
{/if}
</span>
{/each}
</div>
<style>
.avatars {
display: flex;
gap: 4px;
}
span:not(:first-of-type) {
margin-left: -6px;
}
.avatars :global(.spectrum-Avatar) {
border: 2px solid var(--avatars-background, var(--background));
}
</style>

View File

@ -15,6 +15,7 @@
Heading,
Modal,
notifications,
TooltipPosition,
} from "@budibase/bbui"
import AppActions from "components/deploy/AppActions.svelte"
import { API } from "api"
@ -172,7 +173,11 @@
</div>
<div class="toprightnav">
<span>
<UserAvatars users={$userStore} />
<UserAvatars
users={$userStore}
order="rtl"
tooltipPosition={TooltipPosition.Bottom}
/>
</span>
<AppActions {application} {loaded} />
</div>

View File

@ -7,7 +7,7 @@
} from "stores/backend"
import { hasData } from "stores/selectors"
import { notifications, Body } from "@budibase/bbui"
import { notifications, Body, Icon, AbsTooltip } from "@budibase/bbui"
import { params, goto } from "@roxi/routify"
import CreateExternalDatasourceModal from "./_components/CreateExternalDatasourceModal/index.svelte"
import CreateInternalTableModal from "./_components/CreateInternalTableModal.svelte"
@ -15,7 +15,6 @@
import IntegrationIcon from "components/backend/DatasourceNavigator/IntegrationIcon.svelte"
import CreationPage from "components/common/CreationPage.svelte"
import ICONS from "components/backend/DatasourceNavigator/icons/index.js"
import FontAwesomeIcon from "components/common/FontAwesomeIcon.svelte"
let internalTableModal
let externalDatasourceModal
@ -54,13 +53,9 @@
>
<div class="subHeading">
<Body>Get started with our Budibase DB</Body>
<div
role="tooltip"
title="Budibase DB is built with CouchDB"
class="tooltip"
>
<FontAwesomeIcon name="fa-solid fa-circle-info" />
</div>
<AbsTooltip text="Budibase DB is built with CouchDB">
<Icon name="Info" size="S" />
</AbsTooltip>
</div>
<div class="options">
@ -116,13 +111,12 @@
display: flex;
align-items: center;
margin-top: 12px;
margin-bottom: 24px;
margin-bottom: 36px;
gap: 8px;
}
.tooltip {
margin-left: 6px;
.subHeading :global(p) {
color: var(--spectrum-global-color-gray-600) !important;
}
.options {
width: 100%;
display: grid;

View File

@ -19,7 +19,7 @@
heading={hasScreens ? "Create new screen" : "Create your first screen"}
>
<div class="subHeading">
<Body size="L">Start from scratch or create screens from your data</Body>
<Body>Start from scratch or create screens from your data</Body>
</div>
<div class="cards">
@ -56,18 +56,18 @@
.subHeading :global(p) {
text-align: center;
margin-top: 12px;
margin-bottom: 24px;
color: var(--grey-6);
margin-bottom: 36px;
color: var(--spectrum-global-color-gray-600);
}
.cards {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 24px;
}
.card {
margin: 12px;
max-width: 235px;
transition: filter 150ms;
}

View File

@ -1,6 +1,6 @@
<script>
import { Content, SideNav, SideNavItem } from "components/portal/page"
import { Page, Layout } from "@budibase/bbui"
import { Page, Layout, AbsTooltip, TooltipPosition } from "@budibase/bbui"
import { url, isActive } from "@roxi/routify"
import DeleteModal from "components/deploy/DeleteModal.svelte"
import { isOnlyUser } from "builderStore"
@ -45,16 +45,20 @@
active={$isActive("./version")}
/>
<div class="delete-action">
<SideNavItem
text="Delete app"
on:click={() => {
deleteModal.show()
}}
disabled={!$isOnlyUser}
tooltip={$isOnlyUser
<AbsTooltip
position={TooltipPosition.Bottom}
text={$isOnlyUser
? null
: "Unavailable - another user is editing this app"}
/>
>
<SideNavItem
text="Delete app"
disabled={!$isOnlyUser}
on:click={() => {
deleteModal.show()
}}
/>
</AbsTooltip>
</div>
</SideNav>
<slot />

View File

@ -6,6 +6,8 @@
Heading,
Body,
Modal,
AbsTooltip,
TooltipPosition,
} from "@budibase/bbui"
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
import CreateRestoreModal from "./CreateRestoreModal.svelte"
@ -46,16 +48,18 @@
</div>
{#if row.type !== "restore"}
<MenuItem
on:click={restoreDialog.show}
icon="Revert"
disabled={!$isOnlyUser}
tooltip={$isOnlyUser
? null
: "Unavailable - another user is editing this app"}
<AbsTooltip
position={TooltipPosition.Left}
text="Unavailable - another user is editing this app"
>
Restore
</MenuItem>
<MenuItem
on:click={restoreDialog.show}
icon="Revert"
disabled={!$isOnlyUser}
>
Restore
</MenuItem>
</AbsTooltip>
<MenuItem on:click={deleteDialog.show} icon="Delete">Delete</MenuItem>
<MenuItem on:click={downloadExport} icon="Download">Download</MenuItem>
{/if}

View File

@ -1,6 +1,6 @@
<script>
import { Heading, Select, ActionButton } from "@budibase/bbui"
import { devToolsStore } from "../../stores"
import { devToolsStore, appStore } from "../../stores"
import { getContext } from "svelte"
const context = getContext("context")
@ -45,27 +45,41 @@
icon="Code"
on:click={() => devToolsStore.actions.setVisible(!$devToolsStore.visible)}
>
{$devToolsStore.visible ? "Close" : "Open"} DevTools
DevTools
</ActionButton>
{/if}
{#if window.parent.isBuilder}
<ActionButton
quiet
icon="LinkOut"
on:click={() => {
window.parent.closePreview?.()
window.open(`/${$appStore.appId}`, "_blank")
}}
>
Fullscreen
</ActionButton>
<ActionButton
quiet
icon="Close"
on:click={() => window.parent.closePreview?.()}
>
Close
</ActionButton>
{/if}
<ActionButton
quiet
icon="Close"
on:click={() => window.parent.closePreview?.()}
>
Close preview
</ActionButton>
</div>
<style>
.dev-preview-header {
flex: 0 0 60px;
display: grid;
align-items: center;
background-color: black;
padding: 0 var(--spacing-xl);
grid-template-columns: 1fr auto auto auto;
grid-gap: var(--spacing-xl);
display: flex;
align-items: center;
gap: var(--spacing-xl);
}
.dev-preview-header :global(.spectrum-Heading) {
flex: 1 1 auto;
}
.dev-preview-header.mobile {
grid-template-columns: 1fr auto auto;

View File

@ -1,60 +1,23 @@
<script>
import { Avatar, Tooltip } from "@budibase/bbui"
import { Avatar, AbsTooltip, TooltipPosition } from "@budibase/bbui"
import { helpers } from "@budibase/shared-core"
export let user
export let size
export let tooltipDirection = "top"
export let size = "S"
export let tooltipPosition = TooltipPosition.Top
export let showTooltip = true
$: tooltipStyle = getTooltipStyle(tooltipDirection)
const getTooltipStyle = direction => {
if (!direction) {
return ""
}
if (direction === "top") {
return "transform: translateX(-50%) translateY(-100%);"
} else if (direction === "bottom") {
return "transform: translateX(-50%) translateY(100%);"
}
}
</script>
{#if user}
<div class="user-avatar">
<AbsTooltip
text={showTooltip ? helpers.getUserLabel(user) : null}
position={tooltipPosition}
color={helpers.getUserColor(user)}
>
<Avatar
{size}
initials={helpers.getUserInitials(user)}
color={helpers.getUserColor(user)}
/>
{#if showTooltip}
<div class="tooltip" style={tooltipStyle}>
<Tooltip
direction={tooltipDirection}
textWrapping
text={helpers.getUserLabel(user)}
size="S"
/>
</div>
{/if}
</div>
</AbsTooltip>
{/if}
<style>
.user-avatar {
position: relative;
}
.tooltip {
position: absolute;
top: 0;
left: 50%;
white-space: nowrap;
pointer-events: none;
opacity: 0;
transition: opacity 130ms ease-out;
}
.user-avatar:hover .tooltip {
opacity: 1;
}
</style>

View File

@ -1,16 +0,0 @@
<script>
import { ActionButton } from "@budibase/bbui"
import { getContext } from "svelte"
const { config, dispatch } = getContext("grid")
</script>
<ActionButton
icon="TableColumnAddRight"
quiet
size="M"
on:click={() => dispatch("add-column")}
disabled={!$config.allowSchemaChanges}
>
Add column
</ActionButton>

View File

@ -1,18 +0,0 @@
<script>
import { ActionButton } from "@budibase/bbui"
import { getContext } from "svelte"
const { dispatch, columns, stickyColumn, config, loaded } = getContext("grid")
</script>
<ActionButton
icon="TableRowAddBottom"
quiet
size="M"
on:click={() => dispatch("add-row-inline")}
disabled={!loaded ||
!$config.allowAddRows ||
(!$columns.length && !$stickyColumn)}
>
Add row
</ActionButton>

View File

@ -71,6 +71,7 @@
contentLines,
gridFocused,
error,
canAddRows,
} = context
// Keep config store up to date with props
@ -143,7 +144,7 @@
<HeaderRow />
<GridBody />
</div>
{#if allowAddRows}
{#if $canAddRows}
<NewRow />
{/if}
<div class="overlays">

View File

@ -9,7 +9,7 @@
renderedRows,
renderedColumns,
rowVerticalInversionIndex,
config,
canAddRows,
hoveredRowId,
dispatch,
isDragging,
@ -43,7 +43,7 @@
invertY={idx >= $rowVerticalInversionIndex}
/>
{/each}
{#if $config.allowAddRows && $renderedColumns.length}
{#if $canAddRows}
<div
class="blank"
class:highlighted={$hoveredRowId === BlankRowID}

View File

@ -2,7 +2,7 @@
import { getContext } from "svelte"
import GridScrollWrapper from "./GridScrollWrapper.svelte"
import HeaderCell from "../cells/HeaderCell.svelte"
import { Icon } from "@budibase/bbui"
import { Icon, TempTooltip, TooltipType } from "@budibase/bbui"
const {
renderedColumns,
@ -11,10 +11,13 @@
hiddenColumnsWidth,
width,
config,
hasNonAutoColumn,
tableId,
loading,
} = getContext("grid")
$: columnsWidth = $renderedColumns.reduce(
(total, col) => (total += col.width),
(total, col) => total + col.width,
0
)
$: end = $hiddenColumnsWidth + columnsWidth - 1 - $scroll.left
@ -30,13 +33,21 @@
</div>
</GridScrollWrapper>
{#if $config.allowSchemaChanges}
<div
class="add"
style="left:{left}px"
on:click={() => dispatch("add-column")}
>
<Icon name="Add" />
</div>
{#key $tableId}
<TempTooltip
text="Click here to create your first column"
type={TooltipType.Info}
condition={!$hasNonAutoColumn && !$loading}
>
<div
class="add"
style="left:{left}px;"
on:click={() => dispatch("add-column")}
>
<Icon name="Add" />
</div>
</TempTooltip>
{/key}
{/if}
</div>

View File

@ -1,6 +1,6 @@
<script>
import { getContext, onDestroy, onMount, tick } from "svelte"
import { Icon, Button } from "@budibase/bbui"
import { Icon, Button, TempTooltip, TooltipType } from "@budibase/bbui"
import GridScrollWrapper from "./GridScrollWrapper.svelte"
import DataCell from "../cells/DataCell.svelte"
import { fade } from "svelte/transition"
@ -27,7 +27,8 @@
rowVerticalInversionIndex,
columnHorizontalInversionIndex,
selectedRows,
config,
loading,
canAddRows,
} = getContext("grid")
let visible = false
@ -40,6 +41,7 @@
$: $tableId, (visible = false)
$: invertY = shouldInvertY(offset, $rowVerticalInversionIndex, $renderedRows)
$: selectedRowCount = Object.values($selectedRows).length
$: hasNoRows = !$rows.length
const shouldInvertY = (offset, inversionIndex, rows) => {
if (offset === 0) {
@ -147,16 +149,22 @@
</script>
<!-- New row FAB -->
{#if !visible && !selectedRowCount && $config.allowAddRows && firstColumn}
<div
class="new-row-fab"
on:click={() => dispatch("add-row-inline")}
transition:fade|local={{ duration: 130 }}
class:offset={!$stickyColumn}
>
<Icon name="Add" size="S" />
</div>
{/if}
<TempTooltip
text="Click here to create your first row"
condition={hasNoRows && !$loading}
type={TooltipType.Info}
>
{#if !visible && !selectedRowCount && $canAddRows}
<div
class="new-row-fab"
on:click={() => dispatch("add-row-inline")}
transition:fade|local={{ duration: 130 }}
class:offset={!$stickyColumn}
>
<Icon name="Add" size="S" />
</div>
{/if}
</TempTooltip>
<!-- Only show new row functionality if we have any columns -->
{#if visible}

View File

@ -13,11 +13,10 @@
rows,
selectedRows,
stickyColumn,
renderedColumns,
renderedRows,
focusedCellId,
hoveredRowId,
config,
canAddRows,
selectedCellMap,
focusedRow,
scrollLeft,
@ -93,7 +92,7 @@
{/if}
</div>
{/each}
{#if $config.allowAddRows && ($renderedColumns.length || $stickyColumn)}
{#if $canAddRows}
<div
class="row new"
on:mouseenter={$isDragging

View File

@ -16,6 +16,7 @@
config,
menu,
gridFocused,
canAddRows,
} = getContext("grid")
const ignoredOriginSelectors = [
@ -45,7 +46,7 @@
e.preventDefault()
focusFirstCell()
} else if (e.key === "Enter" && (e.ctrlKey || e.metaKey)) {
if ($config.allowAddRows) {
if ($canAddRows) {
e.preventDefault()
dispatch("add-row-inline")
}
@ -99,7 +100,7 @@
}
break
case "Enter":
if ($config.allowAddRows) {
if ($canAddRows) {
dispatch("add-row-inline")
}
}

View File

@ -17,6 +17,7 @@
focusedCellAPI,
focusedRowId,
notifications,
canAddRows,
} = getContext("grid")
$: style = makeStyle($menu)
@ -93,7 +94,7 @@
</MenuItem>
<MenuItem
icon="Duplicate"
disabled={isNewRow || !$config.allowAddRows}
disabled={isNewRow || !$canAddRows}
on:click={duplicate}
>
Duplicate row

View File

@ -83,6 +83,21 @@ export const deriveStores = context => {
await saveChanges()
}
// Derive if we have any normal columns
const hasNonAutoColumn = derived(
[columns, stickyColumn],
([$columns, $stickyColumn]) => {
let allCols = $columns || []
if ($stickyColumn) {
allCols = [...allCols, $stickyColumn]
}
const normalCols = allCols.filter(column => {
return !column.schema?.autocolumn
})
return normalCols.length > 0
}
)
// Persists column changes by saving metadata against table schema
const saveChanges = async () => {
const $columns = get(columns)
@ -128,6 +143,7 @@ export const deriveStores = context => {
}
return {
hasNonAutoColumn,
columns: {
...columns,
actions: {

View File

@ -70,6 +70,8 @@ export const deriveStores = context => {
rowHeight,
stickyColumn,
width,
hasNonAutoColumn,
config,
} = context
// Derive the row that contains the selected cell
@ -112,7 +114,16 @@ export const deriveStores = context => {
return ($stickyColumn?.width || 0) + $width + GutterWidth < 1100
})
// Derive if we're able to add rows
const canAddRows = derived(
[config, hasNonAutoColumn],
([$config, $hasNonAutoColumn]) => {
return $config.allowAddRows && $hasNonAutoColumn
}
)
return {
canAddRows,
focusedRow,
contentLines,
compact,