Merge master.
This commit is contained in:
commit
be52250fb4
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"$schema": "node_modules/lerna/schemas/lerna-schema.json",
|
||||
"version": "3.5.2",
|
||||
"version": "3.5.3",
|
||||
"npmClient": "yarn",
|
||||
"concurrency": 20,
|
||||
"command": {
|
||||
|
|
|
@ -147,9 +147,7 @@ export class FlagSet<T extends { [name: string]: boolean }> {
|
|||
|
||||
for (const [name, value] of Object.entries(posthogFlags)) {
|
||||
if (!this.isFlagName(name)) {
|
||||
// We don't want an unexpected PostHog flag to break the app, so we
|
||||
// just log it and continue.
|
||||
console.warn(`Unexpected posthog flag "${name}": ${value}`)
|
||||
// We don't want an unexpected PostHog flag to break the app
|
||||
continue
|
||||
}
|
||||
|
||||
|
|
|
@ -5,10 +5,10 @@
|
|||
|
||||
export let disabled = false
|
||||
export let align = "left"
|
||||
export let portalTarget
|
||||
export let portalTarget = undefined
|
||||
export let openOnHover = false
|
||||
export let animate
|
||||
export let offset
|
||||
export let animate = true
|
||||
export let offset = undefined
|
||||
|
||||
const actionMenuContext = getContext("actionMenu")
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
export let url = ""
|
||||
export let disabled = false
|
||||
export let initials = "JD"
|
||||
export let color = null
|
||||
export let color = ""
|
||||
|
||||
const DefaultColor = "#3aab87"
|
||||
|
||||
|
|
|
@ -28,23 +28,7 @@
|
|||
const dispatch = createEventDispatcher()
|
||||
const categories = [
|
||||
{
|
||||
label: "Theme",
|
||||
colors: [
|
||||
"gray-50",
|
||||
"gray-75",
|
||||
"gray-100",
|
||||
"gray-200",
|
||||
"gray-300",
|
||||
"gray-400",
|
||||
"gray-500",
|
||||
"gray-600",
|
||||
"gray-700",
|
||||
"gray-800",
|
||||
"gray-900",
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Colors",
|
||||
label: "Theme colors",
|
||||
colors: [
|
||||
"red-100",
|
||||
"orange-100",
|
||||
|
@ -91,6 +75,49 @@
|
|||
"indigo-700",
|
||||
"magenta-700",
|
||||
|
||||
"gray-50",
|
||||
"gray-75",
|
||||
"gray-100",
|
||||
"gray-200",
|
||||
"gray-300",
|
||||
"gray-400",
|
||||
"gray-500",
|
||||
"gray-600",
|
||||
"gray-700",
|
||||
"gray-800",
|
||||
"gray-900",
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Static colors",
|
||||
colors: [
|
||||
"static-red-400",
|
||||
"static-orange-400",
|
||||
"static-yellow-400",
|
||||
"static-green-400",
|
||||
"static-seafoam-400",
|
||||
"static-blue-400",
|
||||
"static-indigo-400",
|
||||
"static-magenta-400",
|
||||
|
||||
"static-red-800",
|
||||
"static-orange-800",
|
||||
"static-yellow-800",
|
||||
"static-green-800",
|
||||
"static-seafoam-800",
|
||||
"static-blue-800",
|
||||
"static-indigo-800",
|
||||
"static-magenta-800",
|
||||
|
||||
"static-red-1200",
|
||||
"static-orange-1200",
|
||||
"static-yellow-1200",
|
||||
"static-green-1200",
|
||||
"static-seafoam-1200",
|
||||
"static-blue-1200",
|
||||
"static-indigo-1200",
|
||||
"static-magenta-1200",
|
||||
|
||||
"static-white",
|
||||
"static-black",
|
||||
],
|
||||
|
@ -137,10 +164,13 @@
|
|||
: "var(--spectrum-global-color-gray-50)"
|
||||
}
|
||||
|
||||
// Use contrasating check for the dim colours
|
||||
// Use contrasting check for the dim colours
|
||||
if (value?.includes("-100")) {
|
||||
return "var(--spectrum-global-color-gray-900)"
|
||||
}
|
||||
if (value?.includes("-1200") || value?.includes("-800")) {
|
||||
return "var(--spectrum-global-color-static-gray-50)"
|
||||
}
|
||||
|
||||
// Use black check for static white
|
||||
if (value?.includes("static-black")) {
|
||||
|
@ -169,7 +199,7 @@
|
|||
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<Popover bind:this={dropdown} anchor={preview} maxHeight={320} {offset} {align}>
|
||||
<Popover bind:this={dropdown} anchor={preview} maxHeight={350} {offset} {align}>
|
||||
<Layout paddingX="XL" paddingY="L">
|
||||
<div class="container">
|
||||
{#each categories as category}
|
||||
|
|
|
@ -1,52 +1,71 @@
|
|||
<script lang="ts">
|
||||
import "@spectrum-css/textfield/dist/index-vars.css"
|
||||
import { createEventDispatcher } from "svelte"
|
||||
import type { FocusEventHandler } from "svelte/elements"
|
||||
|
||||
export let value: string | null = null
|
||||
export let placeholder: string | null = null
|
||||
export let disabled = false
|
||||
export let readonly = false
|
||||
export let id: string | null = null
|
||||
export let height: number | null = null
|
||||
export let minHeight: number | null = null
|
||||
export let value: string | undefined = ""
|
||||
export let placeholder: string | undefined = undefined
|
||||
export let disabled: boolean = false
|
||||
export let readonly: boolean = false
|
||||
export let id: string | undefined = undefined
|
||||
export let height: string | number | undefined = undefined
|
||||
export let minHeight: string | number | undefined = undefined
|
||||
export let align = null
|
||||
export let updateOnChange: boolean = false
|
||||
|
||||
let isFocused = false
|
||||
export const getCaretPosition = () => ({
|
||||
start: textarea.selectionStart,
|
||||
end: textarea.selectionEnd,
|
||||
})
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
let focus = false
|
||||
let textarea: HTMLTextAreaElement
|
||||
const dispatch = createEventDispatcher<{ change: string }>()
|
||||
const onChange: FocusEventHandler<HTMLTextAreaElement> = event => {
|
||||
dispatch("change", event.currentTarget.value)
|
||||
isFocused = false
|
||||
let scrollable = false
|
||||
|
||||
$: heightString = getStyleString("height", height)
|
||||
$: minHeightString = getStyleString("min-height", minHeight)
|
||||
$: dispatch("scrollable", scrollable)
|
||||
|
||||
const onBlur = () => {
|
||||
focus = false
|
||||
updateValue()
|
||||
}
|
||||
|
||||
export function focus() {
|
||||
textarea.focus()
|
||||
const onChange = () => {
|
||||
scrollable = textarea.clientHeight < textarea.scrollHeight
|
||||
if (!updateOnChange) {
|
||||
return
|
||||
}
|
||||
updateValue()
|
||||
}
|
||||
|
||||
export function contents() {
|
||||
return textarea.value
|
||||
const updateValue = () => {
|
||||
if (readonly || disabled) {
|
||||
return
|
||||
}
|
||||
dispatch("change", textarea.value)
|
||||
}
|
||||
|
||||
const getStyleString = (attribute: string, value: number | null) => {
|
||||
if (!attribute || value == null) {
|
||||
const getStyleString = (
|
||||
attribute: string,
|
||||
value: string | number | undefined
|
||||
) => {
|
||||
if (value == null) {
|
||||
return ""
|
||||
}
|
||||
if (typeof value === "number" && isNaN(value)) {
|
||||
if (typeof value !== "number" || isNaN(value)) {
|
||||
return `${attribute}:${value};`
|
||||
}
|
||||
return `${attribute}:${value}px;`
|
||||
}
|
||||
|
||||
$: heightString = getStyleString("height", height)
|
||||
$: minHeightString = getStyleString("min-height", minHeight)
|
||||
</script>
|
||||
|
||||
<div
|
||||
style={`${heightString}${minHeightString}`}
|
||||
class="spectrum-Textfield spectrum-Textfield--multiline"
|
||||
class:is-disabled={disabled}
|
||||
class:is-focused={isFocused}
|
||||
class:is-focused={focus}
|
||||
>
|
||||
<!-- prettier-ignore -->
|
||||
<textarea
|
||||
|
@ -57,8 +76,10 @@
|
|||
{disabled}
|
||||
{readonly}
|
||||
{id}
|
||||
on:focus={() => (isFocused = true)}
|
||||
on:blur={onChange}
|
||||
on:input={onChange}
|
||||
on:focus={() => (focus = true)}
|
||||
on:blur={onBlur}
|
||||
on:blur
|
||||
on:keypress
|
||||
>{value || ""}</textarea>
|
||||
</div>
|
||||
|
|
|
@ -1,132 +0,0 @@
|
|||
<script>
|
||||
import Field from "./Field.svelte"
|
||||
import PickerDropdown from "./Core/PickerDropdown.svelte"
|
||||
import { createEventDispatcher } from "svelte"
|
||||
|
||||
export let primaryValue = null
|
||||
export let secondaryValue = null
|
||||
export let inputType = "text"
|
||||
export let label = null
|
||||
export let labelPosition = "above"
|
||||
export let secondaryPlaceholder = null
|
||||
export let autocomplete
|
||||
export let placeholder = null
|
||||
export let disabled = false
|
||||
export let readonly = false
|
||||
export let error = null
|
||||
export let getSecondaryOptionLabel = option =>
|
||||
extractProperty(option, "label")
|
||||
export let getSecondaryOptionValue = option =>
|
||||
extractProperty(option, "value")
|
||||
export let getSecondaryOptionColour = () => {}
|
||||
export let getSecondaryOptionIcon = () => {}
|
||||
export let quiet = false
|
||||
export let autofocus
|
||||
export let primaryOptions = []
|
||||
export let secondaryOptions = []
|
||||
export let searchTerm
|
||||
export let showClearIcon = true
|
||||
export let helpText = null
|
||||
|
||||
let primaryLabel
|
||||
let secondaryLabel
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
$: secondaryFieldText = getSecondaryFieldText(
|
||||
secondaryValue,
|
||||
secondaryOptions,
|
||||
secondaryPlaceholder
|
||||
)
|
||||
$: secondaryFieldIcon = getSecondaryFieldAttribute(
|
||||
getSecondaryOptionIcon,
|
||||
secondaryValue,
|
||||
secondaryOptions
|
||||
)
|
||||
$: secondaryFieldColour = getSecondaryFieldAttribute(
|
||||
getSecondaryOptionColour,
|
||||
secondaryValue,
|
||||
secondaryOptions
|
||||
)
|
||||
|
||||
const getSecondaryFieldAttribute = (getAttribute, value, options) => {
|
||||
// Wait for options to load if there is a value but no options
|
||||
|
||||
if (!options?.length) {
|
||||
return ""
|
||||
}
|
||||
|
||||
const index = options.findIndex(
|
||||
(option, idx) => getSecondaryOptionValue(option, idx) === value
|
||||
)
|
||||
|
||||
return index !== -1 ? getAttribute(options[index], index) : null
|
||||
}
|
||||
|
||||
const getSecondaryFieldText = (value, options, placeholder) => {
|
||||
// Always use placeholder if no value
|
||||
if (value == null || value === "") {
|
||||
return placeholder || "Choose an option"
|
||||
}
|
||||
|
||||
return getSecondaryFieldAttribute(getSecondaryOptionLabel, value, options)
|
||||
}
|
||||
|
||||
const onPickPrimary = e => {
|
||||
primaryLabel = e?.detail?.label || null
|
||||
primaryValue = e?.detail?.value || null
|
||||
dispatch("pickprimary", e?.detail?.value || {})
|
||||
}
|
||||
|
||||
const onPickSecondary = e => {
|
||||
secondaryValue = e.detail
|
||||
dispatch("picksecondary", e.detail)
|
||||
}
|
||||
|
||||
const extractProperty = (value, property) => {
|
||||
if (value && typeof value === "object") {
|
||||
return value[property]
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
const updateSearchTerm = e => {
|
||||
searchTerm = e.detail
|
||||
}
|
||||
</script>
|
||||
|
||||
<Field {helpText} {label} {labelPosition} {error}>
|
||||
<PickerDropdown
|
||||
{searchTerm}
|
||||
{autocomplete}
|
||||
{error}
|
||||
{disabled}
|
||||
{readonly}
|
||||
{placeholder}
|
||||
{inputType}
|
||||
{quiet}
|
||||
{autofocus}
|
||||
{primaryOptions}
|
||||
{secondaryOptions}
|
||||
{getSecondaryOptionLabel}
|
||||
{getSecondaryOptionValue}
|
||||
{getSecondaryOptionIcon}
|
||||
{getSecondaryOptionColour}
|
||||
{secondaryFieldText}
|
||||
{secondaryFieldIcon}
|
||||
{secondaryFieldColour}
|
||||
{primaryValue}
|
||||
{secondaryValue}
|
||||
{primaryLabel}
|
||||
{secondaryLabel}
|
||||
{showClearIcon}
|
||||
on:pickprimary={onPickPrimary}
|
||||
on:picksecondary={onPickSecondary}
|
||||
on:search={updateSearchTerm}
|
||||
on:click
|
||||
on:input
|
||||
on:blur
|
||||
on:focus
|
||||
on:keyup
|
||||
on:closed
|
||||
/>
|
||||
</Field>
|
|
@ -1,16 +1,15 @@
|
|||
<script>
|
||||
<script lang="ts">
|
||||
import Icon from "./Icon.svelte"
|
||||
|
||||
import Tooltip from "../Tooltip/Tooltip.svelte"
|
||||
import { fade } from "svelte/transition"
|
||||
|
||||
export let icon
|
||||
export let background
|
||||
export let color
|
||||
export let size = "M"
|
||||
export let tooltip
|
||||
export let icon: string | undefined = undefined
|
||||
export let background: string | undefined = undefined
|
||||
export let color: string | undefined = undefined
|
||||
export let size: "XS" | "S" | "M" | "L" = "M"
|
||||
export let tooltip: string | undefined = undefined
|
||||
|
||||
let showTooltip = false
|
||||
let showTooltip: boolean = false
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
|
|
|
@ -1,19 +1,24 @@
|
|||
<script>
|
||||
<script lang="ts">
|
||||
import "@spectrum-css/popover/dist/index-vars.css"
|
||||
import clickOutside from "../Actions/click_outside"
|
||||
import { fly } from "svelte/transition"
|
||||
import Icon from "../Icon/Icon.svelte"
|
||||
import { createEventDispatcher } from "svelte"
|
||||
|
||||
export let value
|
||||
export let size = "M"
|
||||
export let alignRight = false
|
||||
export let value: string | undefined
|
||||
export let size: "S" | "M" | "L" = "M"
|
||||
export let alignRight: boolean = false
|
||||
|
||||
let open = false
|
||||
let open: boolean = false
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
const iconList = [
|
||||
interface IconCategory {
|
||||
label: string
|
||||
icons: string[]
|
||||
}
|
||||
|
||||
const iconList: IconCategory[] = [
|
||||
{
|
||||
label: "Icons",
|
||||
icons: [
|
||||
|
@ -45,12 +50,12 @@
|
|||
},
|
||||
]
|
||||
|
||||
const onChange = value => {
|
||||
const onChange = (value: string) => {
|
||||
dispatch("change", value)
|
||||
open = false
|
||||
}
|
||||
|
||||
const handleOutsideClick = event => {
|
||||
const handleOutsideClick = (event: MouseEvent) => {
|
||||
if (open) {
|
||||
event.stopPropagation()
|
||||
open = false
|
||||
|
@ -77,11 +82,11 @@
|
|||
class="spectrum-Popover spectrum-Popover--bottom spectrum-Picker-popover is-open"
|
||||
class:spectrum-Popover--align-right={alignRight}
|
||||
>
|
||||
{#each iconList as icon}
|
||||
{#each iconList as iconList}
|
||||
<div class="category">
|
||||
<div class="heading">{icon.label}</div>
|
||||
<div class="heading">{iconList.label}</div>
|
||||
<div class="icons">
|
||||
{#each icon.icons as icon}
|
||||
{#each iconList.icons as icon}
|
||||
<div
|
||||
on:click={() => {
|
||||
onChange(icon)
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
<div class="icon-side-nav">
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.icon-side-nav {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
padding: var(--spacing-s);
|
||||
gap: var(--spacing-xs);
|
||||
}
|
||||
</style>
|
|
@ -1,58 +0,0 @@
|
|||
<script>
|
||||
import Icon from "../Icon/Icon.svelte"
|
||||
import Tooltip from "../Tooltip/Tooltip.svelte"
|
||||
import { fade } from "svelte/transition"
|
||||
|
||||
export let icon
|
||||
export let active = false
|
||||
export let tooltip
|
||||
|
||||
let showTooltip = false
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div
|
||||
class="icon-side-nav-item"
|
||||
class:active
|
||||
on:mouseover={() => (showTooltip = true)}
|
||||
on:focus={() => (showTooltip = true)}
|
||||
on:mouseleave={() => (showTooltip = false)}
|
||||
on:click
|
||||
>
|
||||
<Icon name={icon} hoverable />
|
||||
{#if tooltip && showTooltip}
|
||||
<div class="tooltip" in:fade={{ duration: 130, delay: 250 }}>
|
||||
<Tooltip textWrapping direction="right" text={tooltip} />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.icon-side-nav-item {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
border-radius: 4px;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
transition: background 130ms ease-out;
|
||||
}
|
||||
.icon-side-nav-item:hover :global(svg),
|
||||
.active :global(svg) {
|
||||
color: var(--spectrum-global-color-gray-900);
|
||||
}
|
||||
.active {
|
||||
background: var(--spectrum-global-color-gray-300);
|
||||
}
|
||||
.tooltip {
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
left: calc(100% - 4px);
|
||||
top: 50%;
|
||||
white-space: nowrap;
|
||||
transform: translateY(-50%);
|
||||
z-index: 1;
|
||||
}
|
||||
</style>
|
|
@ -1,22 +1,22 @@
|
|||
<script>
|
||||
<script lang="ts">
|
||||
import "@spectrum-css/inlinealert/dist/index-vars.css"
|
||||
import Button from "../Button/Button.svelte"
|
||||
import Icon from "../Icon/Icon.svelte"
|
||||
|
||||
export let type = "info"
|
||||
export let header = ""
|
||||
export let message = ""
|
||||
export let onConfirm = undefined
|
||||
export let buttonText = ""
|
||||
export let cta = false
|
||||
export let link = ""
|
||||
export let linkText = ""
|
||||
export let type: "info" | "error" | "success" | "help" | "negative" = "info"
|
||||
export let header: string = ""
|
||||
export let message: string = ""
|
||||
export let onConfirm: (() => void) | undefined = undefined
|
||||
export let buttonText: string = ""
|
||||
export let cta: boolean = false
|
||||
export let link: string = ""
|
||||
export let linkText: string = ""
|
||||
|
||||
$: icon = selectIcon(type)
|
||||
// if newlines used, convert them to different elements
|
||||
$: split = message.split("\n")
|
||||
|
||||
function selectIcon(alertType) {
|
||||
function selectIcon(alertType: string): string {
|
||||
switch (alertType) {
|
||||
case "error":
|
||||
case "negative":
|
||||
|
|
|
@ -1,19 +1,21 @@
|
|||
<script>
|
||||
<script lang="ts">
|
||||
import Input from "../Form/Input.svelte"
|
||||
import Icon from "../Icon/Icon.svelte"
|
||||
import { notifications } from "../Stores/notifications"
|
||||
|
||||
export let label = null
|
||||
export let value
|
||||
export let label: string | undefined = undefined
|
||||
export let value: string | undefined = undefined
|
||||
|
||||
const copyToClipboard = val => {
|
||||
const dummy = document.createElement("textarea")
|
||||
document.body.appendChild(dummy)
|
||||
dummy.value = val
|
||||
dummy.select()
|
||||
document.execCommand("copy")
|
||||
document.body.removeChild(dummy)
|
||||
notifications.success(`Copied to clipboard`)
|
||||
const copyToClipboard = (val: string | undefined) => {
|
||||
if (val) {
|
||||
const dummy = document.createElement("textarea")
|
||||
document.body.appendChild(dummy)
|
||||
dummy.value = val
|
||||
dummy.select()
|
||||
document.execCommand("copy")
|
||||
document.body.removeChild(dummy)
|
||||
notifications.success(`Copied to clipboard`)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
<script>
|
||||
<script lang="ts">
|
||||
import "@spectrum-css/fieldlabel/dist/index-vars.css"
|
||||
import TooltipWrapper from "../Tooltip/TooltipWrapper.svelte"
|
||||
|
||||
export let size = "M"
|
||||
export let tooltip = ""
|
||||
export let muted = undefined
|
||||
export let size: "S" | "M" | "L" = "M"
|
||||
export let tooltip: string = ""
|
||||
export let muted: boolean | undefined = undefined
|
||||
</script>
|
||||
|
||||
<TooltipWrapper {tooltip} {size}>
|
||||
|
|
|
@ -1,12 +1,17 @@
|
|||
<script>
|
||||
export let horizontal = false
|
||||
export let paddingX = "M"
|
||||
export let paddingY = "M"
|
||||
export let noPadding = false
|
||||
export let gap = "M"
|
||||
export let noGap = false
|
||||
export let alignContent = "normal"
|
||||
export let justifyItems = "stretch"
|
||||
<script lang="ts">
|
||||
export let horizontal: boolean = false
|
||||
export let paddingX: "S" | "M" | "L" | "XL" | "XXL" = "M"
|
||||
export let paddingY: "S" | "M" | "L" | "XL" | "XXL" = "M"
|
||||
export let noPadding: boolean = false
|
||||
export let gap: "XXS" | "XS" | "S" | "M" | "L" | "XL" = "M"
|
||||
export let noGap: boolean = false
|
||||
export let alignContent:
|
||||
| "start"
|
||||
| "center"
|
||||
| "space-between"
|
||||
| "space-around"
|
||||
| "normal" = "normal"
|
||||
export let justifyItems: "stretch" | "start" | "center" | "end" = "stretch"
|
||||
</script>
|
||||
|
||||
<div
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
<script>
|
||||
<script lang="ts">
|
||||
import { setContext } from "svelte"
|
||||
import clickOutside from "../Actions/click_outside"
|
||||
|
||||
export let wide = false
|
||||
export let narrow = false
|
||||
export let narrower = false
|
||||
export let noPadding = false
|
||||
export let wide: boolean = false
|
||||
export let narrow: boolean = false
|
||||
export let narrower: boolean = false
|
||||
export let noPadding: boolean = false
|
||||
|
||||
let sidePanelVisible = false
|
||||
let sidePanelVisible: boolean = false
|
||||
|
||||
setContext("side-panel", {
|
||||
open: () => (sidePanelVisible = true),
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<script>
|
||||
<script lang="ts">
|
||||
import Detail from "../Typography/Detail.svelte"
|
||||
|
||||
export let title = null
|
||||
export let title: string | null = null
|
||||
</script>
|
||||
|
||||
<div>
|
||||
|
|
|
@ -1,20 +1,20 @@
|
|||
<script>
|
||||
<script lang="ts">
|
||||
import SpectrumMDE from "./SpectrumMDE.svelte"
|
||||
import { createEventDispatcher } from "svelte"
|
||||
|
||||
export let value = null
|
||||
export let height = null
|
||||
export let placeholder = null
|
||||
export let id = null
|
||||
export let fullScreenOffset = 0
|
||||
export let disabled = false
|
||||
export let readonly = false
|
||||
export let easyMDEOptions
|
||||
export let value: string | null = null
|
||||
export let height: string | null = null
|
||||
export let placeholder: string | null = null
|
||||
export let id: string | null = null
|
||||
export let fullScreenOffset: { x: string; y: string } | null = null
|
||||
export let disabled: boolean = false
|
||||
export let readonly: boolean = false
|
||||
export let easyMDEOptions: Record<string, any> = {}
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
let latestValue
|
||||
let mde
|
||||
let latestValue: string | null
|
||||
let mde: any
|
||||
|
||||
// Ensure the value is updated if the value prop changes outside the editor's
|
||||
// control
|
||||
|
@ -24,7 +24,7 @@
|
|||
mde?.togglePreview()
|
||||
}
|
||||
|
||||
const checkValue = val => {
|
||||
const checkValue = (val: string | null) => {
|
||||
if (mde && val !== latestValue) {
|
||||
mde.value(val)
|
||||
}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
<script>
|
||||
<script lang="ts">
|
||||
import SpectrumMDE from "./SpectrumMDE.svelte"
|
||||
|
||||
export let value
|
||||
export let height
|
||||
export let value: string | undefined = undefined
|
||||
export let height: string | undefined = undefined
|
||||
|
||||
let mde
|
||||
let mde: any
|
||||
|
||||
// Keep the value up to date
|
||||
$: mde && mde.value(value || "")
|
||||
|
@ -40,6 +40,7 @@
|
|||
border: none;
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
color: inherit;
|
||||
}
|
||||
.markdown-viewer :global(.EasyMDEContainer) {
|
||||
background: transparent;
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
<script>
|
||||
<script lang="ts">
|
||||
import EasyMDE from "easymde"
|
||||
import "easymde/dist/easymde.min.css"
|
||||
import { onMount } from "svelte"
|
||||
|
||||
export let height = null
|
||||
export let scroll = true
|
||||
export let easyMDEOptions = null
|
||||
export let mde = null
|
||||
export let id = null
|
||||
export let fullScreenOffset = null
|
||||
export let disabled = false
|
||||
export let height: string | null = null
|
||||
export let scroll: boolean = true
|
||||
export let easyMDEOptions: Record<string, any> | null = null
|
||||
export let mde: EasyMDE | null = null
|
||||
export let id: string | null = null
|
||||
export let fullScreenOffset: { x: string; y: string } | null = null
|
||||
export let disabled: boolean = false
|
||||
|
||||
let element
|
||||
let element: HTMLTextAreaElement | undefined = undefined
|
||||
|
||||
onMount(() => {
|
||||
height = height || "200px"
|
||||
|
@ -27,13 +27,13 @@
|
|||
|
||||
// Revert the editor when we unmount
|
||||
return () => {
|
||||
mde.toTextArea()
|
||||
mde?.toTextArea()
|
||||
}
|
||||
})
|
||||
|
||||
$: styleString = getStyleString(fullScreenOffset)
|
||||
|
||||
const getStyleString = offset => {
|
||||
const getStyleString = (offset: { x?: string; y?: string } | null) => {
|
||||
let string = ""
|
||||
string += `--fullscreen-offset-x:${offset?.x || "0px"};`
|
||||
string += `--fullscreen-offset-y:${offset?.y || "0px"};`
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
<script>
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher, getContext } from "svelte"
|
||||
import Icon from "../Icon/Icon.svelte"
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const actionMenu = getContext("actionMenu")
|
||||
const actionMenu = getContext("actionMenu") as { hideAll: () => void }
|
||||
|
||||
export let icon = undefined
|
||||
export let disabled = undefined
|
||||
export let noClose = false
|
||||
export let keyBind = undefined
|
||||
export let icon: string | undefined = undefined
|
||||
export let disabled: boolean | undefined = undefined
|
||||
export let noClose: boolean = false
|
||||
export let keyBind: string | undefined = undefined
|
||||
|
||||
$: keys = getKeys(keyBind)
|
||||
|
||||
const getKeys = keyBind => {
|
||||
const getKeys = (keyBind: string | undefined): string[] => {
|
||||
let keys = keyBind?.split("+") || []
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
if (
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<script>
|
||||
<script lang="ts">
|
||||
import "@spectrum-css/menu/dist/index-vars.css"
|
||||
</script>
|
||||
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
<script>
|
||||
import Menu from './Menu.svelte'
|
||||
import Separator from './Separator.svelte'
|
||||
import Section from './Section.svelte'
|
||||
import Item from './Item.svelte'
|
||||
</script>
|
||||
|
||||
<Menu>
|
||||
<Section heading="Section heading">
|
||||
<Item>Some Item 1</Item>
|
||||
<Item>Some Item 2</Item>
|
||||
<Item>Some Item 3</Item>
|
||||
</Section>
|
||||
<Separator />
|
||||
<Section heading="Section heading">
|
||||
<Item icon="SaveFloppy">Save</Item>
|
||||
<Item disabled icon="DataDownload">Download</Item>
|
||||
</Section>
|
||||
</Menu>
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
export let heading
|
||||
<script lang="ts">
|
||||
export let heading: string
|
||||
</script>
|
||||
|
||||
<li role="presentation">
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<script>
|
||||
<script lang="ts">
|
||||
import Input from "../Form/Input.svelte"
|
||||
|
||||
let value = ""
|
||||
let value: string = ""
|
||||
</script>
|
||||
|
||||
<Input label="Your Name" bind:value />
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
<script>
|
||||
<script lang="ts">
|
||||
import { getContext } from "svelte"
|
||||
import Context from "../context"
|
||||
|
||||
const { hide } = getContext(Context.Modal)
|
||||
const { hide } = getContext(Context.Modal) as { hide: () => void }
|
||||
|
||||
let count = 0
|
||||
const clicks = 5
|
||||
let count: number = 0
|
||||
const clicks: number = 5
|
||||
$: if (count === clicks) hide()
|
||||
$: remaining = clicks - count
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<script>
|
||||
<script lang="ts">
|
||||
import "@spectrum-css/modal/dist/index-vars.css"
|
||||
import "@spectrum-css/underlay/dist/index-vars.css"
|
||||
import { createEventDispatcher, setContext, tick, onMount } from "svelte"
|
||||
|
@ -6,33 +6,37 @@
|
|||
import Portal from "svelte-portal"
|
||||
import Context from "../context"
|
||||
|
||||
export let fixed = false
|
||||
export let inline = false
|
||||
export let disableCancel = false
|
||||
export let autoFocus = true
|
||||
export let zIndex = 1001
|
||||
export let fixed: boolean = false
|
||||
export let inline: boolean = false
|
||||
export let disableCancel: boolean = false
|
||||
export let autoFocus: boolean = true
|
||||
export let zIndex: number = 1001
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
let visible = fixed || inline
|
||||
let modal
|
||||
const dispatch = createEventDispatcher<{
|
||||
show: void
|
||||
hide: void
|
||||
cancel: void
|
||||
}>()
|
||||
let visible: boolean = fixed || inline
|
||||
let modal: HTMLElement | undefined
|
||||
|
||||
$: dispatch(visible ? "show" : "hide")
|
||||
|
||||
export function show() {
|
||||
export function show(): void {
|
||||
if (visible) {
|
||||
return
|
||||
}
|
||||
visible = true
|
||||
}
|
||||
|
||||
export function hide() {
|
||||
export function hide(): void {
|
||||
if (!visible || fixed || inline) {
|
||||
return
|
||||
}
|
||||
visible = false
|
||||
}
|
||||
|
||||
export function toggle() {
|
||||
export function toggle(): void {
|
||||
if (visible) {
|
||||
hide()
|
||||
} else {
|
||||
|
@ -40,7 +44,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
export function cancel() {
|
||||
export function cancel(): void {
|
||||
if (!visible || disableCancel) {
|
||||
return
|
||||
}
|
||||
|
@ -48,34 +52,33 @@
|
|||
hide()
|
||||
}
|
||||
|
||||
function handleKey(e) {
|
||||
function handleKey(e: KeyboardEvent): void {
|
||||
if (visible && e.key === "Escape") {
|
||||
cancel()
|
||||
}
|
||||
}
|
||||
|
||||
async function focusModal(node) {
|
||||
if (!autoFocus) {
|
||||
return
|
||||
}
|
||||
await tick()
|
||||
|
||||
// Try to focus first input
|
||||
const inputs = node.querySelectorAll("input")
|
||||
if (inputs?.length) {
|
||||
inputs[0].focus()
|
||||
}
|
||||
|
||||
// Otherwise try to focus confirmation button
|
||||
else if (modal) {
|
||||
const confirm = modal.querySelector(".confirm-wrap .spectrum-Button")
|
||||
if (confirm) {
|
||||
confirm.focus()
|
||||
function focusModal(node: HTMLElement): void {
|
||||
if (!autoFocus) return
|
||||
tick().then(() => {
|
||||
const inputs = node.querySelectorAll("input")
|
||||
if (inputs?.length) {
|
||||
inputs[0].focus()
|
||||
} else if (modal) {
|
||||
const confirm = modal.querySelector(".confirm-wrap .spectrum-Button")
|
||||
if (confirm) {
|
||||
;(confirm as HTMLElement).focus()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
setContext(Context.Modal, { show, hide, toggle, cancel })
|
||||
setContext(Context.Modal, {
|
||||
show,
|
||||
hide,
|
||||
toggle,
|
||||
cancel,
|
||||
} as { show: () => void; hide: () => void; toggle: () => void; cancel: () => void })
|
||||
|
||||
onMount(() => {
|
||||
document.addEventListener("keydown", handleKey)
|
||||
|
|
|
@ -1,116 +0,0 @@
|
|||
<script>
|
||||
import { View } from "svench";
|
||||
import Modal from "./Modal.svelte";
|
||||
import ModalContent from "./ModalContent.svelte";
|
||||
import Button from "../Button/Button.svelte";
|
||||
import Content from "./Content.svelte";
|
||||
import QuizModal from "./QuizModal.svelte";
|
||||
import CustomContent from "./CustomContent.svelte";
|
||||
|
||||
let modal1
|
||||
let modal2
|
||||
let modal3
|
||||
|
||||
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))
|
||||
async function longTask() {
|
||||
await sleep(3000)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
p, span {
|
||||
font-size: var(--font-size-s);
|
||||
}
|
||||
|
||||
code {
|
||||
display: inline-block;
|
||||
box-sizing: border-box;
|
||||
padding: var(--spacing-xs) var(--spacing-s);
|
||||
background-color: var(--grey-2);
|
||||
color: var(--red-dark);
|
||||
border-radius: var(--spacing-xs);
|
||||
}
|
||||
</style>
|
||||
|
||||
<h3>Modals</h3>
|
||||
<p>
|
||||
Modals provide a means to render content in front of everything else on a page.
|
||||
</p>
|
||||
<p>
|
||||
The modal module in BBUI exposes two
|
||||
separate components to provide this functionality; a <code>Modal</code> component to control visibility of content,
|
||||
and a <code>ModalContent</code> component to quickly construct the typical content - although this is optional.
|
||||
</p>
|
||||
<p>
|
||||
One of the common problems with modals and popups is stale state reappearing after hiding and showing the content
|
||||
again, since the state hasn't been garbage collected if a component controls its own visibility. This is handled for
|
||||
you when using the <code>Modal</code> component as it will fully unmount child components, properly resetting state
|
||||
every time it appears.
|
||||
</p>
|
||||
|
||||
<br/>
|
||||
<p>Use ModalContent to render typical modal content.</p>
|
||||
<View name="Simple Confirmation Modal">
|
||||
<Button primary on:click={modal1.show}>Delete Record</Button>
|
||||
<Modal bind:this={modal1}>
|
||||
<ModalContent title="Confirm Deletion" confirmText="Delete">
|
||||
<span>Are you sure you want to delete this record?</span>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
</View>
|
||||
|
||||
<br/>
|
||||
<p>
|
||||
Width can be specified as a prop to a <code>Modal</code>. Any additional <code>ModalContent</code> props provided
|
||||
will be passed to the confirmation button.
|
||||
</p>
|
||||
<View name="Different Buttons and Width">
|
||||
<Button primary on:click={modal3.show}>Open Modal</Button>
|
||||
<Modal bind:this={modal3} width="250px">
|
||||
<ModalContent
|
||||
title="Confirmation Required"
|
||||
showCancelButton={false}
|
||||
showCloseIcon={false}
|
||||
confirmText="I'm sure!"
|
||||
green
|
||||
large
|
||||
wide
|
||||
>
|
||||
<span>Are you sure you want to do that?</span>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
</View>
|
||||
|
||||
<br/>
|
||||
<p>Any content can be rendered inside a <code>Modal</code>. Use context to close the modal from your own components.</p>
|
||||
<View name="Custom Content">
|
||||
<Button primary on:click={modal1.show}>Open Modal</Button>
|
||||
<Modal bind:this={modal1} padding={false} border={false}>
|
||||
<CustomContent/>
|
||||
</Modal>
|
||||
</View>
|
||||
|
||||
<br/>
|
||||
<p>Async functions passed in as the onConfirm prop will make the modal wait until the callback is completed.</p>
|
||||
<View name="Async Callbacks">
|
||||
<Button primary on:click={modal2.show}>Long Task</Button>
|
||||
<Modal bind:this={modal2}>
|
||||
<ModalContent
|
||||
title="Perform Long Task"
|
||||
confirmText="Submit"
|
||||
onConfirm={longTask}
|
||||
>
|
||||
<span>Pressing submit will wait 3 seconds before finishing and disable the confirm button until it's done.</span>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
</View>
|
||||
|
||||
<br/>
|
||||
<p>Returning false from a onConfirm callback will prevent the modal being closed.</p>
|
||||
<View name="Callback Failure Handling">
|
||||
<Button primary on:click={modal3.show}>Open Quiz</Button>
|
||||
<Modal bind:this={modal3}>
|
||||
<QuizModal />
|
||||
</Modal>
|
||||
</View>
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
<script context="module">
|
||||
<script context="module" lang="ts">
|
||||
export const keepOpen = Symbol("keepOpen")
|
||||
</script>
|
||||
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import "@spectrum-css/dialog/dist/index-vars.css"
|
||||
import { getContext } from "svelte"
|
||||
import Button from "../Button/Button.svelte"
|
||||
|
@ -11,31 +11,36 @@
|
|||
import Context from "../context"
|
||||
import ProgressCircle from "../ProgressCircle/ProgressCircle.svelte"
|
||||
|
||||
export let title = undefined
|
||||
export let size = "S"
|
||||
export let cancelText = "Cancel"
|
||||
export let confirmText = "Confirm"
|
||||
export let showCancelButton = true
|
||||
export let showConfirmButton = true
|
||||
export let showCloseIcon = true
|
||||
export let onConfirm = undefined
|
||||
export let onCancel = undefined
|
||||
export let disabled = false
|
||||
export let showDivider = true
|
||||
export let title: string | undefined = undefined
|
||||
export let size: "S" | "M" | "L" | "XL" = "S"
|
||||
export let cancelText: string = "Cancel"
|
||||
export let confirmText: string = "Confirm"
|
||||
export let showCancelButton: boolean = true
|
||||
export let showConfirmButton: boolean = true
|
||||
export let showCloseIcon: boolean = true
|
||||
export let onConfirm: (() => Promise<any> | any) | undefined = undefined
|
||||
export let onCancel: (() => Promise<any> | any) | undefined = undefined
|
||||
export let disabled: boolean = false
|
||||
export let showDivider: boolean = true
|
||||
|
||||
export let showSecondaryButton = false
|
||||
export let secondaryButtonText = undefined
|
||||
export let secondaryAction = undefined
|
||||
export let secondaryButtonWarning = false
|
||||
export let custom = false
|
||||
export let showSecondaryButton: boolean = false
|
||||
export let secondaryButtonText: string | undefined = undefined
|
||||
export let secondaryAction: ((_e: Event) => Promise<any> | any) | undefined =
|
||||
undefined
|
||||
export let secondaryButtonWarning: boolean = false
|
||||
export let custom: boolean = false
|
||||
|
||||
const { hide, cancel } = getContext(Context.Modal)
|
||||
const { hide, cancel } = getContext(Context.Modal) as {
|
||||
hide: () => void
|
||||
cancel: () => void
|
||||
}
|
||||
|
||||
let loading = false
|
||||
let loading: boolean = false
|
||||
|
||||
let confirmDisabled: boolean
|
||||
$: confirmDisabled = disabled || loading
|
||||
|
||||
async function secondary(e) {
|
||||
async function secondary(e: Event): Promise<void> {
|
||||
loading = true
|
||||
if (!secondaryAction || (await secondaryAction(e)) !== keepOpen) {
|
||||
hide()
|
||||
|
@ -43,7 +48,7 @@
|
|||
loading = false
|
||||
}
|
||||
|
||||
export async function confirm() {
|
||||
export async function confirm(): Promise<void> {
|
||||
loading = true
|
||||
if (!onConfirm || (await onConfirm()) !== keepOpen) {
|
||||
hide()
|
||||
|
@ -51,7 +56,7 @@
|
|||
loading = false
|
||||
}
|
||||
|
||||
async function close() {
|
||||
async function close(): Promise<void> {
|
||||
loading = true
|
||||
if (!onCancel || (await onCancel()) !== keepOpen) {
|
||||
cancel()
|
||||
|
@ -90,7 +95,6 @@
|
|||
{/if}
|
||||
{/if}
|
||||
|
||||
<!-- TODO: Remove content-grid class once Layout components are in bbui -->
|
||||
<section class="spectrum-Dialog-content content-grid">
|
||||
<slot {loading} />
|
||||
</section>
|
||||
|
@ -102,7 +106,6 @@
|
|||
{#if showSecondaryButton && secondaryButtonText && secondaryAction}
|
||||
<div class="secondary-action">
|
||||
<Button
|
||||
group
|
||||
secondary
|
||||
warning={secondaryButtonWarning}
|
||||
on:click={secondary}>{secondaryButtonText}</Button
|
||||
|
@ -111,14 +114,13 @@
|
|||
{/if}
|
||||
|
||||
{#if showCancelButton}
|
||||
<Button group secondary on:click={close}>
|
||||
<Button secondary on:click={close}>
|
||||
{cancelText}
|
||||
</Button>
|
||||
{/if}
|
||||
{#if showConfirmButton}
|
||||
<span class="confirm-wrap">
|
||||
<Button
|
||||
group
|
||||
cta
|
||||
{...$$restProps}
|
||||
disabled={confirmDisabled}
|
||||
|
|
|
@ -1,53 +0,0 @@
|
|||
<script>
|
||||
import ModalContent from "./ModalContent.svelte"
|
||||
import Input from "../Form/Input.svelte"
|
||||
|
||||
let modal
|
||||
let answer
|
||||
let error
|
||||
|
||||
export function show() {
|
||||
modal.show()
|
||||
}
|
||||
export function hide() {
|
||||
modal.hide
|
||||
}
|
||||
|
||||
function resetState() {
|
||||
answer = undefined
|
||||
error = undefined
|
||||
}
|
||||
|
||||
async function answerQuiz() {
|
||||
const correct = answer === "8"
|
||||
error = !correct
|
||||
return correct
|
||||
}
|
||||
</script>
|
||||
|
||||
<ModalContent
|
||||
title="Quick Maths"
|
||||
bind:this={modal}
|
||||
confirmText="Submit"
|
||||
onConfirm={answerQuiz}
|
||||
on:show={resetState}
|
||||
>
|
||||
{#if error}
|
||||
<p class="error">Wrong answer! Try again.</p>
|
||||
{/if}
|
||||
<p>What is 4 + 4?</p>
|
||||
<Input label="Answer" bind:value={answer} />
|
||||
</ModalContent>
|
||||
|
||||
<style>
|
||||
p {
|
||||
margin: 0;
|
||||
font-size: var(--font-size-s);
|
||||
}
|
||||
p.error {
|
||||
color: #e26d69;
|
||||
background-color: #ffe6e6;
|
||||
padding: 8px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
</style>
|
|
@ -1,17 +1,17 @@
|
|||
<script>
|
||||
<script lang="ts">
|
||||
import { ActionButton } from "../"
|
||||
|
||||
import { createEventDispatcher } from "svelte"
|
||||
|
||||
export let type = "info"
|
||||
export let icon = "Info"
|
||||
export let message = ""
|
||||
export let dismissable = false
|
||||
export let actionMessage = null
|
||||
export let action = null
|
||||
export let wide = false
|
||||
export let type: string = "info"
|
||||
export let icon: string = "Info"
|
||||
export let message: string = ""
|
||||
export let dismissable: boolean = false
|
||||
export let actionMessage: string | null = null
|
||||
export let action: ((_dismiss: () => void) => void) | null = null
|
||||
export let wide: boolean = false
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const dispatch = createEventDispatcher<{ dismiss: void }>()
|
||||
</script>
|
||||
|
||||
<div class="spectrum-Toast spectrum-Toast--{type}" class:wide>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<script>
|
||||
<script lang="ts">
|
||||
import "@spectrum-css/toast/dist/index-vars.css"
|
||||
import Portal from "svelte-portal"
|
||||
import { notifications } from "../Stores/notifications"
|
||||
|
|
|
@ -1,20 +1,19 @@
|
|||
<script>
|
||||
<script lang="ts">
|
||||
import "@spectrum-css/pagination/dist/index-vars.css"
|
||||
import "@spectrum-css/actionbutton/dist/index-vars.css"
|
||||
import "@spectrum-css/typography/dist/index-vars.css"
|
||||
|
||||
export let page
|
||||
export let goToPrevPage
|
||||
export let goToNextPage
|
||||
export let hasPrevPage = true
|
||||
export let hasNextPage = true
|
||||
export let page: number
|
||||
export let goToPrevPage: () => void
|
||||
export let goToNextPage: () => void
|
||||
export let hasPrevPage: boolean = true
|
||||
export let hasNextPage: boolean = true
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<nav class="spectrum-Pagination spectrum-Pagination--explicit">
|
||||
<div
|
||||
href="#"
|
||||
class="spectrum-ActionButton spectrum-ActionButton--sizeM spectrum-ActionButton--quiet spectrum-Pagination-prevButton"
|
||||
on:click={hasPrevPage ? goToPrevPage : null}
|
||||
class:is-disabled={!hasPrevPage}
|
||||
|
@ -32,7 +31,6 @@
|
|||
Page {page}
|
||||
</span>
|
||||
<div
|
||||
href="#"
|
||||
class="spectrum-ActionButton spectrum-ActionButton--sizeM spectrum-ActionButton--quiet spectrum-Pagination-nextButton"
|
||||
on:click={hasNextPage ? goToNextPage : null}
|
||||
class:is-disabled={!hasNextPage}
|
||||
|
|
|
@ -1,25 +1,24 @@
|
|||
<script>
|
||||
<script lang="ts">
|
||||
import "@spectrum-css/progressbar/dist/index-vars.css"
|
||||
|
||||
export let value = false
|
||||
export let duration = 1000
|
||||
export let width = false
|
||||
export let sideLabel = false
|
||||
export let hidePercentage = true
|
||||
export let color // red, green, default = blue
|
||||
export let size = "M"
|
||||
export let value: number | boolean = false
|
||||
export let duration: number = 1000
|
||||
export let width: string | boolean = false
|
||||
export let sideLabel: boolean = false
|
||||
export let hidePercentage: boolean = true
|
||||
export let color: "red" | "green" | undefined = undefined // red, green, default = blue
|
||||
export let size: string = "M"
|
||||
</script>
|
||||
|
||||
<div
|
||||
class:spectrum-ProgressBar--indeterminate={!value && value !== 0}
|
||||
class:spectrum-ProgressBar--sideLabel={sideLabel}
|
||||
class="spectrum-ProgressBar spectrum-ProgressBar--size{size}"
|
||||
{value}
|
||||
role="progressbar"
|
||||
aria-valuenow={value}
|
||||
aria-valuenow={typeof value === "number" ? value : undefined}
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
style={width ? `width: ${width};` : ""}
|
||||
style={width ? `width: ${typeof width === "string" ? width : ""};` : ""}
|
||||
>
|
||||
{#if $$slots}
|
||||
<div
|
||||
|
@ -32,7 +31,7 @@
|
|||
<div
|
||||
class="spectrum-FieldLabel spectrum-ProgressBar-percentage spectrum-FieldLabel--size{size}"
|
||||
>
|
||||
{Math.round(value)}%
|
||||
{Math.round(Number(value))}%
|
||||
</div>
|
||||
{/if}
|
||||
<div class="spectrum-ProgressBar-track">
|
||||
|
@ -40,10 +39,12 @@
|
|||
class="spectrum-ProgressBar-fill"
|
||||
class:color-green={color === "green"}
|
||||
class:color-red={color === "red"}
|
||||
style="width: {value}%; --duration: {duration}ms;"
|
||||
style="width: {typeof value === 'number'
|
||||
? value
|
||||
: 0}%; --duration: {duration}ms;"
|
||||
/>
|
||||
</div>
|
||||
<div class="spectrum-ProgressBar-label" hidden="" />
|
||||
<div class="spectrum-ProgressBar-label" hidden={false} />
|
||||
</div>
|
||||
|
||||
<style>
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<script>
|
||||
<script lang="ts">
|
||||
import "@spectrum-css/progresscircle/dist/index-vars.css"
|
||||
|
||||
export let size = "M"
|
||||
function convertSize(size) {
|
||||
export let size: "S" | "M" | "L" = "M"
|
||||
function convertSize(size: "S" | "M" | "L"): string | undefined {
|
||||
switch (size) {
|
||||
case "S":
|
||||
return "small"
|
||||
|
@ -13,18 +13,18 @@
|
|||
}
|
||||
}
|
||||
|
||||
export let value = null
|
||||
export let minValue = 0
|
||||
export let maxValue = 100
|
||||
export let value: number | null = null
|
||||
export let minValue: number = 0
|
||||
export let maxValue: number = 100
|
||||
|
||||
let subMask1Style
|
||||
let subMask2Style
|
||||
let subMask1Style: string | undefined
|
||||
let subMask2Style: string | undefined
|
||||
$: calculateSubMasks(value)
|
||||
|
||||
function calculateSubMasks(value) {
|
||||
function calculateSubMasks(value: number | null): void {
|
||||
if (value) {
|
||||
let percentage = ((value - minValue) / (maxValue - minValue)) * 100
|
||||
let angle
|
||||
let angle: number
|
||||
if (percentage > 0 && percentage <= 50) {
|
||||
angle = -180 + (percentage / 50) * 180
|
||||
subMask1Style = `transform: rotate(${angle}deg);`
|
||||
|
@ -37,7 +37,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
export let overBackground = false
|
||||
export let overBackground: boolean = false
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
|
|
|
@ -1,78 +0,0 @@
|
|||
<script>
|
||||
import { flip } from 'svelte/animate';
|
||||
import { fly } from "svelte/transition"
|
||||
import { View } from "svench";
|
||||
import { notifications } from "./notifications";
|
||||
|
||||
export let themes = {
|
||||
danger: "#E26D69",
|
||||
success: "#84C991",
|
||||
warning: "#f0ad4e",
|
||||
info: "#5bc0de",
|
||||
default: "#aaaaaa",
|
||||
}
|
||||
</script>
|
||||
|
||||
## Notification Store
|
||||
|
||||
This custom can be used to display toast messages. It has 5 different methods: `send`, `danger`, `warning`, `success`, `info`.
|
||||
|
||||
|
||||
<View name="danger">
|
||||
<button on:click={() => notifications.error('This is a danger!')}>Danger</button>
|
||||
</View>
|
||||
<View name="warning">
|
||||
<button on:click={() => notifications.warning('This is a warning!')}>Warning</button>
|
||||
</View>
|
||||
<View name="success">
|
||||
<button on:click={() => notifications.success('This is a success!')}>Success</button>
|
||||
</View>
|
||||
<View name="info">
|
||||
<button on:click={() => notifications.info('This is an info toast!')}>Info</button>
|
||||
</View>
|
||||
|
||||
<div class="notifications">
|
||||
{#each $notifications as notification (notification.id)}
|
||||
<div
|
||||
animate:flip
|
||||
class="toast"
|
||||
style="background: {themes[notification.type]};"
|
||||
transition:fly={{ y: -30 }}>
|
||||
<div class="content">{notification.message}</div>
|
||||
{#if notification.icon}<i class={notification.icon} />{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.notifications {
|
||||
position: fixed;
|
||||
top: 10px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin: 0 auto;
|
||||
padding: 0;
|
||||
z-index: 9999;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.toast {
|
||||
flex: 0 0 auto;
|
||||
margin-bottom: 10px;
|
||||
border-radius: var(--border-radius-s);
|
||||
/* The toasts now support being auto sized, so this static width could be removed */
|
||||
width: 40vw;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 10px;
|
||||
display: block;
|
||||
color: white;
|
||||
font-weight: 500;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
import AbsTooltip from "./AbsTooltip.svelte"
|
||||
|
||||
export let tooltip: string = ""
|
||||
export let size: "S" | "M" = "M"
|
||||
export let size: "S" | "M" | "L" = "M"
|
||||
export let disabled: boolean = true
|
||||
</script>
|
||||
|
||||
|
|
|
@ -11,6 +11,16 @@
|
|||
--bb-forest-green: #053835;
|
||||
--bb-beige: #f6efea;
|
||||
|
||||
/* Custom spectrum additions */
|
||||
--spectrum-global-color-static-red-1200: #740000;
|
||||
--spectrum-global-color-static-orange-1200: #612300;
|
||||
--spectrum-global-color-static-yellow-1200: #483300;
|
||||
--spectrum-global-color-static-green-1200: #053f27;
|
||||
--spectrum-global-color-static-seafoam-1200: #123c3a;
|
||||
--spectrum-global-color-static-blue-1200: #003571;
|
||||
--spectrum-global-color-static-indigo-1200: #262986;
|
||||
--spectrum-global-color-static-magenta-1200: #700037;
|
||||
|
||||
--grey-1: #fafafa;
|
||||
--grey-2: #f5f5f5;
|
||||
--grey-3: #eeeeee;
|
||||
|
|
|
@ -17,7 +17,6 @@ export { default as Toggle } from "./Form/Toggle.svelte"
|
|||
export { default as RadioGroup } from "./Form/RadioGroup.svelte"
|
||||
export { default as Checkbox } from "./Form/Checkbox.svelte"
|
||||
export { default as InputDropdown } from "./Form/InputDropdown.svelte"
|
||||
export { default as PickerDropdown } from "./Form/PickerDropdown.svelte"
|
||||
export { default as EnvDropdown } from "./Form/EnvDropdown.svelte"
|
||||
export { default as Multiselect } from "./Form/Multiselect.svelte"
|
||||
export { default as Search } from "./Form/Search.svelte"
|
||||
|
@ -87,8 +86,6 @@ export { default as MarkdownEditor } from "./Markdown/MarkdownEditor.svelte"
|
|||
export { default as MarkdownViewer } from "./Markdown/MarkdownViewer.svelte"
|
||||
export { default as List } from "./List/List.svelte"
|
||||
export { default as ListItem } from "./List/ListItem.svelte"
|
||||
export { default as IconSideNav } from "./IconSideNav/IconSideNav.svelte"
|
||||
export { default as IconSideNavItem } from "./IconSideNav/IconSideNavItem.svelte"
|
||||
export { default as Accordion } from "./Accordion/Accordion.svelte"
|
||||
export { default as AbsTooltip } from "./Tooltip/AbsTooltip.svelte"
|
||||
|
||||
|
|
|
@ -56,7 +56,10 @@
|
|||
memo,
|
||||
fetchData,
|
||||
} from "@budibase/frontend-core"
|
||||
import { getSchemaForDatasourcePlus } from "@/dataBinding"
|
||||
import {
|
||||
getSchemaForDatasourcePlus,
|
||||
readableToRuntimeBinding,
|
||||
} from "@/dataBinding"
|
||||
import { TriggerStepID, ActionStepID } from "@/constants/backend/automations"
|
||||
import { onMount, createEventDispatcher } from "svelte"
|
||||
import { writable } from "svelte/store"
|
||||
|
@ -1034,7 +1037,10 @@
|
|||
{bindings}
|
||||
{schema}
|
||||
panel={AutomationBindingPanel}
|
||||
on:change={e => onChange({ [key]: e.detail })}
|
||||
on:change={e =>
|
||||
onChange({
|
||||
[key]: readableToRuntimeBinding(bindings, e.detail),
|
||||
})}
|
||||
context={$memoContext}
|
||||
value={inputData[key]}
|
||||
/>
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
<script lang="ts">
|
||||
import { DrawerBindableInput } from "@/components/common/bindings"
|
||||
</script>
|
||||
|
||||
<DrawerBindableInput
|
||||
on:change
|
||||
on:blur
|
||||
on:drawerHide
|
||||
on:drawerShow
|
||||
{...$$props}
|
||||
multiline
|
||||
/>
|
|
@ -5,8 +5,8 @@
|
|||
import { appsStore } from "@/stores/portal"
|
||||
import { API } from "@/api"
|
||||
import { writable } from "svelte/store"
|
||||
import { createValidationStore } from "@/helpers/validation/yup"
|
||||
import * as appValidation from "@/helpers/validation/yup/app"
|
||||
import { createValidationStore } from "@budibase/frontend-core/src/utils/validation/yup"
|
||||
import * as appValidation from "@budibase/frontend-core/src/utils/validation/yup/app"
|
||||
import EditableIcon from "@/components/common/EditableIcon.svelte"
|
||||
import { isEqual } from "lodash"
|
||||
import { createEventDispatcher } from "svelte"
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script lang="ts">
|
||||
import { Icon, Input, Drawer, Button } from "@budibase/bbui"
|
||||
import { Icon, Input, Drawer, Button, CoreTextArea } from "@budibase/bbui"
|
||||
import {
|
||||
readableToRuntimeBinding,
|
||||
runtimeToReadableBinding,
|
||||
|
@ -25,11 +25,13 @@
|
|||
export let forceModal: boolean = false
|
||||
export let context = null
|
||||
export let autocomplete: boolean | undefined = undefined
|
||||
export let multiline: boolean = false
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
let bindingDrawer: any
|
||||
let currentVal = value
|
||||
let scrollable = false
|
||||
|
||||
$: readableValue = runtimeToReadableBinding(bindings, value)
|
||||
$: tempValue = readableValue
|
||||
|
@ -63,14 +65,16 @@
|
|||
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div class="control" class:disabled>
|
||||
<Input
|
||||
<div class="control" class:multiline class:disabled class:scrollable>
|
||||
<svelte:component
|
||||
this={multiline ? CoreTextArea : Input}
|
||||
{label}
|
||||
{disabled}
|
||||
readonly={isJS}
|
||||
value={isJS ? "(JavaScript function)" : readableValue}
|
||||
on:change={event => onChange(event.detail)}
|
||||
on:blur={onBlur}
|
||||
on:scrollable={e => (scrollable = e.detail)}
|
||||
{placeholder}
|
||||
{updateOnChange}
|
||||
{autocomplete}
|
||||
|
@ -114,36 +118,38 @@
|
|||
position: relative;
|
||||
}
|
||||
|
||||
.icon {
|
||||
right: 1px;
|
||||
bottom: 1px;
|
||||
position: absolute;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
box-sizing: border-box;
|
||||
border-left: 1px solid var(--spectrum-alias-border-color);
|
||||
border-top-right-radius: var(--spectrum-alias-border-radius-regular);
|
||||
border-bottom-right-radius: var(--spectrum-alias-border-radius-regular);
|
||||
width: 31px;
|
||||
color: var(--spectrum-alias-text-color);
|
||||
background-color: var(--spectrum-global-color-gray-75);
|
||||
transition: background-color
|
||||
var(--spectrum-global-animation-duration-100, 130ms),
|
||||
box-shadow var(--spectrum-global-animation-duration-100, 130ms),
|
||||
border-color var(--spectrum-global-animation-duration-100, 130ms);
|
||||
height: calc(var(--spectrum-alias-item-height-m) - 2px);
|
||||
/* Multiline styles */
|
||||
.control.multiline :global(textarea) {
|
||||
min-height: 0 !important;
|
||||
field-sizing: content;
|
||||
max-height: 105px;
|
||||
padding: 6px 11px 6px 11px;
|
||||
height: auto;
|
||||
resize: none;
|
||||
flex: 1 1 auto;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.icon {
|
||||
right: 6px;
|
||||
top: 8px;
|
||||
position: absolute;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
box-sizing: border-box;
|
||||
border-radius: 4px;
|
||||
color: var(--spectrum-alias-text-color);
|
||||
}
|
||||
.icon:hover {
|
||||
cursor: pointer;
|
||||
color: var(--spectrum-alias-text-color-hover);
|
||||
background-color: var(--spectrum-global-color-gray-50);
|
||||
border-color: var(--spectrum-alias-border-color-hover);
|
||||
color: var(--spectrum-global-color-blue-600);
|
||||
}
|
||||
.control.scrollable .icon {
|
||||
right: 12px;
|
||||
}
|
||||
|
||||
.control:not(.disabled) :global(.spectrum-Textfield-input) {
|
||||
padding-right: 40px;
|
||||
.control:not(.disabled) :global(.spectrum-Textfield-input),
|
||||
.control:not(.disabled) :global(textarea) {
|
||||
padding-right: 26px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -31,9 +31,11 @@ import FormStepConfiguration from "./controls/FormStepConfiguration.svelte"
|
|||
import FormStepControls from "./controls/FormStepControls.svelte"
|
||||
import PaywalledSetting from "./controls/PaywalledSetting.svelte"
|
||||
import TableConditionEditor from "./controls/TableConditionEditor.svelte"
|
||||
import MultilineDrawerBindableInput from "@/components/common/MultilineDrawerBindableInput.svelte"
|
||||
|
||||
const componentMap = {
|
||||
text: DrawerBindableInput,
|
||||
"text/multiline": MultilineDrawerBindableInput,
|
||||
plainText: Input,
|
||||
select: Select,
|
||||
radio: RadioGroup,
|
||||
|
|
|
@ -1,34 +0,0 @@
|
|||
<script>
|
||||
import { ModalContent, Body, notifications } from "@budibase/bbui"
|
||||
import PasswordRepeatInput from "@/components/common/users/PasswordRepeatInput.svelte"
|
||||
import { auth } from "@/stores/portal"
|
||||
|
||||
let password
|
||||
let error
|
||||
|
||||
const updatePassword = async () => {
|
||||
try {
|
||||
await auth.updateSelf({ password })
|
||||
notifications.success("Password changed successfully")
|
||||
} catch (error) {
|
||||
notifications.error("Failed to update password")
|
||||
}
|
||||
}
|
||||
|
||||
const handleKeydown = evt => {
|
||||
if (evt.key === "Enter" && !error && password) {
|
||||
updatePassword()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:window on:keydown={handleKeydown} />
|
||||
<ModalContent
|
||||
title="Update password"
|
||||
confirmText="Update password"
|
||||
onConfirm={updatePassword}
|
||||
disabled={error || !password}
|
||||
>
|
||||
<Body size="S">Enter your new password below.</Body>
|
||||
<PasswordRepeatInput bind:password bind:error />
|
||||
</ModalContent>
|
|
@ -1,29 +0,0 @@
|
|||
<script>
|
||||
import { ModalContent, Body, Input, notifications } from "@budibase/bbui"
|
||||
import { writable } from "svelte/store"
|
||||
import { auth } from "@/stores/portal"
|
||||
|
||||
const values = writable({
|
||||
firstName: $auth.user.firstName,
|
||||
lastName: $auth.user.lastName,
|
||||
})
|
||||
|
||||
const updateInfo = async () => {
|
||||
try {
|
||||
await auth.updateSelf($values)
|
||||
notifications.success("Information updated successfully")
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
notifications.error("Failed to update information")
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<ModalContent title="My profile" confirmText="Save" onConfirm={updateInfo}>
|
||||
<Body size="S">
|
||||
Personalise the platform by adding your first name and last name.
|
||||
</Body>
|
||||
<Input disabled bind:value={$auth.user.email} label="Email" />
|
||||
<Input bind:value={$values.firstName} label="First name" />
|
||||
<Input bind:value={$values.lastName} label="Last name" />
|
||||
</ModalContent>
|
|
@ -12,8 +12,8 @@
|
|||
import { appsStore, admin, auth } from "@/stores/portal"
|
||||
import { onMount } from "svelte"
|
||||
import { goto } from "@roxi/routify"
|
||||
import { createValidationStore } from "@/helpers/validation/yup"
|
||||
import * as appValidation from "@/helpers/validation/yup/app"
|
||||
import { createValidationStore } from "@budibase/frontend-core/src/utils/validation/yup"
|
||||
import * as appValidation from "@budibase/frontend-core/src/utils/validation/yup/app"
|
||||
import TemplateCard from "@/components/common/TemplateCard.svelte"
|
||||
import { lowercase } from "@/helpers"
|
||||
import { sdk } from "@budibase/shared-core"
|
||||
|
|
|
@ -6,9 +6,9 @@
|
|||
Layout,
|
||||
keepOpen,
|
||||
} from "@budibase/bbui"
|
||||
import { createValidationStore } from "@/helpers/validation/yup"
|
||||
import { createValidationStore } from "@budibase/frontend-core/src/utils/validation/yup"
|
||||
import { writable, get } from "svelte/store"
|
||||
import * as appValidation from "@/helpers/validation/yup/app"
|
||||
import * as appValidation from "@budibase/frontend-core/src/utils/validation/yup/app"
|
||||
import { appsStore, auth } from "@/stores/portal"
|
||||
import { onMount } from "svelte"
|
||||
import { API } from "@/api"
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
notifications,
|
||||
} from "@budibase/bbui"
|
||||
import { downloadFile } from "@budibase/frontend-core"
|
||||
import { createValidationStore } from "@/helpers/validation/yup"
|
||||
import { createValidationStore } from "@budibase/frontend-core/src/utils/validation/yup"
|
||||
|
||||
export let app
|
||||
export let published
|
||||
|
|
|
@ -219,6 +219,7 @@ export const PrettyRelationshipDefinitions = {
|
|||
|
||||
export const BUDIBASE_INTERNAL_DB_ID = INTERNAL_TABLE_SOURCE_ID
|
||||
export const DEFAULT_BB_DATASOURCE_ID = "datasource_internal_bb_default"
|
||||
export const DEFAULT_EMPLOYEE_TABLE_ID = "ta_bb_employee"
|
||||
export const BUDIBASE_DATASOURCE_TYPE = "budibase"
|
||||
export const DB_TYPE_INTERNAL = "internal"
|
||||
export const DB_TYPE_EXTERNAL = "external"
|
||||
|
|
|
@ -41,11 +41,6 @@ export const LAYOUT_NAMES = {
|
|||
},
|
||||
}
|
||||
|
||||
// one or more word characters and whitespace
|
||||
export const APP_NAME_REGEX = /^[\w\s]+$/
|
||||
// zero or more non-whitespace characters
|
||||
export const APP_URL_REGEX = /^[0-9a-zA-Z-_]+$/
|
||||
|
||||
export const DefaultAppTheme = {
|
||||
primaryColor: "var(--spectrum-global-color-blue-600)",
|
||||
primaryColorHover: "var(--spectrum-global-color-blue-500)",
|
||||
|
|
|
@ -37,7 +37,7 @@ const { ContextScopes } = Constants
|
|||
|
||||
// Regex to match all instances of template strings
|
||||
const CAPTURE_VAR_INSIDE_TEMPLATE = /{{([^}]+)}}/g
|
||||
const CAPTURE_VAR_INSIDE_JS = /\$\("([^")]+)"\)/g
|
||||
const CAPTURE_VAR_INSIDE_JS = /\$\((["'`])([^"'`]+)\1\)/g
|
||||
const CAPTURE_HBS_TEMPLATE = /{{[\S\s]*?}}/g
|
||||
|
||||
const UpdateReferenceAction = {
|
||||
|
|
|
@ -28,13 +28,13 @@
|
|||
Constants,
|
||||
Utils,
|
||||
RoleUtils,
|
||||
emailValidator,
|
||||
} from "@budibase/frontend-core"
|
||||
import { sdk } from "@budibase/shared-core"
|
||||
import { API } from "@/api"
|
||||
import GroupIcon from "../../../portal/users/groups/_components/GroupIcon.svelte"
|
||||
import RoleSelect from "@/components/common/RoleSelect.svelte"
|
||||
import UpgradeModal from "@/components/common/users/UpgradeModal.svelte"
|
||||
import { emailValidator } from "@/helpers/validation"
|
||||
import { fly } from "svelte/transition"
|
||||
import InfoDisplay from "../design/[screenId]/[componentId]/_components/Component/InfoDisplay.svelte"
|
||||
import BuilderGroupPopover from "./BuilderGroupPopover.svelte"
|
||||
|
|
|
@ -1,13 +1,30 @@
|
|||
<script>
|
||||
<script lang="ts">
|
||||
import { redirect } from "@roxi/routify"
|
||||
import { TableNames } from "@/constants"
|
||||
import { datasources } from "@/stores/builder"
|
||||
import { onMount } from "svelte"
|
||||
|
||||
$: {
|
||||
onMount(() => {
|
||||
// Get first valid table ID of first datasource
|
||||
let tableId: string = TableNames.USERS
|
||||
for (let ds of $datasources.list) {
|
||||
if (Array.isArray(ds.entities) && ds.entities.length > 0) {
|
||||
if (ds.entities[0]._id) {
|
||||
tableId = ds.entities[0]._id
|
||||
break
|
||||
}
|
||||
} else {
|
||||
const keys = Object.keys(ds.entities || {})
|
||||
if (keys.length > 0) {
|
||||
tableId = keys[0]
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($datasources.hasData) {
|
||||
$redirect(`./table/${TableNames.USERS}`)
|
||||
$redirect(`./table/${tableId}`)
|
||||
} else {
|
||||
$redirect("./new")
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
import StyleSection from "./StyleSection.svelte"
|
||||
import * as ComponentStyles from "./componentStyles"
|
||||
import ComponentSettingsSection from "./ComponentSettingsSection.svelte"
|
||||
import ColorPicker from "@/components/design/settings/controls/ColorPicker.svelte"
|
||||
|
||||
export let componentDefinition
|
||||
export let componentInstance
|
||||
|
@ -18,6 +19,19 @@
|
|||
styles.push(ComponentStyles[style])
|
||||
}
|
||||
})
|
||||
|
||||
// Add section for CSS variables if present
|
||||
if (def?.cssVariables?.length) {
|
||||
styles.push({
|
||||
label: "Customization",
|
||||
settings: def.cssVariables.map(variable => ({
|
||||
label: variable.label,
|
||||
key: variable.variable,
|
||||
control: ColorPicker,
|
||||
})),
|
||||
})
|
||||
}
|
||||
|
||||
return styles
|
||||
}
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
Checkbox,
|
||||
notifications,
|
||||
Select,
|
||||
Stepper,
|
||||
} from "@budibase/bbui"
|
||||
import {
|
||||
themeStore,
|
||||
|
@ -182,6 +183,16 @@
|
|||
options: screenRouteOptions,
|
||||
}}
|
||||
/>
|
||||
<PropertyControl
|
||||
label="Logo height (px)"
|
||||
control={Stepper}
|
||||
value={$nav.logoHeight}
|
||||
onChange={height => update("logoHeight", height)}
|
||||
props={{
|
||||
updateOnChange: false,
|
||||
placeholder: "24",
|
||||
}}
|
||||
/>
|
||||
<PropertyControl
|
||||
label="New tab"
|
||||
control={Checkbox}
|
||||
|
|
|
@ -18,6 +18,11 @@
|
|||
ghost.src =
|
||||
"data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw=="
|
||||
|
||||
// Aliases for other strings to match to when searching
|
||||
const aliases = {
|
||||
text: ["headline", "paragraph"],
|
||||
}
|
||||
|
||||
let searchString
|
||||
let searchRef
|
||||
let selectedIndex
|
||||
|
@ -148,11 +153,12 @@
|
|||
}
|
||||
|
||||
const filterStructure = (structure, allowedComponents, search) => {
|
||||
selectedIndex = search ? 0 : null
|
||||
componentList = []
|
||||
if (!structure?.length) {
|
||||
return []
|
||||
}
|
||||
search = search?.toLowerCase()
|
||||
selectedIndex = search ? 0 : null
|
||||
componentList = []
|
||||
|
||||
// Return only items which match the search string
|
||||
let filteredStructure = []
|
||||
|
@ -161,8 +167,12 @@
|
|||
const name = child.name.toLowerCase()
|
||||
|
||||
// Check if the component matches the search string
|
||||
if (search && !name.includes(search.toLowerCase())) {
|
||||
return false
|
||||
if (search) {
|
||||
const nameMatch = name.includes(search)
|
||||
const aliasMatch = (aliases[name] || []).some(x => x.includes(search))
|
||||
if (!nameMatch && !aliasMatch) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the component is allowed as a child
|
||||
|
|
|
@ -32,8 +32,7 @@
|
|||
"name": "Basic",
|
||||
"icon": "TextParagraph",
|
||||
"children": [
|
||||
"heading",
|
||||
"text",
|
||||
"textv2",
|
||||
"button",
|
||||
"buttongroup",
|
||||
"tag",
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
import { redirect } from "@roxi/routify"
|
||||
|
||||
$redirect("./data")
|
||||
$redirect("./design")
|
||||
</script>
|
||||
|
|
|
@ -24,13 +24,13 @@
|
|||
import { goto } from "@roxi/routify"
|
||||
import { AppStatus } from "@/constants"
|
||||
import { gradient } from "@/actions"
|
||||
import ProfileModal from "@/components/settings/ProfileModal.svelte"
|
||||
import ChangePasswordModal from "@/components/settings/ChangePasswordModal.svelte"
|
||||
import { ProfileModal, ChangePasswordModal } from "@budibase/frontend-core"
|
||||
import { processStringSync } from "@budibase/string-templates"
|
||||
import Spaceman from "assets/bb-space-man.svg"
|
||||
import Logo from "assets/bb-emblem.svg"
|
||||
import { UserAvatar } from "@budibase/frontend-core"
|
||||
import { helpers, sdk } from "@budibase/shared-core"
|
||||
import { API } from "@/api"
|
||||
|
||||
let loaded = false
|
||||
let userInfoModal
|
||||
|
@ -105,8 +105,8 @@
|
|||
<img class="logo" alt="logo" src={$organisation.logoUrl || Logo} />
|
||||
<ActionMenu align="right">
|
||||
<div slot="control" class="avatar">
|
||||
<UserAvatar user={$auth.user} showTooltip={false} />
|
||||
<Icon size="XL" name="ChevronDown" />
|
||||
<UserAvatar size="M" user={$auth.user} showTooltip={false} />
|
||||
<Icon size="L" name="ChevronDown" />
|
||||
</div>
|
||||
<MenuItem icon="UserEdit" on:click={() => userInfoModal.show()}>
|
||||
My profile
|
||||
|
@ -201,10 +201,14 @@
|
|||
</Page>
|
||||
</div>
|
||||
<Modal bind:this={userInfoModal}>
|
||||
<ProfileModal />
|
||||
<ProfileModal {API} user={$auth.user} on:save={() => auth.getSelf()} />
|
||||
</Modal>
|
||||
<Modal bind:this={changePasswordModal}>
|
||||
<ChangePasswordModal />
|
||||
<ChangePasswordModal
|
||||
{API}
|
||||
passwordMinLength={$admin.passwordMinLength}
|
||||
on:save={() => auth.getSelf()}
|
||||
/>
|
||||
</Modal>
|
||||
{/if}
|
||||
|
||||
|
@ -239,6 +243,7 @@
|
|||
grid-template-columns: auto auto;
|
||||
place-items: center;
|
||||
grid-gap: var(--spacing-xs);
|
||||
transition: filter 130ms ease-out;
|
||||
}
|
||||
.avatar:hover {
|
||||
cursor: pointer;
|
||||
|
|
|
@ -8,11 +8,10 @@
|
|||
notifications,
|
||||
} from "@budibase/bbui"
|
||||
import { goto, params } from "@roxi/routify"
|
||||
import { auth, organisation } from "@/stores/portal"
|
||||
import { auth, organisation, admin } from "@/stores/portal"
|
||||
import Logo from "assets/bb-emblem.svg"
|
||||
import { TestimonialPage } from "@budibase/frontend-core/src/components"
|
||||
import { TestimonialPage, PasswordRepeatInput } from "@budibase/frontend-core"
|
||||
import { onMount } from "svelte"
|
||||
import PasswordRepeatInput from "../../../components/common/users/PasswordRepeatInput.svelte"
|
||||
|
||||
const resetCode = $params["?code"]
|
||||
let form
|
||||
|
@ -80,9 +79,9 @@
|
|||
<Heading size="M">Reset your password</Heading>
|
||||
<Body size="M">Must contain at least 12 characters</Body>
|
||||
<PasswordRepeatInput
|
||||
bind:passwordForm={form}
|
||||
bind:password
|
||||
bind:error={passwordError}
|
||||
minLength={$admin.passwordMinLength || 12}
|
||||
/>
|
||||
<Button secondary cta on:click={reset}>
|
||||
{#if loading}
|
||||
|
|
|
@ -2,11 +2,12 @@
|
|||
import { admin, auth } from "@/stores/portal"
|
||||
import { ActionMenu, MenuItem, Icon, Modal } from "@budibase/bbui"
|
||||
import { goto } from "@roxi/routify"
|
||||
import ProfileModal from "@/components/settings/ProfileModal.svelte"
|
||||
import ChangePasswordModal from "@/components/settings/ChangePasswordModal.svelte"
|
||||
import ProfileModal from "@budibase/frontend-core/src/components/ProfileModal.svelte"
|
||||
import ChangePasswordModal from "@budibase/frontend-core/src/components/ChangePasswordModal.svelte"
|
||||
import ThemeModal from "@/components/settings/ThemeModal.svelte"
|
||||
import APIKeyModal from "@/components/settings/APIKeyModal.svelte"
|
||||
import { UserAvatar } from "@budibase/frontend-core"
|
||||
import { API } from "@/api"
|
||||
|
||||
let themeModal
|
||||
let profileModal
|
||||
|
@ -26,8 +27,8 @@
|
|||
|
||||
<ActionMenu align="right">
|
||||
<div slot="control" class="user-dropdown">
|
||||
<UserAvatar user={$auth.user} showTooltip={false} />
|
||||
<Icon size="XL" name="ChevronDown" />
|
||||
<UserAvatar size="M" user={$auth.user} showTooltip={false} />
|
||||
<Icon size="L" name="ChevronDown" />
|
||||
</div>
|
||||
<MenuItem icon="UserEdit" on:click={() => profileModal.show()}>
|
||||
My profile
|
||||
|
@ -60,10 +61,14 @@
|
|||
<ThemeModal />
|
||||
</Modal>
|
||||
<Modal bind:this={profileModal}>
|
||||
<ProfileModal />
|
||||
<ProfileModal {API} user={$auth.user} on:save={() => auth.getSelf()} />
|
||||
</Modal>
|
||||
<Modal bind:this={updatePasswordModal}>
|
||||
<ChangePasswordModal />
|
||||
<ChangePasswordModal
|
||||
{API}
|
||||
passwordMinLength={$admin.passwordMinLength}
|
||||
on:save={() => auth.getSelf()}
|
||||
/>
|
||||
</Modal>
|
||||
<Modal bind:this={apiKeyModal}>
|
||||
<APIKeyModal />
|
||||
|
@ -75,7 +80,8 @@
|
|||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
gap: var(--spacing-s);
|
||||
gap: var(--spacing-xs);
|
||||
transition: filter 130ms ease-out;
|
||||
}
|
||||
.user-dropdown:hover {
|
||||
cursor: pointer;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<script>
|
||||
import { Button, FancyForm, FancyInput } from "@budibase/bbui"
|
||||
import PanelHeader from "./PanelHeader.svelte"
|
||||
import { APP_URL_REGEX } from "@/constants"
|
||||
import { Constants } from "@budibase/frontend-core"
|
||||
|
||||
export let disabled
|
||||
export let name = ""
|
||||
|
@ -28,7 +28,7 @@
|
|||
return "URL must be provided"
|
||||
}
|
||||
|
||||
if (!APP_URL_REGEX.test(url)) {
|
||||
if (!Constants.APP_URL_REGEX.test(url)) {
|
||||
return "Invalid URL"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,7 +33,6 @@
|
|||
},
|
||||
edit: {
|
||||
width: "auto",
|
||||
borderLeft: true,
|
||||
displayName: "",
|
||||
},
|
||||
}
|
||||
|
|
|
@ -10,8 +10,7 @@
|
|||
Icon,
|
||||
} from "@budibase/bbui"
|
||||
import { groups, licensing } from "@/stores/portal"
|
||||
import { Constants } from "@budibase/frontend-core"
|
||||
import { emailValidator } from "@/helpers/validation"
|
||||
import { Constants, emailValidator } from "@budibase/frontend-core"
|
||||
import { capitalise } from "@/helpers"
|
||||
|
||||
export let showOnboardingTypeModal
|
||||
|
|
|
@ -8,8 +8,7 @@
|
|||
Icon,
|
||||
} from "@budibase/bbui"
|
||||
import { groups, licensing, admin } from "@/stores/portal"
|
||||
import { emailValidator } from "@/helpers/validation"
|
||||
import { Constants } from "@budibase/frontend-core"
|
||||
import { emailValidator, Constants } from "@budibase/frontend-core"
|
||||
import { capitalise } from "@/helpers"
|
||||
|
||||
const BYTES_IN_MB = 1000000
|
||||
|
|
|
@ -5,10 +5,10 @@ import getValidRoute from "../getValidRoute"
|
|||
import { getRowActionButtonTemplates } from "@/templates/rowActions"
|
||||
|
||||
const inline = async ({ tableOrView, permissions, screens }) => {
|
||||
const heading = new Component("@budibase/standard-components/heading")
|
||||
const heading = new Component("@budibase/standard-components/textv2")
|
||||
.instanceName("Table heading")
|
||||
.customProps({
|
||||
text: tableOrView.name,
|
||||
text: `## ${tableOrView.name}`,
|
||||
})
|
||||
.gridDesktopColSpan(1, 13)
|
||||
.gridDesktopRowSpan(1, 3)
|
||||
|
|
|
@ -43,10 +43,10 @@ const modal = async ({ tableOrView, permissions, screens }) => {
|
|||
.gridDesktopColSpan(7, 13)
|
||||
.gridDesktopRowSpan(1, 3)
|
||||
|
||||
const heading = new Component("@budibase/standard-components/heading")
|
||||
const heading = new Component("@budibase/standard-components/textv2")
|
||||
.instanceName("Table heading")
|
||||
.customProps({
|
||||
text: tableOrView.name,
|
||||
text: `## ${tableOrView.name}`,
|
||||
})
|
||||
.gridDesktopColSpan(1, 7)
|
||||
.gridDesktopRowSpan(1, 3)
|
||||
|
|
|
@ -40,10 +40,10 @@ const getTableScreenTemplate = ({
|
|||
.gridDesktopColSpan(7, 13)
|
||||
.gridDesktopRowSpan(1, 3)
|
||||
|
||||
const heading = new Component("@budibase/standard-components/heading")
|
||||
const heading = new Component("@budibase/standard-components/textv2")
|
||||
.instanceName("Table heading")
|
||||
.customProps({
|
||||
text: tableOrView.name,
|
||||
text: `## ${tableOrView.name}`,
|
||||
})
|
||||
.gridDesktopColSpan(1, 7)
|
||||
.gridDesktopRowSpan(1, 3)
|
||||
|
|
|
@ -41,10 +41,10 @@ const sidePanel = async ({ tableOrView, permissions, screens }) => {
|
|||
.gridDesktopColSpan(7, 13)
|
||||
.gridDesktopRowSpan(1, 3)
|
||||
|
||||
const heading = new Component("@budibase/standard-components/heading")
|
||||
const heading = new Component("@budibase/standard-components/textv2")
|
||||
.instanceName("Table heading")
|
||||
.customProps({
|
||||
text: tableOrView.name,
|
||||
text: `## ${tableOrView.name}`,
|
||||
})
|
||||
.gridDesktopColSpan(1, 7)
|
||||
.gridDesktopRowSpan(1, 3)
|
||||
|
|
|
@ -1048,6 +1048,7 @@
|
|||
},
|
||||
"text": {
|
||||
"name": "Paragraph",
|
||||
"deprecated": true,
|
||||
"description": "A component for displaying paragraph text.",
|
||||
"icon": "TextParagraph",
|
||||
"illegalChildren": ["section"],
|
||||
|
@ -1171,6 +1172,7 @@
|
|||
},
|
||||
"heading": {
|
||||
"name": "Headline",
|
||||
"deprecated": true,
|
||||
"icon": "TextBold",
|
||||
"description": "A component for displaying heading text",
|
||||
"illegalChildren": ["section"],
|
||||
|
@ -7582,6 +7584,15 @@
|
|||
"name": "Table",
|
||||
"icon": "Table",
|
||||
"styles": ["size"],
|
||||
"cssVariables": [{
|
||||
"label": "Header color",
|
||||
"variable": "--custom-header-cell-background",
|
||||
"type": "color"
|
||||
}, {
|
||||
"label": "Stripe color",
|
||||
"variable": "--custom-stripe-cell-background",
|
||||
"type": "color"
|
||||
}],
|
||||
"size": {
|
||||
"width": 600,
|
||||
"height": 400
|
||||
|
@ -7689,7 +7700,7 @@
|
|||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"label": "High contrast",
|
||||
"label": "Striped rows",
|
||||
"key": "stripeRows",
|
||||
"defaultValue": false
|
||||
},
|
||||
|
@ -7990,5 +8001,62 @@
|
|||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"textv2": {
|
||||
"name": "Text",
|
||||
"description": "A component for displaying text",
|
||||
"icon": "Text",
|
||||
"size": {
|
||||
"width": 400,
|
||||
"height": 24
|
||||
},
|
||||
"settings": [
|
||||
{
|
||||
"type": "text/multiline",
|
||||
"label": "Text (Markdown supported)",
|
||||
"key": "text",
|
||||
"wide": true
|
||||
},
|
||||
{
|
||||
"type": "select",
|
||||
"label": "Alignment",
|
||||
"key": "align",
|
||||
"defaultValue": "left",
|
||||
"showInBar": true,
|
||||
"barStyle": "buttons",
|
||||
"options": [
|
||||
{
|
||||
"label": "Left",
|
||||
"value": "left",
|
||||
"barIcon": "TextAlignLeft",
|
||||
"barTitle": "Align left"
|
||||
},
|
||||
{
|
||||
"label": "Center",
|
||||
"value": "center",
|
||||
"barIcon": "TextAlignCenter",
|
||||
"barTitle": "Align center"
|
||||
},
|
||||
{
|
||||
"label": "Right",
|
||||
"value": "right",
|
||||
"barIcon": "TextAlignRight",
|
||||
"barTitle": "Align right"
|
||||
},
|
||||
{
|
||||
"label": "Justify",
|
||||
"value": "justify",
|
||||
"barIcon": "TextAlignJustify",
|
||||
"barTitle": "Justify text"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "color",
|
||||
"label": "Color",
|
||||
"key": "color",
|
||||
"showInBar": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
import { Heading, Icon, clickOutside } from "@budibase/bbui"
|
||||
import { Constants } from "@budibase/frontend-core"
|
||||
import NavItem from "./NavItem.svelte"
|
||||
import UserMenu from "./UserMenu.svelte"
|
||||
|
||||
const sdk = getContext("sdk")
|
||||
const {
|
||||
|
@ -13,7 +14,6 @@
|
|||
builderStore,
|
||||
sidePanelStore,
|
||||
modalStore,
|
||||
appStore,
|
||||
} = sdk
|
||||
const context = getContext("context")
|
||||
const navStateStore = writable({})
|
||||
|
@ -34,6 +34,7 @@
|
|||
export let navWidth
|
||||
export let pageWidth
|
||||
export let logoLinkUrl
|
||||
export let logoHeight
|
||||
export let openLogoLinkInNewTab
|
||||
export let textAlign
|
||||
export let embedded = false
|
||||
|
@ -70,6 +71,7 @@
|
|||
$: navStyle = getNavStyle(
|
||||
navBackground,
|
||||
navTextColor,
|
||||
logoHeight,
|
||||
$context.device.width,
|
||||
$context.device.height
|
||||
)
|
||||
|
@ -156,11 +158,6 @@
|
|||
return !url.startsWith("http") ? `http://${url}` : url
|
||||
}
|
||||
|
||||
const navigateToPortal = () => {
|
||||
if ($builderStore.inBuilder) return
|
||||
window.location.href = "/builder/apps"
|
||||
}
|
||||
|
||||
const getScreenXOffset = (navigation, mobile) => {
|
||||
if (navigation !== "Left") {
|
||||
return 0
|
||||
|
@ -175,7 +172,13 @@
|
|||
}
|
||||
}
|
||||
|
||||
const getNavStyle = (backgroundColor, textColor, width, height) => {
|
||||
const getNavStyle = (
|
||||
backgroundColor,
|
||||
textColor,
|
||||
logoHeight,
|
||||
width,
|
||||
height
|
||||
) => {
|
||||
let style = `--width:${width}px; --height:${height}px;`
|
||||
if (backgroundColor) {
|
||||
style += `--navBackground:${backgroundColor};`
|
||||
|
@ -183,6 +186,7 @@
|
|||
if (textColor) {
|
||||
style += `--navTextColor:${textColor};`
|
||||
}
|
||||
style += `--logoHeight:${logoHeight || 24}px;`
|
||||
return style
|
||||
}
|
||||
|
||||
|
@ -267,13 +271,8 @@
|
|||
{/if}
|
||||
</div>
|
||||
{#if !embedded}
|
||||
<div class="portal">
|
||||
<Icon
|
||||
hoverable
|
||||
name="Apps"
|
||||
on:click={navigateToPortal}
|
||||
disabled={$appStore.isDevApp}
|
||||
/>
|
||||
<div class="user top">
|
||||
<UserMenu compact />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
@ -297,13 +296,11 @@
|
|||
{navStateStore}
|
||||
/>
|
||||
{/each}
|
||||
<div class="close">
|
||||
<Icon
|
||||
hoverable
|
||||
name="Close"
|
||||
on:click={() => (mobileOpen = false)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{#if !embedded}
|
||||
<div class="user left">
|
||||
<UserMenu />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
@ -394,21 +391,15 @@
|
|||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
.layout--top .nav-wrapper {
|
||||
border-bottom: 1px solid var(--spectrum-global-color-gray-300);
|
||||
}
|
||||
.layout--left .nav-wrapper {
|
||||
border-right: 1px solid var(--spectrum-global-color-gray-300);
|
||||
}
|
||||
|
||||
.nav {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: stretch;
|
||||
padding: 24px 32px 20px 32px;
|
||||
padding: 18px 32px 18px 32px;
|
||||
max-width: 100%;
|
||||
gap: var(--spacing-xl);
|
||||
gap: var(--spacing-xs);
|
||||
}
|
||||
.nav :global(.spectrum-Icon) {
|
||||
color: var(--navTextColor);
|
||||
|
@ -522,7 +513,7 @@
|
|||
flex: 1 1 auto;
|
||||
}
|
||||
.logo img {
|
||||
height: 36px;
|
||||
height: var(--logoHeight);
|
||||
}
|
||||
.logo :global(h1) {
|
||||
font-weight: 600;
|
||||
|
@ -532,11 +523,8 @@
|
|||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.portal {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
}
|
||||
.links {
|
||||
flex: 1 0 auto;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
|
@ -544,45 +532,38 @@
|
|||
gap: var(--spacing-xl);
|
||||
margin-top: var(--spacing-xl);
|
||||
}
|
||||
.close {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: var(--spacing-xl);
|
||||
right: var(--spacing-xl);
|
||||
}
|
||||
.mobile-click-handler {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Left overrides for both desktop and mobile */
|
||||
.nav--left {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
/* Desktop nav overrides */
|
||||
.desktop.layout--left .layout-body {
|
||||
flex-direction: row;
|
||||
overflow: hidden;
|
||||
}
|
||||
.desktop.layout--left .nav-wrapper {
|
||||
border-bottom: none;
|
||||
}
|
||||
.desktop.layout--left .main-wrapper {
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
.desktop.layout--left .links {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.desktop .nav--left {
|
||||
width: 250px;
|
||||
padding: var(--spacing-xl);
|
||||
}
|
||||
|
||||
.desktop .nav--left .links {
|
||||
margin-top: var(--spacing-m);
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: stretch;
|
||||
gap: var(--spacing-xs);
|
||||
}
|
||||
.desktop .nav--left .link {
|
||||
font-size: var(--spectrum-global-dimension-font-size-150);
|
||||
.desktop .nav--left .user.top,
|
||||
.desktop .nav--top .user.left {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Mobile nav overrides */
|
||||
|
@ -591,13 +572,9 @@
|
|||
top: 0;
|
||||
left: 0;
|
||||
box-shadow: 0 0 8px -1px rgba(0, 0, 0, 0.075);
|
||||
border-bottom: 1px solid var(--spectrum-global-color-gray-300);
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
/* Show close button in drawer */
|
||||
.mobile .close {
|
||||
display: block;
|
||||
.mobile .user.left {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Force standard top bar */
|
||||
|
@ -635,6 +612,7 @@
|
|||
left: -250px;
|
||||
transform: translateX(0);
|
||||
width: 250px;
|
||||
max-width: 75%;
|
||||
transition: transform 0.26s ease-in-out, opacity 0.26s ease-in-out;
|
||||
height: var(--height);
|
||||
opacity: 0;
|
||||
|
@ -645,10 +623,10 @@
|
|||
align-items: stretch;
|
||||
padding: var(--spacing-xl);
|
||||
overflow-y: auto;
|
||||
gap: var(--spacing-xs);
|
||||
}
|
||||
.mobile .link {
|
||||
width: calc(100% - 30px);
|
||||
font-size: 120%;
|
||||
.mobile .links :global(a) {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
.mobile .links.visible {
|
||||
opacity: 1;
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
import { createEventDispatcher } from "svelte"
|
||||
import active from "svelte-spa-router/active"
|
||||
import { Icon } from "@budibase/bbui"
|
||||
import { builderStore, screenStore } from "@/stores"
|
||||
|
||||
export let type
|
||||
export let url
|
||||
|
@ -16,7 +17,16 @@
|
|||
|
||||
let renderKey
|
||||
|
||||
$: expanded = !!$navStateStore[text]
|
||||
$: isBuilderActive = testUrl => {
|
||||
return (
|
||||
$builderStore.inBuilder &&
|
||||
testUrl &&
|
||||
testUrl === $screenStore.activeScreen?.routing?.route
|
||||
)
|
||||
}
|
||||
$: builderActive = isBuilderActive(url)
|
||||
$: containsActiveLink = (subLinks || []).some(x => isBuilderActive(x.url))
|
||||
$: expanded = !!$navStateStore[text] || containsActiveLink
|
||||
$: renderLeftNav = leftNav || mobile
|
||||
$: icon = !renderLeftNav || expanded ? "ChevronDown" : "ChevronRight"
|
||||
|
||||
|
@ -47,7 +57,7 @@
|
|||
href="#{url}"
|
||||
on:click={onClickLink}
|
||||
use:active={url}
|
||||
class:active={false}
|
||||
class:builderActive
|
||||
>
|
||||
{text}
|
||||
</a>
|
||||
|
@ -73,6 +83,9 @@
|
|||
href="#{subLink.url}"
|
||||
on:click={onClickLink}
|
||||
use:active={subLink.url}
|
||||
class:active={false}
|
||||
class:builderActive={isBuilderActive(subLink.url)}
|
||||
class="sublink"
|
||||
>
|
||||
{subLink.text}
|
||||
</a>
|
||||
|
@ -91,22 +104,29 @@
|
|||
<style>
|
||||
/* Generic styles */
|
||||
a,
|
||||
.dropdown .text {
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
a,
|
||||
.text span {
|
||||
opacity: 0.75;
|
||||
color: var(--navTextColor);
|
||||
font-size: var(--spectrum-global-dimension-font-size-200);
|
||||
font-size: var(--spectrum-global-dimension-font-size-150);
|
||||
transition: opacity 130ms ease-out;
|
||||
font-weight: 600;
|
||||
user-select: none;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
a.active {
|
||||
a.active:not(.sublink),
|
||||
a.builderActive:not(.sublink),
|
||||
.dropdown.left a.sublink.active,
|
||||
.dropdown.left a.sublink.builderActive {
|
||||
background: rgba(0, 0, 0, 0.15);
|
||||
opacity: 1;
|
||||
}
|
||||
a:hover,
|
||||
.dropdown:not(.left.expanded):hover .text,
|
||||
.text:hover {
|
||||
.text:hover span {
|
||||
cursor: pointer;
|
||||
opacity: 1;
|
||||
}
|
||||
|
@ -167,8 +187,4 @@
|
|||
.dropdown.dropdown.left.expanded .sublinks {
|
||||
display: contents;
|
||||
}
|
||||
.dropdown.left a {
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,105 +1,44 @@
|
|||
<script>
|
||||
<script lang="ts">
|
||||
import { getContext } from "svelte"
|
||||
import { MarkdownViewer } from "@budibase/bbui"
|
||||
|
||||
export let text: string = ""
|
||||
export let color: string | undefined = undefined
|
||||
export let align: "left" | "center" | "right" | "justify" = "left"
|
||||
|
||||
const { styleable, builderStore } = getContext("sdk")
|
||||
const component = getContext("component")
|
||||
const { styleable } = getContext("sdk")
|
||||
|
||||
export let text
|
||||
export let color
|
||||
export let align
|
||||
export let bold
|
||||
export let italic
|
||||
export let underline
|
||||
export let size
|
||||
// Add in certain settings to styles
|
||||
$: styles = enrichStyles($component.styles, color, align)
|
||||
|
||||
let node
|
||||
let touched = false
|
||||
|
||||
$: $component.editing && node?.focus()
|
||||
$: placeholder = $builderStore.inBuilder && !text && !$component.editing
|
||||
$: componentText = getComponentText(text, $builderStore, $component)
|
||||
$: sizeClass = `spectrum-Body--size${size || "M"}`
|
||||
$: alignClass = `align--${align || "left"}`
|
||||
|
||||
// Add color styles to main styles object, otherwise the styleable helper
|
||||
// overrides the color when it's passed as inline style.
|
||||
$: styles = enrichStyles($component.styles, color)
|
||||
|
||||
const getComponentText = (text, builderState, componentState) => {
|
||||
if (!builderState.inBuilder || componentState.editing) {
|
||||
return text || ""
|
||||
const enrichStyles = (
|
||||
styles: any,
|
||||
colorStyle: typeof color,
|
||||
alignStyle: typeof align
|
||||
) => {
|
||||
let additions: Record<string, string> = {
|
||||
"text-align": alignStyle,
|
||||
}
|
||||
return text || componentState.name || "Placeholder text"
|
||||
}
|
||||
|
||||
const enrichStyles = (styles, color) => {
|
||||
if (!color) {
|
||||
return styles
|
||||
if (colorStyle) {
|
||||
additions.color = colorStyle
|
||||
}
|
||||
return {
|
||||
...styles,
|
||||
normal: {
|
||||
...styles?.normal,
|
||||
color,
|
||||
...styles.normal,
|
||||
...additions,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Convert contenteditable HTML to text and save
|
||||
const updateText = e => {
|
||||
if (touched) {
|
||||
builderStore.actions.updateProp("text", e.target.textContent)
|
||||
}
|
||||
touched = false
|
||||
}
|
||||
</script>
|
||||
|
||||
{#key $component.editing}
|
||||
<p
|
||||
bind:this={node}
|
||||
contenteditable={$component.editing}
|
||||
use:styleable={styles}
|
||||
class:placeholder
|
||||
class:bold
|
||||
class:italic
|
||||
class:underline
|
||||
class="spectrum-Body {sizeClass} {alignClass}"
|
||||
on:blur={$component.editing ? updateText : null}
|
||||
on:input={() => (touched = true)}
|
||||
>
|
||||
{componentText}
|
||||
</p>
|
||||
{/key}
|
||||
<div use:styleable={styles}>
|
||||
<MarkdownViewer value={text} />
|
||||
</div>
|
||||
|
||||
<style>
|
||||
p {
|
||||
display: inline-block;
|
||||
white-space: pre-wrap;
|
||||
margin: 0;
|
||||
}
|
||||
.placeholder {
|
||||
font-style: italic;
|
||||
color: var(--spectrum-global-color-gray-600);
|
||||
}
|
||||
.bold {
|
||||
font-weight: 600;
|
||||
}
|
||||
.italic {
|
||||
font-style: italic;
|
||||
}
|
||||
.underline {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.align--left {
|
||||
text-align: left;
|
||||
}
|
||||
.align--center {
|
||||
text-align: center;
|
||||
}
|
||||
.align--right {
|
||||
text-align: right;
|
||||
}
|
||||
.align--justify {
|
||||
text-align: justify;
|
||||
div :global(img) {
|
||||
max-width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -0,0 +1,144 @@
|
|||
<script lang="ts">
|
||||
import { ActionMenu, Icon, MenuItem, Modal } from "@budibase/bbui"
|
||||
import {
|
||||
UserAvatar,
|
||||
ProfileModal,
|
||||
ChangePasswordModal,
|
||||
} from "@budibase/frontend-core"
|
||||
import { getContext } from "svelte"
|
||||
import { type User, type ContextUser, isSSOUser } from "@budibase/types"
|
||||
import { sdk } from "@budibase/shared-core"
|
||||
import { API } from "@/api"
|
||||
|
||||
export let compact: boolean = false
|
||||
|
||||
const { authStore, environmentStore, notificationStore, appStore } =
|
||||
getContext("sdk")
|
||||
|
||||
let profileModal: any
|
||||
let changePasswordModal: any
|
||||
|
||||
$: text = getText($authStore)
|
||||
$: isBuilder = sdk.users.hasBuilderPermissions($authStore)
|
||||
$: isSSO = $authStore != null && isSSOUser($authStore)
|
||||
$: isOwner = $authStore?.accountPortalAccess && $environmentStore.cloud
|
||||
$: embedded = $appStore.embedded || $appStore.inIframe
|
||||
|
||||
const getText = (user?: User | ContextUser): string => {
|
||||
if (!user) {
|
||||
return ""
|
||||
}
|
||||
if (user.firstName) {
|
||||
let text = user.firstName
|
||||
if (user.lastName) {
|
||||
text += ` ${user.lastName}`
|
||||
}
|
||||
return text
|
||||
} else {
|
||||
return user.email
|
||||
}
|
||||
}
|
||||
|
||||
const goToPortal = () => {
|
||||
window.location.href = isBuilder ? "/builder/portal/apps" : "/builder/apps"
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if $authStore}
|
||||
<ActionMenu align={compact ? "right" : "left"}>
|
||||
<svelte:fragment slot="control">
|
||||
<div class="container">
|
||||
<UserAvatar user={$authStore} size="M" showTooltip={false} />
|
||||
{#if !compact}
|
||||
<div class="text">
|
||||
<div class="name">
|
||||
{text}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
<Icon size="L" name="ChevronDown" />
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
|
||||
<MenuItem icon="UserEdit" on:click={() => profileModal?.show()}>
|
||||
My profile
|
||||
</MenuItem>
|
||||
{#if !isSSO}
|
||||
<MenuItem
|
||||
icon="LockClosed"
|
||||
on:click={() => {
|
||||
if (isOwner) {
|
||||
window.location.href = `${$environmentStore.accountPortalUrl}/portal/account`
|
||||
} else {
|
||||
changePasswordModal?.show()
|
||||
}
|
||||
}}
|
||||
>
|
||||
Update password
|
||||
</MenuItem>
|
||||
{/if}
|
||||
|
||||
<MenuItem icon="Apps" on:click={goToPortal} disabled={embedded}>
|
||||
Go to portal
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
icon="LogOut"
|
||||
on:click={authStore.actions.logOut}
|
||||
disabled={embedded}
|
||||
>
|
||||
Log out
|
||||
</MenuItem>
|
||||
</ActionMenu>
|
||||
|
||||
<Modal bind:this={profileModal}>
|
||||
<ProfileModal
|
||||
{API}
|
||||
user={$authStore}
|
||||
on:save={() => authStore.actions.fetchUser()}
|
||||
notifySuccess={notificationStore.actions.success}
|
||||
notifyError={notificationStore.actions.error}
|
||||
/>
|
||||
</Modal>
|
||||
<Modal bind:this={changePasswordModal}>
|
||||
<ChangePasswordModal
|
||||
{API}
|
||||
passwordMinLength={$environmentStore.passwordMinLength}
|
||||
on:save={() => authStore.actions.logOut()}
|
||||
notifySuccess={notificationStore.actions.success}
|
||||
notifyError={notificationStore.actions.error}
|
||||
/>
|
||||
</Modal>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
gap: var(--spacing-xs);
|
||||
transition: filter 130ms ease-out;
|
||||
overflow: hidden;
|
||||
}
|
||||
.text {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
color: var(--navTextColor);
|
||||
display: flex;
|
||||
margin-left: var(--spacing-xs);
|
||||
overflow: hidden;
|
||||
}
|
||||
.name {
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
}
|
||||
.container:hover {
|
||||
cursor: pointer;
|
||||
filter: brightness(110%);
|
||||
}
|
||||
</style>
|
|
@ -125,9 +125,9 @@
|
|||
order={0}
|
||||
>
|
||||
<BlockComponent
|
||||
type="heading"
|
||||
type="textv2"
|
||||
props={{
|
||||
text: title,
|
||||
text: title ? `## ${title}` : "",
|
||||
}}
|
||||
order={0}
|
||||
/>
|
||||
|
|
|
@ -148,7 +148,10 @@
|
|||
}}
|
||||
order={0}
|
||||
>
|
||||
<BlockComponent type="heading" props={{ text: step.title }} />
|
||||
<BlockComponent
|
||||
type="textv2"
|
||||
props={{ text: `## ${step.title}` }}
|
||||
/>
|
||||
{#if buttonPosition === "top"}
|
||||
<BlockComponent
|
||||
type="buttongroup"
|
||||
|
@ -157,7 +160,7 @@
|
|||
{/if}
|
||||
</BlockComponent>
|
||||
</BlockComponent>
|
||||
<BlockComponent type="text" props={{ text: step.desc }} order={1} />
|
||||
<BlockComponent type="textv2" props={{ text: step.desc }} order={1} />
|
||||
|
||||
<BlockComponent type="container" order={2}>
|
||||
<div
|
||||
|
|
|
@ -190,7 +190,7 @@
|
|||
}}
|
||||
/>
|
||||
<BlockComponent
|
||||
type="text"
|
||||
type="textv2"
|
||||
order={1}
|
||||
props={{
|
||||
text: "Select a row to view its fields",
|
||||
|
|
|
@ -74,11 +74,11 @@
|
|||
order={0}
|
||||
>
|
||||
<BlockComponent
|
||||
type="heading"
|
||||
props={{ text: title || "" }}
|
||||
type="textv2"
|
||||
props={{ text: title ? `## ${title}` : "" }}
|
||||
order={0}
|
||||
/>
|
||||
{#if buttonPosition == "top"}
|
||||
{#if buttonPosition === "top"}
|
||||
<BlockComponent
|
||||
type="buttongroup"
|
||||
props={{
|
||||
|
@ -93,7 +93,7 @@
|
|||
</BlockComponent>
|
||||
{/if}
|
||||
{#if description}
|
||||
<BlockComponent type="text" props={{ text: description }} order={1} />
|
||||
<BlockComponent type="textv2" props={{ text: description }} order={1} />
|
||||
{/if}
|
||||
<BlockComponent type="container">
|
||||
<div class="form-block fields" class:mobile={$context.device.mobile}>
|
||||
|
|
|
@ -184,9 +184,9 @@
|
|||
order={0}
|
||||
>
|
||||
<BlockComponent
|
||||
type="heading"
|
||||
type="textv2"
|
||||
props={{
|
||||
text: title,
|
||||
text: title ? `## ${title}` : "",
|
||||
}}
|
||||
order={0}
|
||||
/>
|
||||
|
|
|
@ -0,0 +1,105 @@
|
|||
<script>
|
||||
import { getContext } from "svelte"
|
||||
|
||||
const { styleable, builderStore } = getContext("sdk")
|
||||
const component = getContext("component")
|
||||
|
||||
export let text
|
||||
export let color
|
||||
export let align
|
||||
export let bold
|
||||
export let italic
|
||||
export let underline
|
||||
export let size
|
||||
|
||||
let node
|
||||
let touched = false
|
||||
|
||||
$: $component.editing && node?.focus()
|
||||
$: placeholder = $builderStore.inBuilder && !text && !$component.editing
|
||||
$: componentText = getComponentText(text, $builderStore, $component)
|
||||
$: sizeClass = `spectrum-Body--size${size || "M"}`
|
||||
$: alignClass = `align--${align || "left"}`
|
||||
|
||||
// Add color styles to main styles object, otherwise the styleable helper
|
||||
// overrides the color when it's passed as inline style.
|
||||
$: styles = enrichStyles($component.styles, color)
|
||||
|
||||
const getComponentText = (text, builderState, componentState) => {
|
||||
if (!builderState.inBuilder || componentState.editing) {
|
||||
return text || ""
|
||||
}
|
||||
return text || componentState.name || "Placeholder text"
|
||||
}
|
||||
|
||||
const enrichStyles = (styles, color) => {
|
||||
if (!color) {
|
||||
return styles
|
||||
}
|
||||
return {
|
||||
...styles,
|
||||
normal: {
|
||||
...styles?.normal,
|
||||
color,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Convert contenteditable HTML to text and save
|
||||
const updateText = e => {
|
||||
if (touched) {
|
||||
builderStore.actions.updateProp("text", e.target.textContent)
|
||||
}
|
||||
touched = false
|
||||
}
|
||||
</script>
|
||||
|
||||
{#key $component.editing}
|
||||
<p
|
||||
bind:this={node}
|
||||
contenteditable={$component.editing}
|
||||
use:styleable={styles}
|
||||
class:placeholder
|
||||
class:bold
|
||||
class:italic
|
||||
class:underline
|
||||
class="spectrum-Body {sizeClass} {alignClass}"
|
||||
on:blur={$component.editing ? updateText : null}
|
||||
on:input={() => (touched = true)}
|
||||
>
|
||||
{componentText}
|
||||
</p>
|
||||
{/key}
|
||||
|
||||
<style>
|
||||
p {
|
||||
display: inline-block;
|
||||
white-space: pre-wrap;
|
||||
margin: 0;
|
||||
}
|
||||
.placeholder {
|
||||
font-style: italic;
|
||||
color: var(--spectrum-global-color-gray-600);
|
||||
}
|
||||
.bold {
|
||||
font-weight: 600;
|
||||
}
|
||||
.italic {
|
||||
font-style: italic;
|
||||
}
|
||||
.underline {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.align--left {
|
||||
text-align: left;
|
||||
}
|
||||
.align--center {
|
||||
text-align: center;
|
||||
}
|
||||
.align--right {
|
||||
text-align: right;
|
||||
}
|
||||
.align--justify {
|
||||
text-align: justify;
|
||||
}
|
||||
</style>
|
|
@ -1,31 +1,36 @@
|
|||
<script>
|
||||
<script lang="ts">
|
||||
import { sdk } from "@budibase/shared-core"
|
||||
import { FieldType } from "@budibase/types"
|
||||
import RelationshipField from "./RelationshipField.svelte"
|
||||
|
||||
export let defaultValue
|
||||
export let defaultValue: string
|
||||
export let type = FieldType.BB_REFERENCE
|
||||
|
||||
function updateUserIDs(value) {
|
||||
function updateUserIDs(value: string | string[]) {
|
||||
if (Array.isArray(value)) {
|
||||
return value.map(val => sdk.users.getGlobalUserID(val))
|
||||
return value.map(val => sdk.users.getGlobalUserID(val)!)
|
||||
} else {
|
||||
return sdk.users.getGlobalUserID(value)
|
||||
}
|
||||
}
|
||||
|
||||
function updateReferences(value) {
|
||||
function updateReferences(value: string) {
|
||||
if (sdk.users.containsUserID(value)) {
|
||||
return updateUserIDs(value)
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
$: updatedDefaultValue = updateReferences(defaultValue)
|
||||
|
||||
// This cannot be typed, as svelte does not provide typed inheritance
|
||||
$: allProps = $$props as any
|
||||
</script>
|
||||
|
||||
<RelationshipField
|
||||
{...$$props}
|
||||
{...allProps}
|
||||
{type}
|
||||
datasourceType={"user"}
|
||||
primaryDisplay={"email"}
|
||||
defaultValue={updateReferences(defaultValue)}
|
||||
defaultValue={updatedDefaultValue}
|
||||
/>
|
||||
|
|
|
@ -1,43 +1,60 @@
|
|||
<script lang="ts">
|
||||
import { getContext, onDestroy } from "svelte"
|
||||
import type { Readable } from "svelte/store"
|
||||
import { writable } from "svelte/store"
|
||||
import { Icon } from "@budibase/bbui"
|
||||
import { memo } from "@budibase/frontend-core"
|
||||
import Placeholder from "../Placeholder.svelte"
|
||||
import InnerForm from "./InnerForm.svelte"
|
||||
import type { FieldApi } from "."
|
||||
import type { FieldSchema, FieldType } from "@budibase/types"
|
||||
import type {
|
||||
FieldApi,
|
||||
FieldState,
|
||||
FieldValidation,
|
||||
FormField,
|
||||
} from "@/types"
|
||||
|
||||
interface FieldInfo {
|
||||
field: string
|
||||
type: FieldType
|
||||
defaultValue: string | undefined
|
||||
disabled: boolean
|
||||
readonly: boolean
|
||||
validation?: FieldValidation
|
||||
formStep: number
|
||||
}
|
||||
|
||||
export let label: string | undefined = undefined
|
||||
export let field: string | undefined = undefined
|
||||
export let fieldState: any
|
||||
export let fieldApi: FieldApi
|
||||
export let fieldSchema: any
|
||||
export let fieldState: FieldState | undefined
|
||||
export let fieldApi: FieldApi | undefined
|
||||
export let fieldSchema: FieldSchema | undefined
|
||||
export let defaultValue: string | undefined = undefined
|
||||
export let type: any
|
||||
export let type: FieldType
|
||||
export let disabled = false
|
||||
export let readonly = false
|
||||
export let validation: any
|
||||
export let validation: FieldValidation | undefined
|
||||
export let span = 6
|
||||
export let helpText: string | undefined = undefined
|
||||
|
||||
// Get contexts
|
||||
const formContext: any = getContext("form")
|
||||
const formStepContext: any = getContext("form-step")
|
||||
const fieldGroupContext: any = getContext("field-group")
|
||||
const formContext = getContext("form")
|
||||
const formStepContext = getContext("form-step")
|
||||
const fieldGroupContext = getContext("field-group")
|
||||
const { styleable, builderStore, Provider } = getContext("sdk")
|
||||
const component: any = getContext("component")
|
||||
const component = getContext("component")
|
||||
|
||||
// Register field with form
|
||||
const formApi = formContext?.formApi
|
||||
const labelPos = fieldGroupContext?.labelPosition || "above"
|
||||
|
||||
let formField: any
|
||||
let formField: Readable<FormField> | undefined
|
||||
let touched = false
|
||||
let labelNode: any
|
||||
let labelNode: HTMLElement | undefined
|
||||
|
||||
// Memoize values required to register the field to avoid loops
|
||||
const formStep = formStepContext || writable(1)
|
||||
const fieldInfo = memo({
|
||||
const fieldInfo = memo<FieldInfo>({
|
||||
field: field || $component.name,
|
||||
type,
|
||||
defaultValue,
|
||||
|
@ -66,16 +83,22 @@
|
|||
$: $component.editing && labelNode?.focus()
|
||||
|
||||
// Update form properties in parent component on every store change
|
||||
$: unsubscribe = formField?.subscribe((value: any) => {
|
||||
fieldState = value?.fieldState
|
||||
fieldApi = value?.fieldApi
|
||||
fieldSchema = value?.fieldSchema
|
||||
})
|
||||
$: unsubscribe = formField?.subscribe(
|
||||
(value?: {
|
||||
fieldState: FieldState
|
||||
fieldApi: FieldApi
|
||||
fieldSchema: FieldSchema
|
||||
}) => {
|
||||
fieldState = value?.fieldState
|
||||
fieldApi = value?.fieldApi
|
||||
fieldSchema = value?.fieldSchema
|
||||
}
|
||||
)
|
||||
|
||||
// Determine label class from position
|
||||
$: labelClass = labelPos === "above" ? "" : `spectrum-FieldLabel--${labelPos}`
|
||||
|
||||
const registerField = (info: any) => {
|
||||
const registerField = (info: FieldInfo) => {
|
||||
formField = formApi?.registerField(
|
||||
info.field,
|
||||
info.type,
|
||||
|
@ -87,10 +110,10 @@
|
|||
)
|
||||
}
|
||||
|
||||
const updateLabel = (e: any) => {
|
||||
const updateLabel = (e: Event) => {
|
||||
if (touched) {
|
||||
// @ts-expect-error and TODO updateProp isn't recognised - need builder TS conversion
|
||||
builderStore.actions.updateProp("label", e.target.textContent)
|
||||
const label = e.target as HTMLLabelElement
|
||||
builderStore.actions.updateProp("label", label.textContent)
|
||||
}
|
||||
touched = false
|
||||
}
|
||||
|
|
|
@ -9,17 +9,19 @@
|
|||
RelationshipFieldMetadata,
|
||||
Row,
|
||||
} from "@budibase/types"
|
||||
import type { FieldApi, FieldState } from "."
|
||||
import type { FieldApi, FieldState, FieldValidation } from "@/types"
|
||||
|
||||
type ValueType = string | string[]
|
||||
|
||||
export let field: string | undefined = undefined
|
||||
export let label: string | undefined = undefined
|
||||
export let placeholder: string | undefined = undefined
|
||||
export let disabled: boolean = false
|
||||
export let readonly: boolean = false
|
||||
export let validation: any
|
||||
export let validation: FieldValidation | undefined = undefined
|
||||
export let autocomplete: boolean = true
|
||||
export let defaultValue: string | string[] | undefined = undefined
|
||||
export let onChange: any
|
||||
export let defaultValue: ValueType | undefined = undefined
|
||||
export let onChange: (_props: { value: ValueType }) => void
|
||||
export let filter: SearchFilter[]
|
||||
export let datasourceType: "table" | "user" = "table"
|
||||
export let primaryDisplay: string | undefined = undefined
|
||||
|
@ -88,14 +90,14 @@
|
|||
// Ensure backwards compatibility
|
||||
$: enrichedDefaultValue = enrichDefaultValue(defaultValue)
|
||||
|
||||
$: emptyValue = multiselect ? [] : undefined
|
||||
// We need to cast value to pass it down, as those components aren't typed
|
||||
$: emptyValue = multiselect ? [] : null
|
||||
$: displayValue = missingIDs.length ? emptyValue : (selectedValue as any)
|
||||
$: displayValue = (missingIDs.length ? emptyValue : selectedValue) as any
|
||||
|
||||
// Ensures that we flatten any objects so that only the IDs of the selected
|
||||
// rows are passed down. Not sure how this can be an object to begin with?
|
||||
const parseSelectedValue = (
|
||||
value: any,
|
||||
value: ValueType | undefined,
|
||||
multiselect: boolean
|
||||
): undefined | string | string[] => {
|
||||
return multiselect ? flatten(value) : flatten(value)[0]
|
||||
|
@ -140,7 +142,7 @@
|
|||
|
||||
// Builds a map of all available options, in a consistent structure
|
||||
const processOptions = (
|
||||
realValue: any | any[],
|
||||
realValue: ValueType | undefined,
|
||||
rows: Row[],
|
||||
primaryDisplay?: string
|
||||
) => {
|
||||
|
@ -171,7 +173,7 @@
|
|||
|
||||
// Parses a row-like structure into a properly shaped option
|
||||
const parseOption = (
|
||||
option: any | BasicRelatedRow | Row,
|
||||
option: string | BasicRelatedRow | Row,
|
||||
primaryDisplay?: string
|
||||
): BasicRelatedRow | null => {
|
||||
if (!option || typeof option !== "object" || !option?._id) {
|
||||
|
|
|
@ -19,15 +19,3 @@ export { default as codescanner } from "./CodeScannerField.svelte"
|
|||
export { default as signaturesinglefield } from "./SignatureField.svelte"
|
||||
export { default as bbreferencefield } from "./BBReferenceField.svelte"
|
||||
export { default as bbreferencesinglefield } from "./BBReferenceSingleField.svelte"
|
||||
|
||||
export interface FieldApi {
|
||||
setValue(value: any): boolean
|
||||
deregister(): void
|
||||
}
|
||||
|
||||
export interface FieldState<T> {
|
||||
value: T
|
||||
fieldId: string
|
||||
disabled: boolean
|
||||
readonly: boolean
|
||||
}
|
||||
|
|
|
@ -20,10 +20,8 @@ export { default as screenslot } from "./ScreenSlot.svelte"
|
|||
export { default as button } from "./Button.svelte"
|
||||
export { default as buttongroup } from "./ButtonGroup.svelte"
|
||||
export { default as repeater } from "./Repeater.svelte"
|
||||
export { default as text } from "./Text.svelte"
|
||||
export { default as layout } from "./Layout.svelte"
|
||||
export { default as link } from "./Link.svelte"
|
||||
export { default as heading } from "./Heading.svelte"
|
||||
export { default as image } from "./Image.svelte"
|
||||
export { default as embed } from "./Embed.svelte"
|
||||
export { default as icon } from "./Icon.svelte"
|
||||
|
@ -37,6 +35,7 @@ export { default as embeddedmap } from "./embedded-map/EmbeddedMap.svelte"
|
|||
export { default as sidepanel } from "./SidePanel.svelte"
|
||||
export { default as modal } from "./Modal.svelte"
|
||||
export { default as gridblock } from "./GridBlock.svelte"
|
||||
export { default as textv2 } from "./Text.svelte"
|
||||
export * from "./charts"
|
||||
export * from "./forms"
|
||||
export * from "./blocks"
|
||||
|
@ -50,3 +49,5 @@ export { default as cardhorizontal } from "./deprecated/CardHorizontal.svelte"
|
|||
export { default as stackedlist } from "./deprecated/StackedList.svelte"
|
||||
export { default as card } from "./deprecated/Card.svelte"
|
||||
export { default as section } from "./deprecated/Section.svelte"
|
||||
export { default as text } from "./deprecated/Text.svelte"
|
||||
export { default as heading } from "./deprecated/Heading.svelte"
|
||||
|
|
|
@ -1,7 +1,13 @@
|
|||
import { Component, Context, SDK } from "."
|
||||
import { Writable } from "svelte"
|
||||
import { Component, FieldGroupContext, FormContext } from "@/types"
|
||||
import { Readable } from "svelte/store"
|
||||
import { SDK } from "@/index.ts"
|
||||
|
||||
declare module "svelte" {
|
||||
export function getContext(key: "sdk"): SDK
|
||||
export function getContext(key: "component"): Component
|
||||
export function getContext(key: "context"): Context
|
||||
export function getContext(key: "context"): Readable<Record<string, any>>
|
||||
export function getContext(key: "form"): FormContext | undefined
|
||||
export function getContext(key: "form-step"): Writable<number> | undefined
|
||||
export function getContext(key: "field-group"): FieldGroupContext | undefined
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import ClientApp from "./components/ClientApp.svelte"
|
||||
import UpdatingApp from "./components/UpdatingApp.svelte"
|
||||
import {
|
||||
authStore,
|
||||
builderStore,
|
||||
appStore,
|
||||
blockStore,
|
||||
|
@ -11,11 +12,10 @@ import {
|
|||
hoverStore,
|
||||
stateStore,
|
||||
routeStore,
|
||||
notificationStore,
|
||||
} from "@/stores"
|
||||
import { get } from "svelte/store"
|
||||
import { initWebsocket } from "@/websocket"
|
||||
import { APIClient } from "@budibase/frontend-core"
|
||||
import type { ActionTypes } from "@/constants"
|
||||
import { Readable } from "svelte/store"
|
||||
import {
|
||||
Screen,
|
||||
|
@ -28,6 +28,8 @@ import {
|
|||
UIComponentError,
|
||||
CustomComponent,
|
||||
} from "@budibase/types"
|
||||
import { ActionTypes } from "@/constants"
|
||||
import { APIClient } from "@budibase/frontend-core"
|
||||
|
||||
// Provide svelte and svelte/internal as globals for custom components
|
||||
import * as svelte from "svelte"
|
||||
|
@ -73,6 +75,8 @@ declare global {
|
|||
}
|
||||
}
|
||||
|
||||
export type Context = Readable<Record<string, any>>
|
||||
|
||||
export interface SDK {
|
||||
API: APIClient
|
||||
styleable: any
|
||||
|
@ -80,27 +84,13 @@ export interface SDK {
|
|||
ActionTypes: typeof ActionTypes
|
||||
fetchDatasourceSchema: any
|
||||
generateGoldenSample: any
|
||||
builderStore: Readable<{
|
||||
inBuilder: boolean
|
||||
}> & {
|
||||
actions: {
|
||||
highlightSetting: (key: string) => void
|
||||
addParentComponent: (
|
||||
componentId: string,
|
||||
fullAncestorType: string
|
||||
) => void
|
||||
}
|
||||
}
|
||||
builderStore: typeof builderStore
|
||||
authStore: typeof authStore
|
||||
notificationStore: typeof notificationStore
|
||||
environmentStore: typeof environmentStore
|
||||
appStore: typeof appStore
|
||||
}
|
||||
|
||||
export type Component = Readable<{
|
||||
id: string
|
||||
styles: any
|
||||
errorState: boolean
|
||||
}>
|
||||
|
||||
export type Context = Readable<Record<string, any>>
|
||||
|
||||
let app: ClientApp
|
||||
|
||||
const loadBudibase = async () => {
|
||||
|
|
|
@ -6,6 +6,7 @@ const initialState = {
|
|||
isDevApp: false,
|
||||
clientLoadTime: window.INIT_TIME ? Date.now() - window.INIT_TIME : null,
|
||||
embedded: false,
|
||||
inIframe: window.self !== window.top,
|
||||
}
|
||||
|
||||
const createAppStore = () => {
|
||||
|
|
|
@ -1,38 +1,52 @@
|
|||
import { API } from "@/api"
|
||||
import { writable } from "svelte/store"
|
||||
import {
|
||||
AppSelfResponse,
|
||||
ContextUserMetadata,
|
||||
GetGlobalSelfResponse,
|
||||
} from "@budibase/types"
|
||||
|
||||
type AuthState = ContextUserMetadata | GetGlobalSelfResponse | undefined
|
||||
|
||||
const createAuthStore = () => {
|
||||
const store = writable<{
|
||||
csrfToken?: string
|
||||
} | null>(null)
|
||||
const store = writable<AuthState>()
|
||||
|
||||
const hasAppSelfUser = (
|
||||
user: AppSelfResponse | null
|
||||
): user is ContextUserMetadata => {
|
||||
return user != null && "_id" in user
|
||||
}
|
||||
|
||||
// Fetches the user object if someone is logged in and has reloaded the page
|
||||
const fetchUser = async () => {
|
||||
let globalSelf = null
|
||||
let appSelf = null
|
||||
let globalSelf, appSelf
|
||||
|
||||
// First try and get the global user, to see if we are logged in at all
|
||||
try {
|
||||
globalSelf = await API.fetchBuilderSelf()
|
||||
} catch (error) {
|
||||
store.set(null)
|
||||
store.set(undefined)
|
||||
return
|
||||
}
|
||||
|
||||
// Then try and get the user for this app to provide via context
|
||||
try {
|
||||
appSelf = await API.fetchSelf()
|
||||
const res = await API.fetchSelf()
|
||||
if (hasAppSelfUser(res)) {
|
||||
appSelf = res
|
||||
}
|
||||
} catch (error) {
|
||||
// Swallow
|
||||
}
|
||||
|
||||
// Use the app self if present, otherwise fallback to the global self
|
||||
store.set(appSelf || globalSelf || null)
|
||||
store.set(appSelf || globalSelf)
|
||||
}
|
||||
|
||||
const logOut = async () => {
|
||||
try {
|
||||
await API.logOut()
|
||||
window.location.href = "/"
|
||||
} catch (error) {
|
||||
// Do nothing
|
||||
}
|
||||
|
|
|
@ -1,13 +1,23 @@
|
|||
import { API } from "@/api"
|
||||
import { writable } from "svelte/store"
|
||||
import type { GetEnvironmentResponse } from "@budibase/types"
|
||||
|
||||
const initialState = {
|
||||
loaded: false,
|
||||
interface EnvironmentState extends GetEnvironmentResponse {
|
||||
loaded: boolean
|
||||
}
|
||||
|
||||
const initialState: EnvironmentState = {
|
||||
multiTenancy: false,
|
||||
offlineMode: false,
|
||||
cloud: false,
|
||||
disableAccountPortal: false,
|
||||
isDev: false,
|
||||
maintenance: [],
|
||||
loaded: false,
|
||||
}
|
||||
|
||||
const createEnvironmentStore = () => {
|
||||
const store = writable(initialState)
|
||||
const store = writable<EnvironmentState>(initialState)
|
||||
|
||||
const actions = {
|
||||
fetchEnvironment: async () => {
|
|
@ -0,0 +1,9 @@
|
|||
import { Readable } from "svelte/store"
|
||||
|
||||
export type Component = Readable<{
|
||||
id: string
|
||||
name: string
|
||||
styles: any
|
||||
editing: boolean
|
||||
errorState: boolean
|
||||
}>
|
|
@ -0,0 +1,3 @@
|
|||
export interface FieldGroupContext {
|
||||
labelPosition: string
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
import { Readable } from "svelte/store"
|
||||
import { FieldSchema, FieldType } from "@budibase/types"
|
||||
|
||||
export interface FormContext {
|
||||
formApi?: {
|
||||
registerField: (
|
||||
field: string,
|
||||
type: FieldType,
|
||||
defaultValue: string | undefined,
|
||||
disabled: boolean,
|
||||
readonly: boolean,
|
||||
validation: FieldValidation | undefined,
|
||||
formStep: number
|
||||
) => Readable<FormField>
|
||||
}
|
||||
}
|
||||
|
||||
export type FieldValidation = () => string | undefined
|
||||
|
||||
export interface FormField {
|
||||
fieldState: FieldState
|
||||
fieldApi: FieldApi
|
||||
fieldSchema: FieldSchema
|
||||
}
|
||||
|
||||
export interface FieldApi {
|
||||
setValue(value: any): boolean
|
||||
deregister(): void
|
||||
}
|
||||
|
||||
export interface FieldState<T = any> {
|
||||
value: T
|
||||
fieldId: string
|
||||
disabled: boolean
|
||||
readonly: boolean
|
||||
error?: string
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
export * from "./components"
|
||||
export * from "./fields"
|
||||
export * from "./forms"
|
|
@ -0,0 +1,43 @@
|
|||
<script lang="ts">
|
||||
import { ModalContent, Body, notifications } from "@budibase/bbui"
|
||||
import PasswordRepeatInput from "./PasswordRepeatInput.svelte"
|
||||
import type { APIClient } from "@budibase/frontend-core"
|
||||
import { createEventDispatcher } from "svelte"
|
||||
|
||||
export let API: APIClient
|
||||
export let passwordMinLength: string | undefined = undefined
|
||||
export let notifySuccess = notifications.success
|
||||
export let notifyError = notifications.error
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
let password: string = ""
|
||||
let error: string = ""
|
||||
|
||||
const updatePassword = async () => {
|
||||
try {
|
||||
await API.updateSelf({ password })
|
||||
notifySuccess("Password changed successfully")
|
||||
dispatch("save")
|
||||
} catch (error) {
|
||||
notifyError("Failed to update password")
|
||||
}
|
||||
}
|
||||
|
||||
const handleKeydown = (evt: KeyboardEvent) => {
|
||||
if (evt.key === "Enter" && !error && password) {
|
||||
updatePassword()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:window on:keydown={handleKeydown} />
|
||||
<ModalContent
|
||||
title="Update password"
|
||||
confirmText="Update password"
|
||||
onConfirm={updatePassword}
|
||||
disabled={!!error || !password}
|
||||
>
|
||||
<Body size="S">Enter your new password below.</Body>
|
||||
<PasswordRepeatInput bind:password bind:error minLength={passwordMinLength} />
|
||||
</ModalContent>
|
|
@ -1,20 +1,14 @@
|
|||
<script>
|
||||
import { FancyForm, FancyInput } from "@budibase/bbui"
|
||||
import {
|
||||
createValidationStore,
|
||||
requiredValidator,
|
||||
} from "@/helpers/validation"
|
||||
import { admin } from "@/stores/portal"
|
||||
import { createValidationStore, requiredValidator } from "../utils/validation"
|
||||
|
||||
export let password
|
||||
export let passwordForm
|
||||
export let error
|
||||
|
||||
$: passwordMinLength = $admin.passwordMinLength ?? 12
|
||||
export let minLength = "12"
|
||||
|
||||
const validatePassword = value => {
|
||||
if (!value || value.length < passwordMinLength) {
|
||||
return `Please enter at least ${passwordMinLength} characters. We recommend using machine generated or random passwords.`
|
||||
if (!value || value.length < minLength) {
|
||||
return `Please enter at least ${minLength} characters. We recommend using machine generated or random passwords.`
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
@ -41,7 +35,7 @@
|
|||
firstPasswordError
|
||||
</script>
|
||||
|
||||
<FancyForm bind:this={passwordForm}>
|
||||
<FancyForm>
|
||||
<FancyInput
|
||||
label="Password"
|
||||
type="password"
|
|
@ -0,0 +1,39 @@
|
|||
<script lang="ts">
|
||||
import { writable } from "svelte/store"
|
||||
import { ModalContent, Body, Input, notifications } from "@budibase/bbui"
|
||||
import type { User, ContextUser } from "@budibase/types"
|
||||
import type { APIClient } from "@budibase/frontend-core"
|
||||
import { createEventDispatcher } from "svelte"
|
||||
|
||||
export let user: User | ContextUser | undefined = undefined
|
||||
export let API: APIClient
|
||||
export let notifySuccess = notifications.success
|
||||
export let notifyError = notifications.error
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
const values = writable({
|
||||
firstName: user?.firstName,
|
||||
lastName: user?.lastName,
|
||||
})
|
||||
|
||||
const updateInfo = async () => {
|
||||
try {
|
||||
await API.updateSelf($values)
|
||||
notifySuccess("Information updated successfully")
|
||||
dispatch("save")
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
notifyError("Failed to update information")
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<ModalContent title="My profile" confirmText="Save" onConfirm={updateInfo}>
|
||||
<Body size="S">
|
||||
Personalise the platform by adding your first name and last name.
|
||||
</Body>
|
||||
<Input disabled value={user?.email || ""} label="Email" />
|
||||
<Input bind:value={$values.firstName} label="First name" />
|
||||
<Input bind:value={$values.lastName} label="Last name" />
|
||||
</ModalContent>
|
|
@ -56,7 +56,7 @@
|
|||
rowIdx={row?.__idx}
|
||||
metadata={row?.__metadata?.row}
|
||||
>
|
||||
<div class="gutter">
|
||||
<div class="gutter" class:selectable={$config.canSelectRows}>
|
||||
{#if $$slots.default}
|
||||
<slot />
|
||||
{:else}
|
||||
|
@ -116,12 +116,9 @@
|
|||
margin: 3px 0 0 0;
|
||||
}
|
||||
.number {
|
||||
color: val(--cell-font-color, var(--spectrum-global-color-gray-500));
|
||||
}
|
||||
.checkbox.visible,
|
||||
.number.visible {
|
||||
display: flex;
|
||||
color: var(--spectrum-global-color-gray-500);
|
||||
}
|
||||
|
||||
.delete,
|
||||
.expand {
|
||||
margin-right: 4px;
|
||||
|
@ -137,4 +134,11 @@
|
|||
.delete:hover :global(.spectrum-Icon) {
|
||||
color: var(--spectrum-global-color-red-600) !important;
|
||||
}
|
||||
|
||||
/* Visibility of checkbox and number */
|
||||
.gutter.selectable .checkbox.visible,
|
||||
.number.visible,
|
||||
.gutter:not(.selectable) .number {
|
||||
display: flex;
|
||||
}
|
||||
</style>
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue