Merge pull request #11176 from Budibase/cheeks-fixes
Grid + collab + tooltip improvements + preview in new tab
This commit is contained in:
commit
0ca1850e4a
|
@ -1,6 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
import "@spectrum-css/button/dist/index-vars.css"
|
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 type
|
||||||
export let disabled = false
|
export let disabled = false
|
||||||
|
@ -16,48 +17,53 @@
|
||||||
export let tooltip = undefined
|
export let tooltip = undefined
|
||||||
export let newStyles = true
|
export let newStyles = true
|
||||||
export let id
|
export let id
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<button
|
<AbsTooltip text={tooltip}>
|
||||||
{id}
|
<button
|
||||||
{type}
|
{id}
|
||||||
class:spectrum-Button--cta={cta}
|
{type}
|
||||||
class:spectrum-Button--primary={primary}
|
class:spectrum-Button--cta={cta}
|
||||||
class:spectrum-Button--secondary={secondary}
|
class:spectrum-Button--primary={primary}
|
||||||
class:spectrum-Button--warning={warning}
|
class:spectrum-Button--secondary={secondary}
|
||||||
class:spectrum-Button--overBackground={overBackground}
|
class:spectrum-Button--warning={warning}
|
||||||
class:spectrum-Button--quiet={quiet}
|
class:spectrum-Button--overBackground={overBackground}
|
||||||
class:new-styles={newStyles}
|
class:spectrum-Button--quiet={quiet}
|
||||||
class:active
|
class:new-styles={newStyles}
|
||||||
class:disabled
|
class:active
|
||||||
class="spectrum-Button spectrum-Button--size{size.toUpperCase()}"
|
class:is-disabled={disabled}
|
||||||
{disabled}
|
class="spectrum-Button spectrum-Button--size{size.toUpperCase()}"
|
||||||
on:click|preventDefault
|
on:click|preventDefault={() => {
|
||||||
>
|
if (!disabled) {
|
||||||
{#if icon}
|
dispatch("click")
|
||||||
<svg
|
}
|
||||||
class="spectrum-Icon spectrum-Icon--size{size.toUpperCase()}"
|
}}
|
||||||
focusable="false"
|
>
|
||||||
aria-hidden="true"
|
{#if icon}
|
||||||
aria-label={icon}
|
<svg
|
||||||
>
|
class="spectrum-Icon spectrum-Icon--size{size.toUpperCase()}"
|
||||||
<use xlink:href="#spectrum-icon-18-{icon}" />
|
focusable="false"
|
||||||
</svg>
|
aria-hidden="true"
|
||||||
{/if}
|
aria-label={icon}
|
||||||
{#if $$slots}
|
>
|
||||||
<span class="spectrum-Button-label"><slot /></span>
|
<use xlink:href="#spectrum-icon-18-{icon}" />
|
||||||
{/if}
|
</svg>
|
||||||
{#if tooltip}
|
{/if}
|
||||||
<div class="tooltip">
|
{#if $$slots}
|
||||||
<Tooltip textWrapping={true} direction={"bottom"} text={tooltip} />
|
<span class="spectrum-Button-label"><slot /></span>
|
||||||
</div>
|
{/if}
|
||||||
{/if}
|
</button>
|
||||||
</button>
|
</AbsTooltip>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
button {
|
button {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
button.is-disabled {
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
.spectrum-Button-label {
|
.spectrum-Button-label {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
@ -66,23 +72,6 @@
|
||||||
.active {
|
.active {
|
||||||
color: var(--spectrum-global-color-blue-600) !important;
|
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 {
|
.spectrum-Button--primary.new-styles {
|
||||||
background: var(--spectrum-global-color-gray-800);
|
background: var(--spectrum-global-color-gray-800);
|
||||||
border-color: transparent;
|
border-color: transparent;
|
||||||
|
@ -96,10 +85,10 @@
|
||||||
border-color: transparent;
|
border-color: transparent;
|
||||||
color: var(--spectrum-global-color-gray-900);
|
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);
|
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);
|
color: var(--spectrum-global-color-gray-500);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -90,6 +90,6 @@
|
||||||
.spectrum-Popover {
|
.spectrum-Popover {
|
||||||
min-width: var(--spectrum-global-dimension-size-2000);
|
min-width: var(--spectrum-global-dimension-size-2000);
|
||||||
border-color: var(--spectrum-global-color-gray-300);
|
border-color: var(--spectrum-global-color-gray-300);
|
||||||
overflow: visible;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -36,6 +36,12 @@ export { default as Layout } from "./Layout/Layout.svelte"
|
||||||
export { default as Page } from "./Layout/Page.svelte"
|
export { default as Page } from "./Layout/Page.svelte"
|
||||||
export { default as Link } from "./Link/Link.svelte"
|
export { default as Link } from "./Link/Link.svelte"
|
||||||
export { default as Tooltip } from "./Tooltip/Tooltip.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 TooltipWrapper } from "./Tooltip/TooltipWrapper.svelte"
|
||||||
export { default as Menu } from "./Menu/Menu.svelte"
|
export { default as Menu } from "./Menu/Menu.svelte"
|
||||||
export { default as MenuSection } from "./Menu/Section.svelte"
|
export { default as MenuSection } from "./Menu/Section.svelte"
|
||||||
|
|
|
@ -127,8 +127,12 @@ export const selectedAutomation = derived(automationStore, $automationStore => {
|
||||||
export const userSelectedResourceMap = derived(userStore, $userStore => {
|
export const userSelectedResourceMap = derived(userStore, $userStore => {
|
||||||
let map = {}
|
let map = {}
|
||||||
$userStore.forEach(user => {
|
$userStore.forEach(user => {
|
||||||
if (user.builderMetadata?.selectedResourceId) {
|
const resource = user.builderMetadata?.selectedResourceId
|
||||||
map[user.builderMetadata?.selectedResourceId] = user
|
if (resource) {
|
||||||
|
if (!map[resource]) {
|
||||||
|
map[resource] = []
|
||||||
|
}
|
||||||
|
map[resource].push(user)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return map
|
return map
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
import { Icon } from "@budibase/bbui"
|
import { Icon } from "@budibase/bbui"
|
||||||
import { createEventDispatcher, getContext } from "svelte"
|
import { createEventDispatcher, getContext } from "svelte"
|
||||||
import { helpers } from "@budibase/shared-core"
|
import { helpers } from "@budibase/shared-core"
|
||||||
|
import UserAvatars from "../../pages/builder/app/[application]/_components/UserAvatars.svelte"
|
||||||
|
|
||||||
export let icon
|
export let icon
|
||||||
export let withArrow = false
|
export let withArrow = false
|
||||||
|
@ -98,21 +99,25 @@
|
||||||
<Icon color={iconColor} size="S" name={icon} />
|
<Icon color={iconColor} size="S" name={icon} />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/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}
|
{#if withActions}
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if $$slots.right}
|
{#if $$slots.right}
|
||||||
<div class="right">
|
<div class="right">
|
||||||
<slot name="right" />
|
<slot name="right" />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{#if selectedBy}
|
|
||||||
<div class="selected-by-label">{helpers.getUserLabel(selectedBy)}</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
@ -136,13 +141,16 @@
|
||||||
}
|
}
|
||||||
.nav-item.highlighted {
|
.nav-item.highlighted {
|
||||||
background-color: var(--spectrum-global-color-gray-200);
|
background-color: var(--spectrum-global-color-gray-200);
|
||||||
|
--avatars-background: var(--spectrum-global-color-gray-200);
|
||||||
}
|
}
|
||||||
.nav-item.selected {
|
.nav-item.selected {
|
||||||
background-color: var(--spectrum-global-color-gray-300);
|
background-color: var(--spectrum-global-color-gray-300);
|
||||||
|
--avatars-background: var(--spectrum-global-color-gray-300);
|
||||||
color: var(--ink);
|
color: var(--ink);
|
||||||
}
|
}
|
||||||
.nav-item:hover {
|
.nav-item:hover {
|
||||||
background-color: var(--spectrum-global-color-gray-300);
|
background-color: var(--spectrum-global-color-gray-300);
|
||||||
|
--avatars-background: var(--spectrum-global-color-gray-300);
|
||||||
}
|
}
|
||||||
.nav-item:hover .actions {
|
.nav-item:hover .actions {
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
|
@ -159,37 +167,6 @@
|
||||||
padding-left: var(--spacing-l);
|
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 */
|
/* Needed to fully display the actions icon */
|
||||||
.nav-item.scrollable .nav-item-content {
|
.nav-item.scrollable .nav-item-content {
|
||||||
padding-right: 1px;
|
padding-right: 1px;
|
||||||
|
@ -245,6 +222,9 @@
|
||||||
color: var(--spectrum-global-color-gray-900);
|
color: var(--spectrum-global-color-gray-900);
|
||||||
order: 2;
|
order: 2;
|
||||||
width: 0;
|
width: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
}
|
}
|
||||||
.scrollable .text {
|
.scrollable .text {
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
Link,
|
Link,
|
||||||
Modal,
|
Modal,
|
||||||
StatusLight,
|
StatusLight,
|
||||||
|
AbsTooltip,
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
import RevertModal from "components/deploy/RevertModal.svelte"
|
import RevertModal from "components/deploy/RevertModal.svelte"
|
||||||
import VersionModal from "components/deploy/VersionModal.svelte"
|
import VersionModal from "components/deploy/VersionModal.svelte"
|
||||||
|
@ -250,15 +251,20 @@
|
||||||
<Link quiet on:click={unpublishApp}>Unpublish</Link>
|
<Link quiet on:click={unpublishApp}>Unpublish</Link>
|
||||||
</span>
|
</span>
|
||||||
<span class="revert-link">
|
<span class="revert-link">
|
||||||
<Link
|
<AbsTooltip
|
||||||
disabled={!$isOnlyUser}
|
text={$isOnlyUser
|
||||||
quiet
|
? null
|
||||||
secondary
|
: "Unavailable - another user is editing this app"}
|
||||||
on:click={revertApp}
|
|
||||||
tooltip="Unavailable - another user is editing this app"
|
|
||||||
>
|
>
|
||||||
Revert
|
<Link
|
||||||
</Link>
|
disabled={!$isOnlyUser}
|
||||||
|
quiet
|
||||||
|
secondary
|
||||||
|
on:click={revertApp}
|
||||||
|
>
|
||||||
|
Revert
|
||||||
|
</Link>
|
||||||
|
</AbsTooltip>
|
||||||
</span>
|
</span>
|
||||||
{:else}
|
{:else}
|
||||||
<span class="status-text unpublished">Not published</span>
|
<span class="status-text unpublished">Not published</span>
|
||||||
|
|
|
@ -1,11 +1,8 @@
|
||||||
<script>
|
<script>
|
||||||
import { Tooltip } from "@budibase/bbui"
|
|
||||||
|
|
||||||
export let text
|
export let text
|
||||||
export let url
|
export let url
|
||||||
export let active = false
|
export let active = false
|
||||||
export let disabled = false
|
export let disabled = false
|
||||||
export let tooltip = null
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="side-nav-item">
|
<div class="side-nav-item">
|
||||||
|
@ -18,11 +15,6 @@
|
||||||
{text || ""}
|
{text || ""}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#if tooltip}
|
|
||||||
<div class="tooltip">
|
|
||||||
<Tooltip textWrapping direction="right" text={tooltip} />
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
@ -45,17 +37,4 @@
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
color: var(--spectrum-global-color-gray-500) !important;
|
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>
|
</style>
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
|
window.isBuilder = true
|
||||||
window.closePreview = () => {
|
window.closePreview = () => {
|
||||||
store.update(state => ({
|
store.update(state => ({
|
||||||
...state,
|
...state,
|
||||||
|
|
|
@ -1,9 +1,14 @@
|
||||||
<script>
|
<script>
|
||||||
import { UserAvatar } from "@budibase/frontend-core"
|
import { UserAvatar } from "@budibase/frontend-core"
|
||||||
|
import { TooltipPosition, Avatar } from "@budibase/bbui"
|
||||||
|
|
||||||
export let users = []
|
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 => {
|
const unique = users => {
|
||||||
let uniqueUsers = {}
|
let uniqueUsers = {}
|
||||||
|
@ -12,17 +17,51 @@
|
||||||
})
|
})
|
||||||
return Object.values(uniqueUsers)
|
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>
|
</script>
|
||||||
|
|
||||||
<div class="avatars">
|
<div class="avatars">
|
||||||
{#each uniqueUsers as user}
|
{#each avatars as user}
|
||||||
<UserAvatar {user} tooltipDirection="bottom" />
|
<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}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.avatars {
|
.avatars {
|
||||||
display: flex;
|
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>
|
</style>
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
Heading,
|
Heading,
|
||||||
Modal,
|
Modal,
|
||||||
notifications,
|
notifications,
|
||||||
|
TooltipPosition,
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
import AppActions from "components/deploy/AppActions.svelte"
|
import AppActions from "components/deploy/AppActions.svelte"
|
||||||
import { API } from "api"
|
import { API } from "api"
|
||||||
|
@ -172,7 +173,11 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="toprightnav">
|
<div class="toprightnav">
|
||||||
<span>
|
<span>
|
||||||
<UserAvatars users={$userStore} />
|
<UserAvatars
|
||||||
|
users={$userStore}
|
||||||
|
order="rtl"
|
||||||
|
tooltipPosition={TooltipPosition.Bottom}
|
||||||
|
/>
|
||||||
</span>
|
</span>
|
||||||
<AppActions {application} {loaded} />
|
<AppActions {application} {loaded} />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
} from "stores/backend"
|
} from "stores/backend"
|
||||||
|
|
||||||
import { hasData } from "stores/selectors"
|
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 { params, goto } from "@roxi/routify"
|
||||||
import CreateExternalDatasourceModal from "./_components/CreateExternalDatasourceModal/index.svelte"
|
import CreateExternalDatasourceModal from "./_components/CreateExternalDatasourceModal/index.svelte"
|
||||||
import CreateInternalTableModal from "./_components/CreateInternalTableModal.svelte"
|
import CreateInternalTableModal from "./_components/CreateInternalTableModal.svelte"
|
||||||
|
@ -15,7 +15,6 @@
|
||||||
import IntegrationIcon from "components/backend/DatasourceNavigator/IntegrationIcon.svelte"
|
import IntegrationIcon from "components/backend/DatasourceNavigator/IntegrationIcon.svelte"
|
||||||
import CreationPage from "components/common/CreationPage.svelte"
|
import CreationPage from "components/common/CreationPage.svelte"
|
||||||
import ICONS from "components/backend/DatasourceNavigator/icons/index.js"
|
import ICONS from "components/backend/DatasourceNavigator/icons/index.js"
|
||||||
import FontAwesomeIcon from "components/common/FontAwesomeIcon.svelte"
|
|
||||||
|
|
||||||
let internalTableModal
|
let internalTableModal
|
||||||
let externalDatasourceModal
|
let externalDatasourceModal
|
||||||
|
@ -54,13 +53,9 @@
|
||||||
>
|
>
|
||||||
<div class="subHeading">
|
<div class="subHeading">
|
||||||
<Body>Get started with our Budibase DB</Body>
|
<Body>Get started with our Budibase DB</Body>
|
||||||
<div
|
<AbsTooltip text="Budibase DB is built with CouchDB">
|
||||||
role="tooltip"
|
<Icon name="Info" size="S" />
|
||||||
title="Budibase DB is built with CouchDB"
|
</AbsTooltip>
|
||||||
class="tooltip"
|
|
||||||
>
|
|
||||||
<FontAwesomeIcon name="fa-solid fa-circle-info" />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="options">
|
<div class="options">
|
||||||
|
@ -116,13 +111,12 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-top: 12px;
|
margin-top: 12px;
|
||||||
margin-bottom: 24px;
|
margin-bottom: 36px;
|
||||||
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
.subHeading :global(p) {
|
||||||
.tooltip {
|
color: var(--spectrum-global-color-gray-600) !important;
|
||||||
margin-left: 6px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.options {
|
.options {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: grid;
|
display: grid;
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
heading={hasScreens ? "Create new screen" : "Create your first screen"}
|
heading={hasScreens ? "Create new screen" : "Create your first screen"}
|
||||||
>
|
>
|
||||||
<div class="subHeading">
|
<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>
|
||||||
|
|
||||||
<div class="cards">
|
<div class="cards">
|
||||||
|
@ -56,18 +56,18 @@
|
||||||
.subHeading :global(p) {
|
.subHeading :global(p) {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin-top: 12px;
|
margin-top: 12px;
|
||||||
margin-bottom: 24px;
|
margin-bottom: 36px;
|
||||||
color: var(--grey-6);
|
color: var(--spectrum-global-color-gray-600);
|
||||||
}
|
}
|
||||||
|
|
||||||
.cards {
|
.cards {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
gap: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card {
|
.card {
|
||||||
margin: 12px;
|
|
||||||
max-width: 235px;
|
max-width: 235px;
|
||||||
transition: filter 150ms;
|
transition: filter 150ms;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import { Content, SideNav, SideNavItem } from "components/portal/page"
|
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 { url, isActive } from "@roxi/routify"
|
||||||
import DeleteModal from "components/deploy/DeleteModal.svelte"
|
import DeleteModal from "components/deploy/DeleteModal.svelte"
|
||||||
import { isOnlyUser } from "builderStore"
|
import { isOnlyUser } from "builderStore"
|
||||||
|
@ -45,16 +45,20 @@
|
||||||
active={$isActive("./version")}
|
active={$isActive("./version")}
|
||||||
/>
|
/>
|
||||||
<div class="delete-action">
|
<div class="delete-action">
|
||||||
<SideNavItem
|
<AbsTooltip
|
||||||
text="Delete app"
|
position={TooltipPosition.Bottom}
|
||||||
on:click={() => {
|
text={$isOnlyUser
|
||||||
deleteModal.show()
|
|
||||||
}}
|
|
||||||
disabled={!$isOnlyUser}
|
|
||||||
tooltip={$isOnlyUser
|
|
||||||
? null
|
? null
|
||||||
: "Unavailable - another user is editing this app"}
|
: "Unavailable - another user is editing this app"}
|
||||||
/>
|
>
|
||||||
|
<SideNavItem
|
||||||
|
text="Delete app"
|
||||||
|
disabled={!$isOnlyUser}
|
||||||
|
on:click={() => {
|
||||||
|
deleteModal.show()
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</AbsTooltip>
|
||||||
</div>
|
</div>
|
||||||
</SideNav>
|
</SideNav>
|
||||||
<slot />
|
<slot />
|
||||||
|
|
|
@ -6,6 +6,8 @@
|
||||||
Heading,
|
Heading,
|
||||||
Body,
|
Body,
|
||||||
Modal,
|
Modal,
|
||||||
|
AbsTooltip,
|
||||||
|
TooltipPosition,
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
||||||
import CreateRestoreModal from "./CreateRestoreModal.svelte"
|
import CreateRestoreModal from "./CreateRestoreModal.svelte"
|
||||||
|
@ -46,16 +48,18 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if row.type !== "restore"}
|
{#if row.type !== "restore"}
|
||||||
<MenuItem
|
<AbsTooltip
|
||||||
on:click={restoreDialog.show}
|
position={TooltipPosition.Left}
|
||||||
icon="Revert"
|
text="Unavailable - another user is editing this app"
|
||||||
disabled={!$isOnlyUser}
|
|
||||||
tooltip={$isOnlyUser
|
|
||||||
? null
|
|
||||||
: "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={deleteDialog.show} icon="Delete">Delete</MenuItem>
|
||||||
<MenuItem on:click={downloadExport} icon="Download">Download</MenuItem>
|
<MenuItem on:click={downloadExport} icon="Download">Download</MenuItem>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import { Heading, Select, ActionButton } from "@budibase/bbui"
|
import { Heading, Select, ActionButton } from "@budibase/bbui"
|
||||||
import { devToolsStore } from "../../stores"
|
import { devToolsStore, appStore } from "../../stores"
|
||||||
import { getContext } from "svelte"
|
import { getContext } from "svelte"
|
||||||
|
|
||||||
const context = getContext("context")
|
const context = getContext("context")
|
||||||
|
@ -45,27 +45,41 @@
|
||||||
icon="Code"
|
icon="Code"
|
||||||
on:click={() => devToolsStore.actions.setVisible(!$devToolsStore.visible)}
|
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>
|
</ActionButton>
|
||||||
{/if}
|
{/if}
|
||||||
<ActionButton
|
|
||||||
quiet
|
|
||||||
icon="Close"
|
|
||||||
on:click={() => window.parent.closePreview?.()}
|
|
||||||
>
|
|
||||||
Close preview
|
|
||||||
</ActionButton>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.dev-preview-header {
|
.dev-preview-header {
|
||||||
flex: 0 0 60px;
|
flex: 0 0 60px;
|
||||||
display: grid;
|
|
||||||
align-items: center;
|
|
||||||
background-color: black;
|
background-color: black;
|
||||||
padding: 0 var(--spacing-xl);
|
padding: 0 var(--spacing-xl);
|
||||||
grid-template-columns: 1fr auto auto auto;
|
display: flex;
|
||||||
grid-gap: var(--spacing-xl);
|
align-items: center;
|
||||||
|
gap: var(--spacing-xl);
|
||||||
|
}
|
||||||
|
.dev-preview-header :global(.spectrum-Heading) {
|
||||||
|
flex: 1 1 auto;
|
||||||
}
|
}
|
||||||
.dev-preview-header.mobile {
|
.dev-preview-header.mobile {
|
||||||
grid-template-columns: 1fr auto auto;
|
grid-template-columns: 1fr auto auto;
|
||||||
|
|
|
@ -1,60 +1,23 @@
|
||||||
<script>
|
<script>
|
||||||
import { Avatar, Tooltip } from "@budibase/bbui"
|
import { Avatar, AbsTooltip, TooltipPosition } from "@budibase/bbui"
|
||||||
import { helpers } from "@budibase/shared-core"
|
import { helpers } from "@budibase/shared-core"
|
||||||
|
|
||||||
export let user
|
export let user
|
||||||
export let size
|
export let size = "S"
|
||||||
export let tooltipDirection = "top"
|
export let tooltipPosition = TooltipPosition.Top
|
||||||
export let showTooltip = true
|
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>
|
</script>
|
||||||
|
|
||||||
{#if user}
|
{#if user}
|
||||||
<div class="user-avatar">
|
<AbsTooltip
|
||||||
|
text={showTooltip ? helpers.getUserLabel(user) : null}
|
||||||
|
position={tooltipPosition}
|
||||||
|
color={helpers.getUserColor(user)}
|
||||||
|
>
|
||||||
<Avatar
|
<Avatar
|
||||||
{size}
|
{size}
|
||||||
initials={helpers.getUserInitials(user)}
|
initials={helpers.getUserInitials(user)}
|
||||||
color={helpers.getUserColor(user)}
|
color={helpers.getUserColor(user)}
|
||||||
/>
|
/>
|
||||||
{#if showTooltip}
|
</AbsTooltip>
|
||||||
<div class="tooltip" style={tooltipStyle}>
|
|
||||||
<Tooltip
|
|
||||||
direction={tooltipDirection}
|
|
||||||
textWrapping
|
|
||||||
text={helpers.getUserLabel(user)}
|
|
||||||
size="S"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
{/if}
|
{/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>
|
|
||||||
|
|
|
@ -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>
|
|
|
@ -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>
|
|
|
@ -71,6 +71,7 @@
|
||||||
contentLines,
|
contentLines,
|
||||||
gridFocused,
|
gridFocused,
|
||||||
error,
|
error,
|
||||||
|
canAddRows,
|
||||||
} = context
|
} = context
|
||||||
|
|
||||||
// Keep config store up to date with props
|
// Keep config store up to date with props
|
||||||
|
@ -143,7 +144,7 @@
|
||||||
<HeaderRow />
|
<HeaderRow />
|
||||||
<GridBody />
|
<GridBody />
|
||||||
</div>
|
</div>
|
||||||
{#if allowAddRows}
|
{#if $canAddRows}
|
||||||
<NewRow />
|
<NewRow />
|
||||||
{/if}
|
{/if}
|
||||||
<div class="overlays">
|
<div class="overlays">
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
renderedRows,
|
renderedRows,
|
||||||
renderedColumns,
|
renderedColumns,
|
||||||
rowVerticalInversionIndex,
|
rowVerticalInversionIndex,
|
||||||
config,
|
canAddRows,
|
||||||
hoveredRowId,
|
hoveredRowId,
|
||||||
dispatch,
|
dispatch,
|
||||||
isDragging,
|
isDragging,
|
||||||
|
@ -43,7 +43,7 @@
|
||||||
invertY={idx >= $rowVerticalInversionIndex}
|
invertY={idx >= $rowVerticalInversionIndex}
|
||||||
/>
|
/>
|
||||||
{/each}
|
{/each}
|
||||||
{#if $config.allowAddRows && $renderedColumns.length}
|
{#if $canAddRows}
|
||||||
<div
|
<div
|
||||||
class="blank"
|
class="blank"
|
||||||
class:highlighted={$hoveredRowId === BlankRowID}
|
class:highlighted={$hoveredRowId === BlankRowID}
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
import { getContext } from "svelte"
|
import { getContext } from "svelte"
|
||||||
import GridScrollWrapper from "./GridScrollWrapper.svelte"
|
import GridScrollWrapper from "./GridScrollWrapper.svelte"
|
||||||
import HeaderCell from "../cells/HeaderCell.svelte"
|
import HeaderCell from "../cells/HeaderCell.svelte"
|
||||||
import { Icon } from "@budibase/bbui"
|
import { Icon, TempTooltip, TooltipType } from "@budibase/bbui"
|
||||||
|
|
||||||
const {
|
const {
|
||||||
renderedColumns,
|
renderedColumns,
|
||||||
|
@ -11,10 +11,13 @@
|
||||||
hiddenColumnsWidth,
|
hiddenColumnsWidth,
|
||||||
width,
|
width,
|
||||||
config,
|
config,
|
||||||
|
hasNonAutoColumn,
|
||||||
|
tableId,
|
||||||
|
loading,
|
||||||
} = getContext("grid")
|
} = getContext("grid")
|
||||||
|
|
||||||
$: columnsWidth = $renderedColumns.reduce(
|
$: columnsWidth = $renderedColumns.reduce(
|
||||||
(total, col) => (total += col.width),
|
(total, col) => total + col.width,
|
||||||
0
|
0
|
||||||
)
|
)
|
||||||
$: end = $hiddenColumnsWidth + columnsWidth - 1 - $scroll.left
|
$: end = $hiddenColumnsWidth + columnsWidth - 1 - $scroll.left
|
||||||
|
@ -30,13 +33,21 @@
|
||||||
</div>
|
</div>
|
||||||
</GridScrollWrapper>
|
</GridScrollWrapper>
|
||||||
{#if $config.allowSchemaChanges}
|
{#if $config.allowSchemaChanges}
|
||||||
<div
|
{#key $tableId}
|
||||||
class="add"
|
<TempTooltip
|
||||||
style="left:{left}px"
|
text="Click here to create your first column"
|
||||||
on:click={() => dispatch("add-column")}
|
type={TooltipType.Info}
|
||||||
>
|
condition={!$hasNonAutoColumn && !$loading}
|
||||||
<Icon name="Add" />
|
>
|
||||||
</div>
|
<div
|
||||||
|
class="add"
|
||||||
|
style="left:{left}px;"
|
||||||
|
on:click={() => dispatch("add-column")}
|
||||||
|
>
|
||||||
|
<Icon name="Add" />
|
||||||
|
</div>
|
||||||
|
</TempTooltip>
|
||||||
|
{/key}
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import { getContext, onDestroy, onMount, tick } from "svelte"
|
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 GridScrollWrapper from "./GridScrollWrapper.svelte"
|
||||||
import DataCell from "../cells/DataCell.svelte"
|
import DataCell from "../cells/DataCell.svelte"
|
||||||
import { fade } from "svelte/transition"
|
import { fade } from "svelte/transition"
|
||||||
|
@ -27,7 +27,8 @@
|
||||||
rowVerticalInversionIndex,
|
rowVerticalInversionIndex,
|
||||||
columnHorizontalInversionIndex,
|
columnHorizontalInversionIndex,
|
||||||
selectedRows,
|
selectedRows,
|
||||||
config,
|
loading,
|
||||||
|
canAddRows,
|
||||||
} = getContext("grid")
|
} = getContext("grid")
|
||||||
|
|
||||||
let visible = false
|
let visible = false
|
||||||
|
@ -40,6 +41,7 @@
|
||||||
$: $tableId, (visible = false)
|
$: $tableId, (visible = false)
|
||||||
$: invertY = shouldInvertY(offset, $rowVerticalInversionIndex, $renderedRows)
|
$: invertY = shouldInvertY(offset, $rowVerticalInversionIndex, $renderedRows)
|
||||||
$: selectedRowCount = Object.values($selectedRows).length
|
$: selectedRowCount = Object.values($selectedRows).length
|
||||||
|
$: hasNoRows = !$rows.length
|
||||||
|
|
||||||
const shouldInvertY = (offset, inversionIndex, rows) => {
|
const shouldInvertY = (offset, inversionIndex, rows) => {
|
||||||
if (offset === 0) {
|
if (offset === 0) {
|
||||||
|
@ -147,16 +149,22 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- New row FAB -->
|
<!-- New row FAB -->
|
||||||
{#if !visible && !selectedRowCount && $config.allowAddRows && firstColumn}
|
<TempTooltip
|
||||||
<div
|
text="Click here to create your first row"
|
||||||
class="new-row-fab"
|
condition={hasNoRows && !$loading}
|
||||||
on:click={() => dispatch("add-row-inline")}
|
type={TooltipType.Info}
|
||||||
transition:fade|local={{ duration: 130 }}
|
>
|
||||||
class:offset={!$stickyColumn}
|
{#if !visible && !selectedRowCount && $canAddRows}
|
||||||
>
|
<div
|
||||||
<Icon name="Add" size="S" />
|
class="new-row-fab"
|
||||||
</div>
|
on:click={() => dispatch("add-row-inline")}
|
||||||
{/if}
|
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 -->
|
<!-- Only show new row functionality if we have any columns -->
|
||||||
{#if visible}
|
{#if visible}
|
||||||
|
|
|
@ -13,11 +13,10 @@
|
||||||
rows,
|
rows,
|
||||||
selectedRows,
|
selectedRows,
|
||||||
stickyColumn,
|
stickyColumn,
|
||||||
renderedColumns,
|
|
||||||
renderedRows,
|
renderedRows,
|
||||||
focusedCellId,
|
focusedCellId,
|
||||||
hoveredRowId,
|
hoveredRowId,
|
||||||
config,
|
canAddRows,
|
||||||
selectedCellMap,
|
selectedCellMap,
|
||||||
focusedRow,
|
focusedRow,
|
||||||
scrollLeft,
|
scrollLeft,
|
||||||
|
@ -93,7 +92,7 @@
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
{#if $config.allowAddRows && ($renderedColumns.length || $stickyColumn)}
|
{#if $canAddRows}
|
||||||
<div
|
<div
|
||||||
class="row new"
|
class="row new"
|
||||||
on:mouseenter={$isDragging
|
on:mouseenter={$isDragging
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
config,
|
config,
|
||||||
menu,
|
menu,
|
||||||
gridFocused,
|
gridFocused,
|
||||||
|
canAddRows,
|
||||||
} = getContext("grid")
|
} = getContext("grid")
|
||||||
|
|
||||||
const ignoredOriginSelectors = [
|
const ignoredOriginSelectors = [
|
||||||
|
@ -45,7 +46,7 @@
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
focusFirstCell()
|
focusFirstCell()
|
||||||
} else if (e.key === "Enter" && (e.ctrlKey || e.metaKey)) {
|
} else if (e.key === "Enter" && (e.ctrlKey || e.metaKey)) {
|
||||||
if ($config.allowAddRows) {
|
if ($canAddRows) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
dispatch("add-row-inline")
|
dispatch("add-row-inline")
|
||||||
}
|
}
|
||||||
|
@ -99,7 +100,7 @@
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
case "Enter":
|
case "Enter":
|
||||||
if ($config.allowAddRows) {
|
if ($canAddRows) {
|
||||||
dispatch("add-row-inline")
|
dispatch("add-row-inline")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
focusedCellAPI,
|
focusedCellAPI,
|
||||||
focusedRowId,
|
focusedRowId,
|
||||||
notifications,
|
notifications,
|
||||||
|
canAddRows,
|
||||||
} = getContext("grid")
|
} = getContext("grid")
|
||||||
|
|
||||||
$: style = makeStyle($menu)
|
$: style = makeStyle($menu)
|
||||||
|
@ -93,7 +94,7 @@
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
icon="Duplicate"
|
icon="Duplicate"
|
||||||
disabled={isNewRow || !$config.allowAddRows}
|
disabled={isNewRow || !$canAddRows}
|
||||||
on:click={duplicate}
|
on:click={duplicate}
|
||||||
>
|
>
|
||||||
Duplicate row
|
Duplicate row
|
||||||
|
|
|
@ -83,6 +83,21 @@ export const deriveStores = context => {
|
||||||
await saveChanges()
|
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
|
// Persists column changes by saving metadata against table schema
|
||||||
const saveChanges = async () => {
|
const saveChanges = async () => {
|
||||||
const $columns = get(columns)
|
const $columns = get(columns)
|
||||||
|
@ -128,6 +143,7 @@ export const deriveStores = context => {
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
hasNonAutoColumn,
|
||||||
columns: {
|
columns: {
|
||||||
...columns,
|
...columns,
|
||||||
actions: {
|
actions: {
|
||||||
|
|
|
@ -70,6 +70,8 @@ export const deriveStores = context => {
|
||||||
rowHeight,
|
rowHeight,
|
||||||
stickyColumn,
|
stickyColumn,
|
||||||
width,
|
width,
|
||||||
|
hasNonAutoColumn,
|
||||||
|
config,
|
||||||
} = context
|
} = context
|
||||||
|
|
||||||
// Derive the row that contains the selected cell
|
// Derive the row that contains the selected cell
|
||||||
|
@ -112,7 +114,16 @@ export const deriveStores = context => {
|
||||||
return ($stickyColumn?.width || 0) + $width + GutterWidth < 1100
|
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 {
|
return {
|
||||||
|
canAddRows,
|
||||||
focusedRow,
|
focusedRow,
|
||||||
contentLines,
|
contentLines,
|
||||||
compact,
|
compact,
|
||||||
|
|
Loading…
Reference in New Issue