Merge remote-tracking branch 'origin/cheeks-lab-day-portal-redesign' into feature/environment-variables
This commit is contained in:
commit
e8e29ddc8c
27 changed files with 359 additions and 357 deletions
|
@ -1,4 +1,4 @@
|
|||
const ignoredClasses = [".flatpickr-calendar", ".modal-container"]
|
||||
const ignoredClasses = [".flatpickr-calendar"]
|
||||
let clickHandlers = []
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,75 +1,69 @@
|
|||
export default function positionDropdown(element, { anchor, align, maxWidth }) {
|
||||
let positionSide = "top"
|
||||
let maxHeight = 0
|
||||
let dimensions = getDimensions(anchor)
|
||||
export default function positionDropdown(
|
||||
element,
|
||||
{ anchor, align, maxWidth, useAnchorWidth }
|
||||
) {
|
||||
const update = () => {
|
||||
console.log("update")
|
||||
const anchorBounds = anchor.getBoundingClientRect()
|
||||
const elementBounds = element.getBoundingClientRect()
|
||||
let styles = {
|
||||
maxHeight: null,
|
||||
minWidth: null,
|
||||
maxWidth,
|
||||
left: null,
|
||||
top: null,
|
||||
}
|
||||
|
||||
function getDimensions() {
|
||||
const {
|
||||
bottom,
|
||||
top: spaceAbove,
|
||||
left,
|
||||
width,
|
||||
} = anchor.getBoundingClientRect()
|
||||
const spaceBelow = window.innerHeight - bottom
|
||||
const containerRect = element.getBoundingClientRect()
|
||||
|
||||
let y
|
||||
|
||||
if (spaceAbove > spaceBelow) {
|
||||
positionSide = "bottom"
|
||||
maxHeight = spaceAbove - 20
|
||||
y = window.innerHeight - spaceAbove + 5
|
||||
// Determine vertical styles
|
||||
if (window.innerHeight - anchorBounds.bottom < 100) {
|
||||
styles.top = anchorBounds.top - elementBounds.height - 5
|
||||
} else {
|
||||
positionSide = "top"
|
||||
y = bottom + 5
|
||||
maxHeight = spaceBelow - 20
|
||||
styles.top = anchorBounds.bottom + 5
|
||||
styles.maxHeight = window.innerHeight - anchorBounds.bottom - 20
|
||||
}
|
||||
|
||||
return {
|
||||
[positionSide]: y,
|
||||
left,
|
||||
width,
|
||||
containerWidth: containerRect.width,
|
||||
// Determine horizontal styles
|
||||
if (!maxWidth && useAnchorWidth) {
|
||||
styles.maxWidth = anchorBounds.width
|
||||
}
|
||||
if (useAnchorWidth) {
|
||||
styles.minWidth = anchorBounds.width
|
||||
}
|
||||
if (align === "right") {
|
||||
styles.left = anchorBounds.left + anchorBounds.width - elementBounds.width
|
||||
} else if (align === "right-side") {
|
||||
styles.left = anchorBounds.left + anchorBounds.width
|
||||
} else {
|
||||
styles.left = anchorBounds.left
|
||||
}
|
||||
|
||||
// Apply styles
|
||||
Object.entries(styles).forEach(([style, value]) => {
|
||||
if (value) {
|
||||
element.style[style] = `${value.toFixed(0)}px`
|
||||
} else {
|
||||
element.style[style] = null
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function calcLeftPosition() {
|
||||
let left
|
||||
|
||||
if (align == "right") {
|
||||
left = dimensions.left + dimensions.width - dimensions.containerWidth
|
||||
} else if (align == "right-side") {
|
||||
left = dimensions.left + dimensions.width
|
||||
} else {
|
||||
left = dimensions.left
|
||||
}
|
||||
|
||||
return left
|
||||
}
|
||||
|
||||
// Apply initial styles which don't need to change
|
||||
element.style.position = "absolute"
|
||||
element.style.zIndex = "9999"
|
||||
if (maxWidth) {
|
||||
element.style.maxWidth = `${maxWidth}px`
|
||||
}
|
||||
element.style.minWidth = `${dimensions.width}px`
|
||||
element.style.maxHeight = `${maxHeight.toFixed(0)}px`
|
||||
element.style.transformOrigin = `center ${positionSide}`
|
||||
element.style[positionSide] = `${dimensions[positionSide]}px`
|
||||
element.style.left = `${calcLeftPosition(dimensions).toFixed(0)}px`
|
||||
|
||||
// Observe both anchor and element and resize the popover as appropriate
|
||||
const resizeObserver = new ResizeObserver(entries => {
|
||||
entries.forEach(() => {
|
||||
dimensions = getDimensions()
|
||||
element.style[positionSide] = `${dimensions[positionSide]}px`
|
||||
element.style.left = `${calcLeftPosition(dimensions).toFixed(0)}px`
|
||||
})
|
||||
entries.forEach(update)
|
||||
})
|
||||
resizeObserver.observe(anchor)
|
||||
resizeObserver.observe(element)
|
||||
|
||||
document.addEventListener("scroll", update, true)
|
||||
|
||||
return {
|
||||
destroy() {
|
||||
resizeObserver.disconnect()
|
||||
document.removeEventListener("scroll", update, true)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,12 +2,12 @@
|
|||
import "@spectrum-css/picker/dist/index-vars.css"
|
||||
import "@spectrum-css/popover/dist/index-vars.css"
|
||||
import "@spectrum-css/menu/dist/index-vars.css"
|
||||
import { fly } from "svelte/transition"
|
||||
import { createEventDispatcher } from "svelte"
|
||||
import clickOutside from "../../Actions/click_outside"
|
||||
import Search from "./Search.svelte"
|
||||
import Icon from "../../Icon/Icon.svelte"
|
||||
import StatusLight from "../../StatusLight/StatusLight.svelte"
|
||||
import Popover from "../../Popover/Popover.svelte"
|
||||
|
||||
export let id = null
|
||||
export let disabled = false
|
||||
|
@ -33,7 +33,10 @@
|
|||
export let sort = false
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
let searchTerm = null
|
||||
let button
|
||||
let popover
|
||||
|
||||
$: sortedOptions = getSortedOptions(options, getOptionLabel, sort)
|
||||
$: filteredOptions = getFilteredOptions(
|
||||
|
@ -76,77 +79,117 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<div use:clickOutside={() => (open = false)}>
|
||||
<button
|
||||
{id}
|
||||
class="spectrum-Picker spectrum-Picker--sizeM"
|
||||
class:spectrum-Picker--quiet={quiet}
|
||||
{disabled}
|
||||
class:is-invalid={!!error}
|
||||
class:is-open={open}
|
||||
aria-haspopup="listbox"
|
||||
on:click={onClick}
|
||||
>
|
||||
{#if fieldIcon}
|
||||
<span class="option-extra icon">
|
||||
<Icon size="S" name={fieldIcon} />
|
||||
</span>
|
||||
{/if}
|
||||
{#if fieldColour}
|
||||
<span class="option-extra">
|
||||
<StatusLight square color={fieldColour} />
|
||||
</span>
|
||||
{/if}
|
||||
<span
|
||||
class="spectrum-Picker-label"
|
||||
class:is-placeholder={isPlaceholder}
|
||||
class:auto-width={autoWidth}
|
||||
>
|
||||
{fieldText}
|
||||
<button
|
||||
{id}
|
||||
class="spectrum-Picker spectrum-Picker--sizeM"
|
||||
class:spectrum-Picker--quiet={quiet}
|
||||
{disabled}
|
||||
class:is-invalid={!!error}
|
||||
class:is-open={open}
|
||||
aria-haspopup="listbox"
|
||||
on:click={onClick}
|
||||
use:clickOutside={() => (open = false)}
|
||||
bind:this={button}
|
||||
>
|
||||
{#if fieldIcon}
|
||||
<span class="option-extra icon">
|
||||
<Icon size="S" name={fieldIcon} />
|
||||
</span>
|
||||
{#if error}
|
||||
<svg
|
||||
class="spectrum-Icon spectrum-Icon--sizeM spectrum-Picker-validationIcon"
|
||||
focusable="false"
|
||||
aria-hidden="true"
|
||||
aria-label="Folder"
|
||||
>
|
||||
<use xlink:href="#spectrum-icon-18-Alert" />
|
||||
</svg>
|
||||
{/if}
|
||||
{/if}
|
||||
{#if fieldColour}
|
||||
<span class="option-extra">
|
||||
<StatusLight square color={fieldColour} />
|
||||
</span>
|
||||
{/if}
|
||||
<span
|
||||
class="spectrum-Picker-label"
|
||||
class:is-placeholder={isPlaceholder}
|
||||
class:auto-width={autoWidth}
|
||||
>
|
||||
{fieldText}
|
||||
</span>
|
||||
{#if error}
|
||||
<svg
|
||||
class="spectrum-Icon spectrum-UIIcon-ChevronDown100 spectrum-Picker-menuIcon"
|
||||
class="spectrum-Icon spectrum-Icon--sizeM spectrum-Picker-validationIcon"
|
||||
focusable="false"
|
||||
aria-hidden="true"
|
||||
aria-label="Folder"
|
||||
>
|
||||
<use xlink:href="#spectrum-css-icon-Chevron100" />
|
||||
<use xlink:href="#spectrum-icon-18-Alert" />
|
||||
</svg>
|
||||
</button>
|
||||
{#if open}
|
||||
<div
|
||||
transition:fly|local={{ y: -20, duration: 200 }}
|
||||
class="spectrum-Popover spectrum-Popover--bottom spectrum-Picker-popover is-open"
|
||||
class:auto-width={autoWidth}
|
||||
>
|
||||
{#if autocomplete}
|
||||
<Search
|
||||
value={searchTerm}
|
||||
on:change={event => (searchTerm = event.detail)}
|
||||
{disabled}
|
||||
placeholder="Search"
|
||||
/>
|
||||
{/if}
|
||||
<svg
|
||||
class="spectrum-Icon spectrum-UIIcon-ChevronDown100 spectrum-Picker-menuIcon"
|
||||
focusable="false"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<use xlink:href="#spectrum-css-icon-Chevron100" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<Popover
|
||||
anchor={button}
|
||||
align="left"
|
||||
portalTarget={document.documentElement}
|
||||
bind:this={popover}
|
||||
{open}
|
||||
on:close={() => (open = false)}
|
||||
useAnchorWidth={!autoWidth}
|
||||
maxWidth={autoWidth ? 400 : null}
|
||||
>
|
||||
<div class="popover-content" class:auto-width={autoWidth}>
|
||||
{#if autocomplete}
|
||||
<Search
|
||||
value={searchTerm}
|
||||
on:change={event => (searchTerm = event.detail)}
|
||||
{disabled}
|
||||
placeholder="Search"
|
||||
/>
|
||||
{/if}
|
||||
<ul class="spectrum-Menu" role="listbox">
|
||||
{#if placeholderOption}
|
||||
<li
|
||||
class="spectrum-Menu-item placeholder"
|
||||
class:is-selected={isPlaceholder}
|
||||
role="option"
|
||||
aria-selected="true"
|
||||
tabindex="0"
|
||||
on:click={() => onSelectOption(null)}
|
||||
>
|
||||
<span class="spectrum-Menu-itemLabel">{placeholderOption}</span>
|
||||
<svg
|
||||
class="spectrum-Icon spectrum-UIIcon-Checkmark100 spectrum-Menu-checkmark spectrum-Menu-itemIcon"
|
||||
focusable="false"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<use xlink:href="#spectrum-css-icon-Checkmark100" />
|
||||
</svg>
|
||||
</li>
|
||||
{/if}
|
||||
<ul class="spectrum-Menu" role="listbox">
|
||||
{#if placeholderOption}
|
||||
{#if filteredOptions.length}
|
||||
{#each filteredOptions as option, idx}
|
||||
<li
|
||||
class="spectrum-Menu-item placeholder"
|
||||
class:is-selected={isPlaceholder}
|
||||
class="spectrum-Menu-item"
|
||||
class:is-selected={isOptionSelected(getOptionValue(option, idx))}
|
||||
role="option"
|
||||
aria-selected="true"
|
||||
tabindex="0"
|
||||
on:click={() => onSelectOption(null)}
|
||||
on:click={() => onSelectOption(getOptionValue(option, idx))}
|
||||
class:is-disabled={!isOptionEnabled(option)}
|
||||
>
|
||||
<span class="spectrum-Menu-itemLabel">{placeholderOption}</span>
|
||||
{#if getOptionIcon(option, idx)}
|
||||
<span class="option-extra icon">
|
||||
<Icon size="S" name={getOptionIcon(option, idx)} />
|
||||
</span>
|
||||
{/if}
|
||||
{#if getOptionColour(option, idx)}
|
||||
<span class="option-extra">
|
||||
<StatusLight square color={getOptionColour(option, idx)} />
|
||||
</span>
|
||||
{/if}
|
||||
<span class="spectrum-Menu-itemLabel">
|
||||
{getOptionLabel(option, idx)}
|
||||
</span>
|
||||
<svg
|
||||
class="spectrum-Icon spectrum-UIIcon-Checkmark100 spectrum-Menu-checkmark spectrum-Menu-itemIcon"
|
||||
focusable="false"
|
||||
|
@ -155,61 +198,13 @@
|
|||
<use xlink:href="#spectrum-css-icon-Checkmark100" />
|
||||
</svg>
|
||||
</li>
|
||||
{/if}
|
||||
{#if filteredOptions.length}
|
||||
{#each filteredOptions as option, idx}
|
||||
<li
|
||||
class="spectrum-Menu-item"
|
||||
class:is-selected={isOptionSelected(getOptionValue(option, idx))}
|
||||
role="option"
|
||||
aria-selected="true"
|
||||
tabindex="0"
|
||||
on:click={() => onSelectOption(getOptionValue(option, idx))}
|
||||
class:is-disabled={!isOptionEnabled(option)}
|
||||
>
|
||||
{#if getOptionIcon(option, idx)}
|
||||
<span class="option-extra icon">
|
||||
<Icon size="S" name={getOptionIcon(option, idx)} />
|
||||
</span>
|
||||
{/if}
|
||||
{#if getOptionColour(option, idx)}
|
||||
<span class="option-extra">
|
||||
<StatusLight square color={getOptionColour(option, idx)} />
|
||||
</span>
|
||||
{/if}
|
||||
<span class="spectrum-Menu-itemLabel">
|
||||
{getOptionLabel(option, idx)}
|
||||
</span>
|
||||
<svg
|
||||
class="spectrum-Icon spectrum-UIIcon-Checkmark100 spectrum-Menu-checkmark spectrum-Menu-itemIcon"
|
||||
focusable="false"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<use xlink:href="#spectrum-css-icon-Checkmark100" />
|
||||
</svg>
|
||||
</li>
|
||||
{/each}
|
||||
{/if}
|
||||
</ul>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
{/if}
|
||||
</ul>
|
||||
</div>
|
||||
</Popover>
|
||||
|
||||
<style>
|
||||
.spectrum-Popover {
|
||||
max-height: 240px;
|
||||
z-index: 999;
|
||||
top: 100%;
|
||||
}
|
||||
.spectrum-Popover:not(.auto-width) {
|
||||
width: 100%;
|
||||
}
|
||||
.spectrum-Popover.auto-width :global(.spectrum-Menu-itemLabel) {
|
||||
max-width: 400px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.spectrum-Picker {
|
||||
width: 100%;
|
||||
box-shadow: none;
|
||||
|
@ -229,9 +224,6 @@
|
|||
.spectrum-Picker-label.auto-width.is-placeholder {
|
||||
padding-right: 2px;
|
||||
}
|
||||
.auto-width .spectrum-Menu-item {
|
||||
padding-right: var(--spacing-xl);
|
||||
}
|
||||
|
||||
/* Icon and colour alignment */
|
||||
.spectrum-Menu-checkmark {
|
||||
|
@ -245,26 +237,44 @@
|
|||
margin: 0 -1px;
|
||||
}
|
||||
|
||||
.spectrum-Popover :global(.spectrum-Search) {
|
||||
/* Popover */
|
||||
.popover-content {
|
||||
display: contents;
|
||||
}
|
||||
.popover-content.auto-width .spectrum-Menu-itemLabel {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.popover-content:not(.auto-width) .spectrum-Menu-itemLabel {
|
||||
width: 0;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
.popover-content.auto-width .spectrum-Menu-item {
|
||||
padding-right: var(--spacing-xl);
|
||||
}
|
||||
.spectrum-Menu-item.is-disabled {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* Search styles inside popover */
|
||||
.popover-content :global(.spectrum-Search) {
|
||||
margin-top: -1px;
|
||||
margin-left: -1px;
|
||||
width: calc(100% + 2px);
|
||||
}
|
||||
.spectrum-Popover :global(.spectrum-Search input) {
|
||||
.popover-content :global(.spectrum-Search input) {
|
||||
height: auto;
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
padding-top: var(--spectrum-global-dimension-size-100);
|
||||
padding-bottom: var(--spectrum-global-dimension-size-100);
|
||||
}
|
||||
.spectrum-Popover :global(.spectrum-Search .spectrum-ClearButton) {
|
||||
.popover-content :global(.spectrum-Search .spectrum-ClearButton) {
|
||||
right: 1px;
|
||||
top: 2px;
|
||||
}
|
||||
.spectrum-Popover :global(.spectrum-Search .spectrum-Textfield-icon) {
|
||||
.popover-content :global(.spectrum-Search .spectrum-Textfield-icon) {
|
||||
top: 9px;
|
||||
}
|
||||
.spectrum-Menu-item.is-disabled {
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
import { createEventDispatcher } from "svelte"
|
||||
import positionDropdown from "../Actions/position_dropdown"
|
||||
import clickOutside from "../Actions/click_outside"
|
||||
import { fly } from "svelte/transition"
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
|
@ -12,9 +13,10 @@
|
|||
export let portalTarget
|
||||
export let dataCy
|
||||
export let maxWidth
|
||||
|
||||
export let direction = "bottom"
|
||||
export let showTip = false
|
||||
export let open = false
|
||||
export let useAnchorWidth = false
|
||||
|
||||
let tipSvg =
|
||||
'<svg xmlns="http://www.w3.org/svg/2000" width="23" height="12" class="spectrum-Popover-tip" > <path class="spectrum-Popover-tip-triangle" d="M 0.7071067811865476 0 L 11.414213562373096 10.707106781186548 L 22.121320343559645 0" /> </svg>'
|
||||
|
@ -35,13 +37,22 @@
|
|||
|
||||
const handleOutsideClick = e => {
|
||||
if (open) {
|
||||
e.stopPropagation()
|
||||
// Stop propagation if the source is the anchor
|
||||
let node = e.target
|
||||
let fromAnchor = false
|
||||
while (!fromAnchor && node && node.parentNode) {
|
||||
fromAnchor = node === anchor
|
||||
node = node.parentNode
|
||||
}
|
||||
if (fromAnchor) {
|
||||
e.stopPropagation()
|
||||
}
|
||||
|
||||
// Hide the popover
|
||||
hide()
|
||||
}
|
||||
}
|
||||
|
||||
let open = null
|
||||
|
||||
function handleEscape(e) {
|
||||
if (open && e.key === "Escape") {
|
||||
hide()
|
||||
|
@ -53,12 +64,13 @@
|
|||
<Portal target={portalTarget}>
|
||||
<div
|
||||
tabindex="0"
|
||||
use:positionDropdown={{ anchor, align, maxWidth }}
|
||||
use:positionDropdown={{ anchor, align, maxWidth, useAnchorWidth }}
|
||||
use:clickOutside={handleOutsideClick}
|
||||
on:keydown={handleEscape}
|
||||
class={"spectrum-Popover is-open " + (tooltipClasses || "")}
|
||||
role="presentation"
|
||||
data-cy={dataCy}
|
||||
transition:fly|local={{ y: -20, duration: 200 }}
|
||||
>
|
||||
{#if showTip}
|
||||
{@html tipSvg}
|
||||
|
|
|
@ -280,6 +280,9 @@
|
|||
styles[field] +=
|
||||
"border-right: 1px solid var(--spectrum-global-color-gray-200);"
|
||||
}
|
||||
if (schema[field].minWidth) {
|
||||
styles[field] += `min-width: ${schema[field].minWidth};`
|
||||
}
|
||||
})
|
||||
return styles
|
||||
}
|
||||
|
@ -450,6 +453,7 @@
|
|||
--table-bg: var(--spectrum-global-color-gray-50);
|
||||
--table-border: 1px solid var(--spectrum-alias-border-color-mid);
|
||||
--cell-padding: var(--spectrum-global-dimension-size-250);
|
||||
overflow: auto;
|
||||
}
|
||||
.wrapper--quiet {
|
||||
--table-bg: var(--spectrum-alias-background-color-transparent);
|
||||
|
|
|
@ -25,7 +25,6 @@
|
|||
export let loading = false
|
||||
export let hideAutocolumns
|
||||
export let rowCount
|
||||
export let type
|
||||
export let disableSorting = false
|
||||
export let customPlaceholder = false
|
||||
|
||||
|
|
|
@ -141,8 +141,4 @@
|
|||
gap: var(--spacing-s);
|
||||
max-width: 175px;
|
||||
}
|
||||
.lock-status-text {
|
||||
font-weight: 400;
|
||||
color: var(--spectrum-global-color-gray-800);
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
import { ModalContent } from "@budibase/bbui"
|
||||
import { Label, Select } from "@budibase/bbui"
|
||||
import { Select } from "@budibase/bbui"
|
||||
import { themeStore } from "builderStore"
|
||||
import { Constants } from "@budibase/frontend-core"
|
||||
</script>
|
||||
|
|
|
@ -1,14 +1,35 @@
|
|||
<script>
|
||||
import { Heading, Body, Button, Icon } from "@budibase/bbui"
|
||||
import { Heading, Body, Button, Icon, notifications } from "@budibase/bbui"
|
||||
import AppLockModal from "../common/AppLockModal.svelte"
|
||||
import { processStringSync } from "@budibase/string-templates"
|
||||
import { goto } from "@roxi/routify"
|
||||
|
||||
export let app
|
||||
export let editApp
|
||||
export let appOverview
|
||||
|
||||
const handleDefaultClick = () => {
|
||||
if (window.innerWidth < 640) {
|
||||
goToOverview()
|
||||
} else {
|
||||
goToBuilder()
|
||||
}
|
||||
}
|
||||
|
||||
const goToBuilder = () => {
|
||||
if (app.lockedOther) {
|
||||
notifications.error(
|
||||
`App locked by ${app.lockedBy.email}. Please allow lock to expire or have them unlock this app.`
|
||||
)
|
||||
return
|
||||
}
|
||||
$goto(`../../app/${app.devId}`)
|
||||
}
|
||||
|
||||
const goToOverview = () => {
|
||||
$goto(`../overview/${app.devId}`)
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="app-row" on:click={() => editApp(app)}>
|
||||
<div class="app-row" on:click={handleDefaultClick}>
|
||||
<div class="title" data-cy={`${app.devId}`}>
|
||||
<div class="app-icon">
|
||||
<Icon size="L" name={app.icon?.name || "Apps"} color={app.icon?.color} />
|
||||
|
@ -35,21 +56,12 @@
|
|||
<Body size="S">{app.deployed ? "Published" : "Unpublished"}</Body>
|
||||
</div>
|
||||
|
||||
<div data-cy={`row_actions_${app.appId}`}>
|
||||
<div class="app-row-actions">
|
||||
<AppLockModal {app} buttonSize="M" />
|
||||
<Button size="S" secondary on:click={() => appOverview(app)}>
|
||||
Manage
|
||||
</Button>
|
||||
<Button
|
||||
size="S"
|
||||
primary
|
||||
disabled={app.lockedOther}
|
||||
on:click={() => editApp(app)}
|
||||
>
|
||||
Edit
|
||||
</Button>
|
||||
</div>
|
||||
<div class="app-row-actions" data-cy={`row_actions_${app.appId}`}>
|
||||
<AppLockModal {app} buttonSize="M" />
|
||||
<Button size="S" secondary on:click={goToOverview}>Manage</Button>
|
||||
<Button size="S" primary disabled={app.lockedOther} on:click={goToBuilder}>
|
||||
Edit
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -139,5 +151,8 @@
|
|||
.app-row {
|
||||
padding: 20px;
|
||||
}
|
||||
.app-row-actions {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -263,6 +263,7 @@
|
|||
orderMap[component.component]}
|
||||
on:click={() => addComponent(component.component)}
|
||||
on:mouseover={() => (selectedIndex = null)}
|
||||
on:focus
|
||||
>
|
||||
<Icon name={component.icon} />
|
||||
<Body size="XS">{component.name}</Body>
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
class="container"
|
||||
on:mouseover={() => (showTooltip = true)}
|
||||
on:mouseleave={() => (showTooltip = false)}
|
||||
on:focus
|
||||
style="--color: {color};"
|
||||
>
|
||||
<StatusLight square {color} />
|
||||
|
|
|
@ -8,12 +8,11 @@
|
|||
Detail,
|
||||
Link,
|
||||
TooltipWrapper,
|
||||
Page,
|
||||
} from "@budibase/bbui"
|
||||
import { onMount } from "svelte"
|
||||
import { admin, auth, licensing } from "../../../../stores/portal"
|
||||
import { admin, auth, licensing } from "stores/portal"
|
||||
import { Constants } from "@budibase/frontend-core"
|
||||
import { DashCard, Usage } from "../../../../components/usage"
|
||||
import { DashCard, Usage } from "components/usage"
|
||||
|
||||
let staticUsage = []
|
||||
let monthlyUsage = []
|
||||
|
|
|
@ -4,7 +4,8 @@
|
|||
import { onMount } from "svelte"
|
||||
import { goto } from "@roxi/routify"
|
||||
|
||||
let loaded = false
|
||||
// Don't block loading if we've already hydrated state
|
||||
let loaded = $apps.length > 0
|
||||
|
||||
onMount(async () => {
|
||||
try {
|
||||
|
|
|
@ -77,31 +77,3 @@
|
|||
<CreateAppModal {template} />
|
||||
</Modal>
|
||||
<AppLimitModal bind:this={appLimitModal} />
|
||||
|
||||
<style>
|
||||
.title .welcome > .buttons {
|
||||
padding-top: 30px;
|
||||
}
|
||||
.title {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: var(--spacing-xl);
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.buttons {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
gap: var(--spacing-xl);
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
@media (max-width: 640px) {
|
||||
.buttons {
|
||||
flex-direction: row-reverse;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -178,20 +178,6 @@
|
|||
creatingApp = false
|
||||
}
|
||||
|
||||
const appOverview = app => {
|
||||
$goto(`../overview/${app.devId}`)
|
||||
}
|
||||
|
||||
const editApp = app => {
|
||||
if (app.lockedOther) {
|
||||
notifications.error(
|
||||
`App locked by ${app.lockedBy.email}. Please allow lock to expire or have them unlock this app.`
|
||||
)
|
||||
return
|
||||
}
|
||||
$goto(`../../app/${app.devId}`)
|
||||
}
|
||||
|
||||
function createAppFromTemplateUrl(templateKey) {
|
||||
// validate the template key just to make sure
|
||||
const templateParts = templateKey.split("/")
|
||||
|
@ -309,7 +295,7 @@
|
|||
|
||||
<div class="app-table">
|
||||
{#each filteredApps as app (app.appId)}
|
||||
<AppRow {app} {editApp} {appOverview} />
|
||||
<AppRow {app} />
|
||||
{/each}
|
||||
</div>
|
||||
</Layout>
|
||||
|
@ -399,7 +385,7 @@
|
|||
display: none;
|
||||
}
|
||||
.app-actions > :global(*) {
|
||||
flex: 0 0 50%;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
import { url, isActive, params, goto } from "@roxi/routify"
|
||||
import { url, isActive, goto } from "@roxi/routify"
|
||||
import {
|
||||
Page,
|
||||
Layout,
|
||||
|
@ -20,7 +20,7 @@
|
|||
Breadcrumb,
|
||||
Header,
|
||||
} from "components/portal/page"
|
||||
import { apps, auth, groups, overview } from "stores/portal"
|
||||
import { apps, auth, overview } from "stores/portal"
|
||||
import { AppStatus } from "constants"
|
||||
import analytics, { Events, EventSource } from "analytics"
|
||||
import { store } from "builderStore"
|
||||
|
@ -29,7 +29,7 @@
|
|||
import { API } from "api"
|
||||
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
||||
import ExportAppModal from "components/start/ExportAppModal.svelte"
|
||||
import { syncURLToState } from "../../../../../helpers/urlStateSync"
|
||||
import { syncURLToState } from "helpers/urlStateSync"
|
||||
import * as routify from "@roxi/routify"
|
||||
import { onDestroy } from "svelte"
|
||||
|
||||
|
|
|
@ -36,7 +36,7 @@
|
|||
},
|
||||
role: {
|
||||
displayName: "Access",
|
||||
width: "150px",
|
||||
width: "160px",
|
||||
borderLeft: true,
|
||||
},
|
||||
}
|
||||
|
|
|
@ -27,7 +27,6 @@
|
|||
|
||||
let pageInfo = createPaginationStore()
|
||||
let runHistory = null
|
||||
let showPanel = false
|
||||
let selectedHistory = null
|
||||
let automationOptions = []
|
||||
let automationId = null
|
||||
|
@ -155,47 +154,47 @@
|
|||
</Layout>
|
||||
<Divider />
|
||||
|
||||
<div class="search">
|
||||
<div class="select">
|
||||
<Select
|
||||
placeholder="All"
|
||||
label="Status"
|
||||
bind:value={status}
|
||||
options={statusOptions}
|
||||
/>
|
||||
</div>
|
||||
<div class="select">
|
||||
<Select
|
||||
placeholder="All"
|
||||
label="Automation"
|
||||
bind:value={automationId}
|
||||
options={automationOptions}
|
||||
/>
|
||||
</div>
|
||||
<div class="select">
|
||||
<Select
|
||||
placeholder="All"
|
||||
label="Date range"
|
||||
bind:value={timeRange}
|
||||
options={timeOptions}
|
||||
isOptionEnabled={x => {
|
||||
if (licensePlan?.type === Constants.PlanType.FREE) {
|
||||
return ["1-w", "30-d", "90-d"].indexOf(x.value) < 0
|
||||
} else if (licensePlan?.type === Constants.PlanType.TEAM) {
|
||||
return ["90-d"].indexOf(x.value) < 0
|
||||
} else if (licensePlan?.type === Constants.PlanType.PRO) {
|
||||
return ["30-d", "90-d"].indexOf(x.value) < 0
|
||||
}
|
||||
return true
|
||||
}}
|
||||
/>
|
||||
<div class="controls">
|
||||
<div class="search">
|
||||
<div class="select">
|
||||
<Select
|
||||
placeholder="All"
|
||||
label="Status"
|
||||
bind:value={status}
|
||||
options={statusOptions}
|
||||
/>
|
||||
</div>
|
||||
<div class="select">
|
||||
<Select
|
||||
placeholder="All"
|
||||
label="Automation"
|
||||
bind:value={automationId}
|
||||
options={automationOptions}
|
||||
/>
|
||||
</div>
|
||||
<div class="select">
|
||||
<Select
|
||||
placeholder="All"
|
||||
label="Date range"
|
||||
bind:value={timeRange}
|
||||
options={timeOptions}
|
||||
isOptionEnabled={x => {
|
||||
if (licensePlan?.type === Constants.PlanType.FREE) {
|
||||
return ["1-w", "30-d", "90-d"].indexOf(x.value) < 0
|
||||
} else if (licensePlan?.type === Constants.PlanType.TEAM) {
|
||||
return ["90-d"].indexOf(x.value) < 0
|
||||
} else if (licensePlan?.type === Constants.PlanType.PRO) {
|
||||
return ["30-d", "90-d"].indexOf(x.value) < 0
|
||||
}
|
||||
return true
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{#if (licensePlan?.type !== Constants.PlanType.ENTERPRISE && $auth.user.accountPortalAccess) || !$admin.cloud}
|
||||
<div class="pro-upgrade">
|
||||
<Button secondary on:click={$licensing.goToUpgradePage()}>
|
||||
Get more history
|
||||
</Button>
|
||||
</div>
|
||||
<Button secondary on:click={$licensing.goToUpgradePage()}>
|
||||
Get more history
|
||||
</Button>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
|
@ -236,14 +235,24 @@
|
|||
{/if}
|
||||
|
||||
<style>
|
||||
.controls {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: var(--spacing-xl);
|
||||
align-items: flex-end;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.search {
|
||||
display: flex;
|
||||
gap: var(--spacing-xl);
|
||||
width: 100%;
|
||||
align-items: flex-end;
|
||||
align-items: flex-start;
|
||||
flex: 1 0 auto;
|
||||
max-width: 100%;
|
||||
}
|
||||
.select {
|
||||
flex-basis: 150px;
|
||||
flex: 1 1 0;
|
||||
max-width: 150px;
|
||||
min-width: 80px;
|
||||
}
|
||||
.pagination {
|
||||
display: flex;
|
||||
|
@ -251,10 +260,4 @@
|
|||
justify-content: flex-end;
|
||||
margin-top: var(--spacing-xl);
|
||||
}
|
||||
.pro-upgrade {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
flex: 1;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -14,7 +14,6 @@
|
|||
Tags,
|
||||
Tag,
|
||||
Table,
|
||||
Page,
|
||||
} from "@budibase/bbui"
|
||||
import { backups, licensing, auth, admin, overview } from "stores/portal"
|
||||
import { createPaginationStore } from "helpers/pagination"
|
||||
|
@ -223,29 +222,30 @@
|
|||
</div>
|
||||
{:else if loaded}
|
||||
<Layout noPadding gap="M" alignContent="start">
|
||||
<div class="search">
|
||||
<div class="select">
|
||||
<Select
|
||||
placeholder="All"
|
||||
label="Type"
|
||||
options={filters}
|
||||
getOptionValue={filter => filter.value}
|
||||
getOptionLabel={filter => filter.label}
|
||||
bind:value={filterOpt}
|
||||
<div class="controls">
|
||||
<div class="search">
|
||||
<div class="select">
|
||||
<Select
|
||||
placeholder="All"
|
||||
label="Type"
|
||||
options={filters}
|
||||
getOptionValue={filter => filter.value}
|
||||
getOptionLabel={filter => filter.label}
|
||||
bind:value={filterOpt}
|
||||
/>
|
||||
</div>
|
||||
<DatePicker
|
||||
range={true}
|
||||
label="Date Range"
|
||||
on:change={e => {
|
||||
if (e.detail[0].length > 1) {
|
||||
startDate = e.detail[0][0].toISOString()
|
||||
endDate = e.detail[0][1].toISOString()
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<DatePicker
|
||||
range={true}
|
||||
label="Date Range"
|
||||
on:change={e => {
|
||||
if (e.detail[0].length > 1) {
|
||||
startDate = e.detail[0][0].toISOString()
|
||||
endDate = e.detail[0][1].toISOString()
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
<div class="split-buttons">
|
||||
<div>
|
||||
<ActionButton on:click={modal.show} icon="SaveAsFloppy">
|
||||
Create new backup
|
||||
</ActionButton>
|
||||
|
@ -291,15 +291,29 @@
|
|||
gap: var(--spacing-xl);
|
||||
}
|
||||
|
||||
.controls {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-end;
|
||||
gap: var(--spacing-xl);
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.search {
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
gap: var(--spacing-xl);
|
||||
width: 100%;
|
||||
align-items: flex-end;
|
||||
}
|
||||
.search :global(.spectrum-InputGroup) {
|
||||
min-width: 100px;
|
||||
}
|
||||
|
||||
.select {
|
||||
flex-basis: 160px;
|
||||
width: 0;
|
||||
min-width: 100px;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
|
@ -309,13 +323,6 @@
|
|||
margin-top: var(--spacing-xl);
|
||||
}
|
||||
|
||||
.split-buttons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.title {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
|
|
@ -16,15 +16,12 @@
|
|||
import clientPackage from "@budibase/client/package.json"
|
||||
import { processStringSync } from "@budibase/string-templates"
|
||||
import { users, auth, apps, groups, overview } from "stores/portal"
|
||||
import { createEventDispatcher } from "svelte"
|
||||
import { fetchData } from "@budibase/frontend-core"
|
||||
import { API } from "api"
|
||||
import GroupIcon from "../../users/groups/_components/GroupIcon.svelte"
|
||||
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
||||
import { checkIncomingDeploymentStatus } from "components/deploy/utils"
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
let appEditor
|
||||
let unpublishModal
|
||||
let deployments
|
||||
|
@ -180,7 +177,7 @@
|
|||
-
|
||||
<Link
|
||||
on:click={() => {
|
||||
$goto("../version")
|
||||
$goto("./version")
|
||||
}}
|
||||
>
|
||||
Update
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
const schema = {
|
||||
name: {
|
||||
width: "2fr",
|
||||
minWidth: "200px",
|
||||
},
|
||||
version: {
|
||||
width: "1fr",
|
||||
|
@ -28,6 +29,7 @@
|
|||
width: "1fr",
|
||||
displayName: "Type",
|
||||
capitalise: true,
|
||||
minWidth: "120px",
|
||||
},
|
||||
edit: {
|
||||
width: "auto",
|
||||
|
@ -119,8 +121,19 @@
|
|||
display: flex;
|
||||
gap: var(--spacing-xl);
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.controls :global(.spectrum-Search) {
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.filters {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
.controls :global(.spectrum-Search) {
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -2,16 +2,14 @@
|
|||
import { onMount, tick } from "svelte"
|
||||
import {
|
||||
Button,
|
||||
Detail,
|
||||
Heading,
|
||||
ActionButton,
|
||||
Body,
|
||||
Layout,
|
||||
notifications,
|
||||
Tabs,
|
||||
Tab,
|
||||
} from "@budibase/bbui"
|
||||
import { goto, url } from "@roxi/routify"
|
||||
import { url } from "@roxi/routify"
|
||||
import { email } from "stores/portal"
|
||||
import Editor from "components/integration/QueryEditor.svelte"
|
||||
import TemplateBindings from "./_components/TemplateBindings.svelte"
|
||||
|
|
|
@ -110,12 +110,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
const getRoleLabel = appId => {
|
||||
const roleId = group?.roles?.[apps.getProdAppID(appId)]
|
||||
const role = $roles.find(x => x._id === roleId)
|
||||
return role?.name || "Custom role"
|
||||
}
|
||||
|
||||
async function deleteGroup() {
|
||||
try {
|
||||
await groups.actions.delete(group)
|
||||
|
|
|
@ -40,7 +40,7 @@
|
|||
]
|
||||
|
||||
$: schema = {
|
||||
name: { displayName: "Group", width: "2fr" },
|
||||
name: { displayName: "Group", width: "2fr", minWidth: "200px" },
|
||||
users: { sortable: false, width: "1fr" },
|
||||
roles: { sortable: false, displayName: "Apps", width: "1fr" },
|
||||
}
|
||||
|
|
|
@ -8,8 +8,6 @@
|
|||
Heading,
|
||||
Body,
|
||||
Label,
|
||||
List,
|
||||
ListItem,
|
||||
Icon,
|
||||
Input,
|
||||
MenuItem,
|
||||
|
|
|
@ -57,6 +57,7 @@
|
|||
email: {
|
||||
sortable: false,
|
||||
width: "2fr",
|
||||
minWidth: "200px",
|
||||
},
|
||||
role: {
|
||||
sortable: false,
|
||||
|
@ -296,6 +297,8 @@
|
|||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--spacing-xl);
|
||||
}
|
||||
|
||||
.controls-right {
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
export let title = ""
|
||||
export let favicon = ""
|
||||
export let metaImage = ""
|
||||
export let url = ""
|
||||
|
||||
export let clientLibPath
|
||||
export let usedPlugins
|
||||
|
|
Loading…
Reference in a new issue