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