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>
|
||||
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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
}
|
||||
|
||||
onMount(() => {
|
||||
window.isBuilder = true
|
||||
window.closePreview = () => {
|
||||
store.update(state => ({
|
||||
...state,
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 />
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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,
|
||||
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">
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue