1
0
Fork 0
mirror of synced 2024-08-08 14:48:13 +12:00

Merge branch 'master' of github.com:Budibase/budibase into prefill-cards

This commit is contained in:
Andrew Kingston 2024-05-21 12:03:16 +01:00
commit 16fd09e4c9
76 changed files with 3532 additions and 707 deletions

View file

@ -17,6 +17,8 @@
export let customPopoverHeight
export let open = false
export let loading
export let onOptionMouseenter = () => {}
export let onOptionMouseleave = () => {}
const dispatch = createEventDispatcher()
@ -97,4 +99,6 @@
{autoWidth}
{customPopoverHeight}
{loading}
{onOptionMouseenter}
{onOptionMouseleave}
/>

View file

@ -41,6 +41,8 @@
export let footer = null
export let customAnchor = null
export let loading
export let onOptionMouseenter = () => {}
export let onOptionMouseleave = () => {}
const dispatch = createEventDispatcher()
@ -199,6 +201,8 @@
aria-selected="true"
tabindex="0"
on:click={() => onSelectOption(getOptionValue(option, idx))}
on:mouseenter={e => onOptionMouseenter(e, option)}
on:mouseleave={e => onOptionMouseleave(e, option)}
class:is-disabled={!isOptionEnabled(option)}
>
{#if getOptionIcon(option, idx)}

View file

@ -26,6 +26,8 @@
export let tag = null
export let searchTerm = null
export let loading
export let onOptionMouseenter = () => {}
export let onOptionMouseleave = () => {}
const dispatch = createEventDispatcher()
@ -95,6 +97,8 @@
{autocomplete}
{sort}
{tag}
{onOptionMouseenter}
{onOptionMouseleave}
isPlaceholder={value == null || value === ""}
placeholderOption={placeholder === false ? null : placeholder}
isOptionSelected={option => compareOptionAndValue(option, value)}

View file

@ -19,6 +19,8 @@
export let searchTerm = null
export let customPopoverHeight
export let helpText = null
export let onOptionMouseenter = () => {}
export let onOptionMouseleave = () => {}
const dispatch = createEventDispatcher()
const onChange = e => {
@ -41,6 +43,8 @@
{autoWidth}
{autocomplete}
{customPopoverHeight}
{onOptionMouseenter}
{onOptionMouseleave}
bind:searchTerm
on:change={onChange}
on:click

View file

@ -29,6 +29,9 @@
export let tag = null
export let helpText = null
export let compare
export let onOptionMouseenter = () => {}
export let onOptionMouseleave = () => {}
const dispatch = createEventDispatcher()
const onChange = e => {
value = e.detail
@ -67,6 +70,8 @@
{customPopoverHeight}
{tag}
{compare}
{onOptionMouseenter}
{onOptionMouseleave}
on:change={onChange}
on:click
/>

View file

@ -1,11 +1,14 @@
<script>
import { getContext, onDestroy } from "svelte"
import { getContext, onDestroy, createEventDispatcher } from "svelte"
import Portal from "svelte-portal"
export let title
export let icon = ""
export let id
export let href = "#"
export let link = false
const dispatch = createEventDispatcher()
let selected = getContext("tab")
let observer
let ref
@ -26,6 +29,18 @@
}
}
const onAnchorClick = e => {
if (e.metaKey || e.shiftKey || e.altKey || e.ctrlKey) return
e.preventDefault()
$selected = {
...$selected,
title,
info: ref.getBoundingClientRect(),
}
dispatch("click")
}
const onClick = () => {
$selected = {
...$selected,
@ -51,31 +66,56 @@
onDestroy(stopObserving)
</script>
<!-- svelte-ignore a11y-no-static-element-interactions -->
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
<div
{id}
bind:this={ref}
on:click={onClick}
on:click
class="spectrum-Tabs-item"
class:is-selected={isSelected}
class:emphasized={isSelected && $selected.emphasized}
tabindex="0"
>
{#if icon}
<svg
class="spectrum-Icon spectrum-Icon--sizeM"
focusable="false"
aria-hidden="true"
aria-label="Folder"
>
<use xlink:href="#spectrum-icon-18-{icon}" />
</svg>
{/if}
<span class="spectrum-Tabs-itemLabel">{title}</span>
</div>
{#if link}
<a
{href}
{id}
bind:this={ref}
on:click={onAnchorClick}
class="spectrum-Tabs-item link"
class:is-selected={isSelected}
class:emphasized={isSelected && $selected.emphasized}
tabindex="0"
>
{#if icon}
<svg
class="spectrum-Icon spectrum-Icon--sizeM"
focusable="false"
aria-hidden="true"
aria-label="Folder"
>
<use xlink:href="#spectrum-icon-18-{icon}" />
</svg>
{/if}
<span class="spectrum-Tabs-itemLabel">{title}</span>
</a>
{:else}
<!-- svelte-ignore a11y-no-static-element-interactions -->
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
<div
{id}
bind:this={ref}
on:click={onClick}
on:click
class="spectrum-Tabs-item"
class:is-selected={isSelected}
class:emphasized={isSelected && $selected.emphasized}
tabindex="0"
>
{#if icon}
<svg
class="spectrum-Icon spectrum-Icon--sizeM"
focusable="false"
aria-hidden="true"
aria-label="Folder"
>
<use xlink:href="#spectrum-icon-18-{icon}" />
</svg>
{/if}
<span class="spectrum-Tabs-itemLabel">{title}</span>
</div>
{/if}
{#if isSelected}
<Portal target=".spectrum-Tabs-content-{$selected.id}">
@ -94,4 +134,7 @@
.spectrum-Tabs-item:hover {
color: var(--spectrum-global-color-gray-900);
}
.link {
user-select: none;
}
</style>

View file

@ -0,0 +1,79 @@
<script>
import Portal from "svelte-portal"
import { getContext } from "svelte"
import Context from "../context"
export let anchor
export let visible = false
export let offset = 0
$: target = getContext(Context.PopoverRoot) || "#app"
let hovering = false
let tooltip
let x = 0
let y = 0
const updatePosition = (anchor, tooltip) => {
if (anchor == null || tooltip == null) {
return
}
requestAnimationFrame(() => {
const rect = anchor.getBoundingClientRect()
const windowOffset =
window.innerHeight - offset - (tooltip.clientHeight + rect.y)
const tooltipWidth = tooltip.clientWidth
x = rect.x - tooltipWidth - offset
y = windowOffset < 0 ? rect.y + windowOffset : rect.y
})
}
$: updatePosition(anchor, tooltip)
const handleMouseenter = () => {
hovering = true
}
const handleMouseleave = () => {
hovering = false
}
</script>
<Portal {target}>
<div
role="tooltip"
on:mouseenter={handleMouseenter}
on:mouseleave={handleMouseleave}
style:left={`${x}px`}
style:top={`${y}px`}
class="wrapper"
class:visible={visible || hovering}
>
<div bind:this={tooltip} class="tooltip">
<slot />
</div>
</div>
</Portal>
<style>
.wrapper {
background-color: var(--spectrum-global-color-gray-100);
box-shadow: 2px 2px 5px 0px rgba(0, 0, 0, 0.42);
opacity: 0;
overflow: hidden;
border-radius: 5px;
box-sizing: border-box;
border: 1px solid var(--grey-4);
position: absolute;
pointer-events: none;
z-index: 1000;
}
.visible {
opacity: 1;
pointer-events: auto;
}
</style>

View file

@ -53,6 +53,7 @@ export { default as Link } from "./Link/Link.svelte"
export { default as Tooltip } from "./Tooltip/Tooltip.svelte"
export { default as TempTooltip } from "./Tooltip/TempTooltip.svelte"
export { default as TooltipWrapper } from "./Tooltip/TooltipWrapper.svelte"
export { default as ContextTooltip } from "./Tooltip/Context.svelte"
export { default as Menu } from "./Menu/Menu.svelte"
export { default as MenuSection } from "./Menu/Section.svelte"
export { default as MenuSeparator } from "./Menu/Separator.svelte"

View file

@ -56,7 +56,7 @@ export function getBindings({
)
}
const field = Object.values(FIELDS).find(
field => field.type === schema.type && field.subtype === schema.subtype
field => field.type === schema.type
)
const label = path == null ? column : `${path}.0.${column}`

View file

@ -54,6 +54,7 @@
export let autofocus = false
export let jsBindingWrapping = true
export let readonly = false
export let readonlyLineNumbers = false
const dispatch = createEventDispatcher()
@ -240,6 +241,9 @@
if (readonly) {
complete.push(EditorState.readOnly.of(true))
if (readonlyLineNumbers) {
complete.push(lineNumbers())
}
} else {
complete = [
...complete,

View file

@ -0,0 +1,48 @@
<script>
import { ContextTooltip } from "@budibase/bbui"
import {
StringsAsDates,
NumbersAsDates,
ScalarJsonOnly,
Column,
Support,
NotRequired,
StringsAsNumbers,
DatesAsNumbers,
} from "./subjects"
import subjects from "../subjects"
export let anchor
export let schema
export let columnName
export let subject = subjects.none
</script>
<ContextTooltip visible={subject !== subjects.none} {anchor} offset={20}>
<div class="explanationModalContent">
{#if subject === subjects.column}
<Column {columnName} {schema} />
{:else if subject === subjects.support}
<Support />
{:else if subject === subjects.stringsAsNumbers}
<StringsAsNumbers />
{:else if subject === subjects.notRequired}
<NotRequired />
{:else if subject === subjects.datesAsNumbers}
<DatesAsNumbers />
{:else if subject === subjects.scalarJsonOnly}
<ScalarJsonOnly {columnName} {schema} />
{:else if subject === subjects.numbersAsDates}
<NumbersAsDates {columnName} />
{:else if subject === subjects.stringsAsDates}
<StringsAsDates {columnName} />
{/if}
</div>
</ContextTooltip>
<style>
.explanationModalContent {
max-width: 300px;
padding: 16px 12px 2px;
}
</style>

View file

@ -0,0 +1,147 @@
<script>
import { tables } from "stores/builder"
import {
BindingValue,
Block,
Subject,
JSONValue,
Property,
Section,
} from "./components"
export let schema
export let columnName
const parseDate = isoString => {
if ([null, undefined, ""].includes(isoString)) {
return "None"
}
const unixTime = Date.parse(isoString)
const date = new Date(unixTime)
return date.toLocaleString()
}
</script>
<Subject>
<div class="heading" slot="heading">
Column Overview for <Block>{columnName}</Block>
</div>
<Section>
{#if schema.type === "string"}
<Property
name="Max Length"
value={schema?.constraints?.length?.maximum ?? "None"}
/>
{:else if schema.type === "datetime"}
<Property
name="Earliest"
value={parseDate(schema?.constraints?.datetime?.earliest)}
/>
<Property
name="Latest"
value={parseDate(schema?.constraints?.datetime?.latest)}
/>
<Property
name="Ignore time zones"
value={schema?.ignoreTimeZones === true ? "Yes" : "No"}
/>
<Property
name="Date only"
value={schema?.dateOnly === true ? "Yes" : "No"}
/>
{:else if schema.type === "number"}
<Property
name="Min Value"
value={[null, undefined, ""].includes(
schema?.constraints?.numericality?.greaterThanOrEqualTo
)
? "None"
: schema?.constraints?.numericality?.greaterThanOrEqualTo}
/>
<Property
name="Max Value"
value={[null, undefined, ""].includes(
schema?.constraints?.numericality?.lessThanOrEqualTo
)
? "None"
: schema?.constraints?.numericality?.lessThanOrEqualTo}
/>
{:else if schema.type === "array"}
{#each schema?.constraints?.inclusion ?? [] as option, index}
<Property name={`Option ${index + 1}`} truncate>
<span
style:background-color={schema?.optionColors?.[option]}
class="optionCircle"
/>{option}
</Property>
{/each}
{:else if schema.type === "options"}
{#each schema?.constraints?.inclusion ?? [] as option, index}
<Property name={`Option ${index + 1}`} truncate>
<span
style:background-color={schema?.optionColors?.[option]}
class="optionCircle"
/>{option}
</Property>
{/each}
{:else if schema.type === "json"}
<Property name="Schema">
<JSONValue value={JSON.stringify(schema?.schema ?? {}, null, 2)} />
</Property>
{:else if schema.type === "formula"}
<Property name="Formula">
<BindingValue value={schema?.formula} />
</Property>
<Property
name="Formula type"
value={schema?.formulaType === "dynamic" ? "Dynamic" : "Static"}
/>
{:else if schema.type === "link"}
<Property name="Type" value={schema?.relationshipType} />
<Property
name="Related Table"
value={$tables?.list?.find(table => table._id === schema?.tableId)
?.name}
/>
<Property name="Column in Related Table" value={schema?.fieldName} />
{:else if schema.type === "bb_reference"}
<Property
name="Allow multiple users"
value={schema?.relationshipType === "many-to-many" ? "Yes" : "No"}
/>
{/if}
<Property
name="Required"
value={schema?.constraints?.presence?.allowEmpty === false ? "Yes" : "No"}
/>
</Section>
</Subject>
<style>
.heading {
display: flex;
align-items: center;
}
.heading > :global(.block) {
margin-left: 4px;
flex-grow: 0;
flex-shrink: 1;
display: inline-block;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.optionCircle {
display: inline-block;
background-color: hsla(0, 1%, 50%, 0.3);
border-radius: 100%;
width: 10px;
height: 10px;
vertical-align: middle;
margin-right: 5px;
}
</style>

View file

@ -0,0 +1,63 @@
<script>
import { onMount } from "svelte"
import {
ExampleSection,
ExampleLine,
Block,
Subject,
Section,
} from "./components"
let timestamp = Date.now()
onMount(() => {
let run = true
const updateTimeStamp = () => {
timestamp = Date.now()
if (run) {
setTimeout(updateTimeStamp, 200)
}
}
updateTimeStamp()
return () => {
run = false
}
})
</script>
<Subject heading="Dates as Numbers">
<Section>
A datetime value can be used in place of a numeric value, but it will be
converted to a <Block>UNIX time</Block> timestamp, which is the number of milliseconds
since Jan 1st 1970. A more recent moment in time will be a higher number.
</Section>
<ExampleSection heading="Examples:">
<ExampleLine>
<Block>
{new Date(946684800000).toLocaleString()}
</Block>
<span class="separator">{"->"} </span><Block>946684800000</Block>
</ExampleLine>
<ExampleLine>
<Block>
{new Date(1577836800000).toLocaleString()}
</Block>
<span class="separator">{"->"} </span><Block>1577836800000</Block>
</ExampleLine>
<ExampleLine>
<Block>Now</Block><span class="separator">{"->"} </span><Block
>{timestamp}</Block
>
</ExampleLine>
</ExampleSection>
</Subject>
<style>
.separator {
margin: 0 5px;
}
</style>

View file

@ -0,0 +1,11 @@
<script>
import { Block, Subject, Section } from "./components"
</script>
<Subject heading="Required Constraint">
<Section>
A <Block>required</Block> constraint can be applied to columns to ensure a value
is always present. If a column doesn't have this constraint, then its value for
a particular row could he missing.
</Section>
</Subject>

View file

@ -0,0 +1,65 @@
<script>
import { onMount } from "svelte"
import {
ExampleSection,
ExampleLine,
Block,
Subject,
Section,
} from "./components"
let timestamp = Date.now()
onMount(() => {
let run = true
const updateTimeStamp = () => {
timestamp = Date.now()
if (run) {
setTimeout(updateTimeStamp, 200)
}
}
updateTimeStamp()
return () => {
run = false
}
})
</script>
<Subject heading="Numbers as Dates">
<Section>
A number value can be used in place of a datetime value, but it will be
parsed as a <Block>UNIX time</Block> timestamp, which is the number of milliseconds
since Jan 1st 1970. A more recent moment in time will be a higher number.
</Section>
<ExampleSection heading="Examples:">
<ExampleLine>
<Block>946684800000</Block>
<span class="separator">{"->"}</span>
<Block>
{new Date(946684800000).toLocaleString()}
</Block>
</ExampleLine>
<ExampleLine>
<Block>1577836800000</Block>
<span class="separator">{"->"}</span>
<Block>
{new Date(1577836800000).toLocaleString()}
</Block>
</ExampleLine>
<ExampleLine>
<Block>{timestamp}</Block>
<span class="separator">{"->"}</span>
<Block>Now</Block>
</ExampleLine>
</ExampleSection>
</Subject>
<style>
.separator {
margin: 0 5px;
}
</style>

View file

@ -0,0 +1,71 @@
<script>
import {
ExampleSection,
ExampleLine,
Block,
Subject,
Section,
} from "./components"
export let schema
export let columnName
const maxScalarDescendantsToFind = 3
const getScalarDescendants = schema => {
const newScalarDescendants = []
const getScalarDescendantFromSchema = (path, schema) => {
if (newScalarDescendants.length >= maxScalarDescendantsToFind) {
return
}
if (["string", "number", "boolean"].includes(schema.type)) {
newScalarDescendants.push({ name: path.join("."), type: schema.type })
} else if (schema.type === "json") {
Object.entries(schema.schema ?? {}).forEach(
([childName, childSchema]) =>
getScalarDescendantFromSchema([...path, childName], childSchema)
)
}
}
Object.entries(schema?.schema ?? {}).forEach(([childName, childSchema]) =>
getScalarDescendantFromSchema([columnName, childName], childSchema)
)
return newScalarDescendants
}
$: scalarDescendants = getScalarDescendants(schema)
</script>
<Subject heading="Using Scalar JSON Values">
<Section>
<Block>JSON objects</Block> can't be used here, but any <Block>number</Block
>, <Block>string</Block> or <Block>boolean</Block> values nested within said
object can be if they are otherwise compatible with the input. These scalar values
can be selected from the same menu as this parent and take the form <Block
>parent.child</Block
>.
</Section>
{#if scalarDescendants.length > 0}
<ExampleSection heading="Examples scalar descendants of this object:">
{#each scalarDescendants as descendant}
<ExampleLine>
<Block truncate>{descendant.name}</Block><span class="separator"
>-</span
><Block truncate noShrink>{descendant.type}</Block>
</ExampleLine>
{/each}
</ExampleSection>
{/if}
</Subject>
<style>
.separator {
margin: 0 4px;
flex-shrink: 0;
}
</style>

View file

@ -0,0 +1,107 @@
<script>
import { onMount } from "svelte"
import {
ExampleSection,
ExampleLine,
Block,
Subject,
Section,
} from "./components"
let timestamp = Date.now()
$: iso = new Date(timestamp).toISOString()
onMount(() => {
let run = true
const updateTimeStamp = () => {
timestamp = Date.now()
if (run) {
setTimeout(updateTimeStamp, 200)
}
}
updateTimeStamp()
return () => {
run = false
}
})
</script>
<Subject heading="Strings as Dates">
<Section>
A string value can be used in place of a datetime value, but it will be
parsed as:
</Section>
<Section>
A <Block>UNIX time</Block> timestamp, which is the number of milliseconds since
Jan 1st 1970. A more recent moment in time will be a higher number.
</Section>
<ExampleSection heading="Examples:">
<ExampleLine>
<Block>946684800000</Block>
<span class="separator">{"->"}</span>
<Block>
{new Date(946684800000).toLocaleString()}
</Block>
</ExampleLine>
<ExampleLine>
<Block>1577836800000</Block>
<span class="separator">{"->"}</span>
<Block>
{new Date(1577836800000).toLocaleString()}
</Block>
</ExampleLine>
<ExampleLine>
<Block>{timestamp}</Block>
<span class="separator">{"->"}</span>
<Block>Now</Block>
</ExampleLine>
</ExampleSection>
<Section>
An <Block>ISO 8601</Block> datetime string, which represents an exact moment
in time as well as the potentional to store the timezone it occured in.
</Section>
<div class="isoExamples">
<ExampleSection heading="Examples:">
<ExampleLine>
<Block>2000-01-01T00:00:00.000Z</Block>
<span class="separator"></span>
<Block>
{new Date(946684800000).toLocaleString()}
</Block>
</ExampleLine>
<ExampleLine>
<Block>2000-01-01T00:00:00.000Z</Block>
<span class="separator"></span>
<Block>
{new Date(1577836800000).toLocaleString()}
</Block>
</ExampleLine>
<ExampleLine>
<Block>{iso}</Block>
<span class="separator"></span>
<Block>Now</Block>
</ExampleLine>
</ExampleSection>
</div>
</Subject>
<style>
.separator {
margin: 0 5px;
}
.isoExamples :global(.block) {
word-break: break-all;
}
.isoExamples :global(.exampleLine) {
align-items: center;
flex-direction: column;
margin-bottom: 16px;
width: 162px;
}
</style>

View file

@ -0,0 +1,56 @@
<script>
import {
ExampleSection,
ExampleLine,
Block,
Subject,
Section,
} from "./components"
</script>
<Subject heading="Text as Numbers">
<Section>
Text can be used in place of numbers in certain scenarios, but care needs to
be taken; if the value isn't purely numerical it may be converted in an
unexpected way.
</Section>
<ExampleSection heading="Examples:">
<ExampleLine>
<Block>"100"</Block><span class="separator">{"->"}</span><Block>100</Block
>
</ExampleLine>
<ExampleLine>
<Block>"100k"</Block><span class="separator">{"->"}</span><Block
>100</Block
>
</ExampleLine>
<ExampleLine>
<Block>"100,000"</Block><span class="separator">{"->"}</span><Block
>100</Block
>
</ExampleLine>
<ExampleLine>
<Block>"100 million"</Block><span class="separator">{"->"}</span><Block
>100</Block
>
</ExampleLine>
<ExampleLine>
<Block>"100.9"</Block><span class="separator">{"->"}</span><Block
>100.9</Block
>
</ExampleLine>
<ExampleLine>
<Block>"One hundred"</Block><span class="separator">{"->"}</span><Block
>Error</Block
>
</ExampleLine>
</ExampleSection>
</Subject>
<style>
.separator {
margin: 0 4px;
flex-shrink: 0;
}
</style>

View file

@ -0,0 +1,35 @@
<script>
import { InfoWord } from "../../typography"
import { Subject, Section } from "./components"
</script>
<Subject heading="Data/Component Compatibility">
<Section>
<InfoWord icon="CheckmarkCircle" color="var(--green)" text="Compatible" />
<span class="body"
>Fully compatible with the input as long as the data is present.</span
>
</Section>
<Section>
<InfoWord
icon="AlertCheck"
color="var(--yellow)"
text="Partially compatible"
/>
<span class="body"
>Partially compatible with the input, but beware of other caveats
mentioned.</span
>
</Section>
<Section>
<InfoWord icon="Alert" color="var(--red)" text="Not compatible" />
<span class="body">Incompatible with the component.</span>
</Section>
</Subject>
<style>
.body {
display: block;
margin-top: 5px;
}
</style>

View file

@ -0,0 +1,39 @@
<script>
import { decodeJSBinding } from "@budibase/string-templates"
import CodeEditor from "components/common/CodeEditor/CodeEditor.svelte"
import { EditorModes } from "components/common/CodeEditor"
import {
runtimeToReadableBinding,
getDatasourceForProvider,
} from "dataBinding"
import { tables, selectedScreen, selectedComponent } from "stores/builder"
import { getBindings } from "components/backend/DataTable/formula"
export let value
$: datasource = getDatasourceForProvider($selectedScreen, $selectedComponent)
$: tableId = datasource.tableId
$: table = $tables?.list?.find(table => table._id === tableId)
$: bindings = getBindings({ table })
$: readableBinding = runtimeToReadableBinding(bindings, value)
$: isJs = value?.startsWith?.("{{ js ")
</script>
<div class="editor">
<CodeEditor
readonly
readonlyLineNumbers
value={isJs ? decodeJSBinding(readableBinding) : readableBinding}
jsBindingWrapping={isJs}
mode={isJs ? EditorModes.JS : EditorModes.Handlebars}
/>
</div>
<style>
.editor {
border: 1px solid var(--grey-2);
border-radius: 2px;
overflow: hidden;
}
</style>

View file

@ -0,0 +1,30 @@
<script>
export let truncate = false
export let noShrink = false
</script>
<span class:truncate class:noShrink class="block">
<slot />
</span>
<style>
.block {
font-style: italic;
border-radius: 1px;
padding: 0px 5px 0px 3px;
border-radius: 1px;
background-color: var(--grey-3);
color: var(--ink);
word-break: break-word;
}
.truncate {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.noShrink {
flex-shrink: 0;
}
</style>

View file

@ -0,0 +1,12 @@
<li>
<div class="exampleLine">
<slot />
</div>
</li>
<style>
.exampleLine {
display: flex;
margin-bottom: 2px;
}
</style>

View file

@ -0,0 +1,32 @@
<script>
import Section from "./Section.svelte"
export let heading
</script>
<Section>
<span class="exampleSectionHeading">
<slot name="heading">
{heading}
</slot>
</span>
<ul>
<slot />
</ul>
</Section>
<style>
.exampleSectionHeading {
display: inline-block;
margin-bottom: 2px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 100%;
}
ul {
padding: 0 0 0 23px;
margin: 0;
}
</style>

View file

@ -0,0 +1,22 @@
<script>
export let value
</script>
<pre class="pre">
{value}
</pre>
<style>
.pre {
border: 1px solid var(--grey-2);
border-radius: 2px;
overflow: hidden;
margin: 0;
margin-top: 3px;
padding: 4px;
border-radius: 3px;
width: 250px;
box-sizing: border-box;
background-color: black;
}
</style>

View file

@ -0,0 +1,49 @@
<script>
export let name
export let value
export let truncate = false
</script>
<div class:truncate class="property">
<span class="propertyName">
<slot name="name">
{name}
</slot>
</span>
<span class="propertyDivider">-</span>
<span class="propertyValue">
<slot>
{value}
</slot>
</span>
</div>
<style>
.property {
max-width: 100%;
margin-bottom: 8px;
}
.truncate {
display: flex;
align-items: center;
overflow: hidden;
}
.propertyName {
font-weight: 600;
flex-shrink: 0;
}
.propertyDivider {
padding: 0 4px;
flex-shrink: 0;
}
.propertyValue {
word-break: break-word;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
</style>

View file

@ -0,0 +1,11 @@
<div class="section">
<slot />
</div>
<style>
.section {
line-height: 20px;
margin-bottom: 13px;
overflow: hidden;
}
</style>

View file

@ -0,0 +1,51 @@
<script>
import { onMount } from "svelte"
export let heading = ""
let body
const handleScroll = e => {
if (!body) return
body.scrollTo({ top: body.scrollTop + e.deltaY, behavior: "smooth" })
}
onMount(() => {
window.addEventListener("wheel", handleScroll)
return () => {
window.removeEventListener("wheel", handleScroll)
}
})
</script>
<div class="heading">
<span class="heading">
<slot name="heading">
{heading}
</slot>
</span>
</div>
<div class="divider" />
<div bind:this={body} class="body">
<slot />
</div>
<style>
.heading {
font-weight: 600;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.divider {
border-bottom: 1px solid var(--grey-4);
margin: 12px 0 12px;
}
.body {
max-height: 300px;
overflow-y: auto;
}
</style>

View file

@ -0,0 +1,8 @@
export { default as Subject } from "./Subject.svelte"
export { default as Property } from "./Property.svelte"
export { default as JSONValue } from "./JSONValue.svelte"
export { default as BindingValue } from "./BindingValue.svelte"
export { default as Section } from "./Section.svelte"
export { default as Block } from "./Block.svelte"
export { default as ExampleSection } from "./ExampleSection.svelte"
export { default as ExampleLine } from "./ExampleLine.svelte"

View file

@ -0,0 +1,8 @@
export { default as Column } from "./Column.svelte"
export { default as NotRequired } from "./NotRequired.svelte"
export { default as StringsAsNumbers } from "./StringsAsNumbers.svelte"
export { default as Support } from "./Support.svelte"
export { default as DatesAsNumbers } from "./DatesAsNumbers.svelte"
export { default as ScalarJsonOnly } from "./ScalarJsonOnly.svelte"
export { default as StringsAsDates } from "./StringsAsDates.svelte"
export { default as NumbersAsDates } from "./NumbersAsDates.svelte"

View file

@ -0,0 +1,102 @@
<script>
import DetailsModal from "./DetailsModal/index.svelte"
import {
messages as messageConstants,
getExplanationMessagesAndSupport,
getExplanationWithPresets,
} from "./explanation"
import {
StringAsDate,
NumberAsDate,
Column,
Support,
NotRequired,
StringAsNumber,
JSONPrimitivesOnly,
DateAsNumber,
} from "./lines"
import subjects from "./subjects"
import { appStore } from "stores/builder"
export let explanation
export let columnIcon
export let columnType
export let columnName
export let tableHref = () => {}
export let schema
$: explanationWithPresets = getExplanationWithPresets(
explanation,
$appStore.typeSupportPresets
)
let support
let messages = []
$: {
const explanationMessagesAndSupport = getExplanationMessagesAndSupport(
schema,
explanationWithPresets
)
support = explanationMessagesAndSupport.support
messages = explanationMessagesAndSupport.messages
}
let root = null
let detailsModalSubject = subjects.none
const setExplanationSubject = option => {
detailsModalSubject = option
root = root
}
</script>
<div bind:this={root} class="tooltipContents">
<Column
{columnName}
{columnIcon}
{columnType}
{tableHref}
{setExplanationSubject}
/>
<Support {support} {setExplanationSubject} />
{#if messages.includes(messageConstants.stringAsNumber)}
<StringAsNumber {setExplanationSubject} />
{/if}
{#if messages.includes(messageConstants.notRequired)}
<NotRequired {setExplanationSubject} />
{/if}
{#if messages.includes(messageConstants.jsonPrimitivesOnly)}
<JSONPrimitivesOnly {setExplanationSubject} />
{/if}
{#if messages.includes(messageConstants.dateAsNumber)}
<DateAsNumber {setExplanationSubject} />
{/if}
{#if messages.includes(messageConstants.numberAsDate)}
<NumberAsDate {setExplanationSubject} />
{/if}
{#if messages.includes(messageConstants.stringAsDate)}
<StringAsDate {setExplanationSubject} />
{/if}
</div>
{#if detailsModalSubject !== subjects.none}
<DetailsModal
{columnName}
anchor={root}
{schema}
subject={detailsModalSubject}
/>
{/if}
<style>
.tooltipContents {
max-width: 450px;
display: block;
padding: 20px 16px 10px;
border-radius: 5px;
box-sizing: border-box;
}
</style>

View file

@ -0,0 +1,100 @@
export const messages = {
jsonPrimitivesOnly: Symbol("explanation-json-primitives-only"),
stringAsNumber: Symbol("explanation-string-as-number"),
dateAsNumber: Symbol("explanation-date-as-number"),
numberAsDate: Symbol("explanation-number-as-date"),
stringAsDate: Symbol("explanation-string-as-date"),
notRequired: Symbol("explanation-not-required"),
contextError: Symbol("explanation-context-error"),
}
export const support = {
unsupported: Symbol("explanation-unsupported"),
partialSupport: Symbol("explanation-partialSupport"),
supported: Symbol("explanation-supported"),
}
const getSupport = (type, explanation) => {
if (!explanation?.typeSupport) {
return support.supported
}
if (
explanation?.typeSupport?.supported?.find(
mapping => mapping === type || mapping?.type === type
)
) {
return support.supported
}
if (
explanation?.typeSupport?.partialSupport?.find(
mapping => mapping === type || mapping?.type === type
)
) {
return support.partialSupport
}
return support.unsupported
}
const getSupportMessage = (type, explanation) => {
if (!explanation?.typeSupport) {
return null
}
const supported = explanation?.typeSupport?.supported?.find(
mapping => mapping?.type === type
)
if (supported) {
return messages[supported?.message]
}
const partialSupport = explanation?.typeSupport?.partialSupport?.find(
mapping => mapping?.type === type
)
if (partialSupport) {
return messages[partialSupport?.message]
}
const unsupported = explanation?.typeSupport?.unsupported?.find(
mapping => mapping?.type === type
)
if (unsupported) {
return messages[unsupported?.message]
}
return null
}
export const getExplanationMessagesAndSupport = (fieldSchema, explanation) => {
try {
const explanationMessagesAndSupport = {
support: getSupport(fieldSchema.type, explanation),
messages: [getSupportMessage(fieldSchema.type, explanation)],
}
const isRequired = fieldSchema?.constraints?.presence?.allowEmpty === false
if (!isRequired) {
explanationMessagesAndSupport.messages.push(messages.notRequired)
}
return explanationMessagesAndSupport
} catch (e) {
return {
support: support.partialSupport,
messages: [messages.contextError],
}
}
}
export const getExplanationWithPresets = (explanation, presets) => {
if (explanation?.typeSupport?.preset) {
return {
...explanation,
typeSupport: presets[explanation?.typeSupport?.preset],
}
}
return explanation
}

View file

@ -0,0 +1 @@
export { default as Explanation } from "./Explanation.svelte"

View file

@ -0,0 +1,84 @@
<script>
import {
Line,
InfoWord,
DocumentationLink,
Text,
Period,
} from "../typography"
import subjects from "../subjects"
export let columnName
export let columnIcon
export let columnType
export let tableHref
export let setExplanationSubject
const getDocLink = columnType => {
if (columnType === "Number") {
return "https://docs.budibase.com/docs/number"
}
if (columnType === "Text") {
return "https://docs.budibase.com/docs/text"
}
if (columnType === "Attachment") {
return "https://docs.budibase.com/docs/attachments"
}
if (columnType === "Multi-select") {
return "https://docs.budibase.com/docs/multi-select"
}
if (columnType === "JSON") {
return "https://docs.budibase.com/docs/json"
}
if (columnType === "Date/Time") {
return "https://docs.budibase.com/docs/datetime"
}
if (columnType === "User") {
return "https://docs.budibase.com/docs/user"
}
if (columnType === "QR") {
return "https://docs.budibase.com/docs/barcodeqr"
}
if (columnType === "Relationship") {
return "https://docs.budibase.com/docs/relationships"
}
if (columnType === "Formula") {
return "https://docs.budibase.com/docs/formula"
}
if (columnType === "Options") {
return "https://docs.budibase.com/docs/options"
}
if (columnType === "BigInt") {
// No BigInt docs
return null
}
if (columnType === "Boolean") {
return "https://docs.budibase.com/docs/boolean-truefalse"
}
if (columnType === "Signature") {
// No Signature docs
return null
}
return null
}
$: docLink = getDocLink(columnType)
</script>
<Line noWrap>
<InfoWord
on:mouseenter={() => setExplanationSubject(subjects.column)}
on:mouseleave={() => setExplanationSubject(subjects.none)}
href={tableHref}
text={columnName}
/>
<Text value=" is a " />
<DocumentationLink
disabled={docLink === null}
href={docLink}
icon={columnIcon}
text={`${columnType} column`}
/>
<Period />
</Line>

View file

@ -0,0 +1,16 @@
<script>
import { Line, InfoWord, Text, Period } from "../typography"
import subjects from "../subjects"
export let setExplanationSubject
</script>
<Line>
<Text value="Will be converted to a " />
<InfoWord
on:mouseenter={() => setExplanationSubject(subjects.datesAsNumbers)}
on:mouseleave={() => setExplanationSubject(subjects.none)}
text="UNIX time value"
/>
<Period />
</Line>

View file

@ -0,0 +1,21 @@
<script>
import { Line, InfoWord, Text, Period } from "../typography"
import subjects from "../subjects"
export let setExplanationSubject
</script>
<Line>
<InfoWord
on:mouseenter={() => setExplanationSubject(subjects.scalarJsonOnly)}
on:mouseleave={() => setExplanationSubject(subjects.none)}
>Scalar JSON values</InfoWord
>
<Text
value=" can be used with this input if their individual types are supported"
/>
<Period />
</Line>
<style>
</style>

View file

@ -0,0 +1,25 @@
<script>
import { Line, InfoWord, DocumentationLink, Space, Text } from "../typography"
import subjects from "../subjects"
export let setExplanationSubject
</script>
<Line>
<Text value="No " />
<InfoWord
on:mouseenter={() => setExplanationSubject(subjects.notRequired)}
on:mouseleave={() => setExplanationSubject(subjects.none)}
text="required"
/>
<Space />
<DocumentationLink
icon="DataUnavailable"
href="https://docs.budibase.com/docs/budibasedb#constraints"
text="Constraint"
/>
<Text value=", so values may be missing." />
</Line>
<style>
</style>

View file

@ -0,0 +1,16 @@
<script>
import { Line, InfoWord, Text, Period } from "../typography"
import subjects from "../subjects"
export let setExplanationSubject
</script>
<Line>
<Text value="Will be treated as a " />
<InfoWord
on:mouseenter={() => setExplanationSubject(subjects.numbersAsDates)}
on:mouseleave={() => setExplanationSubject(subjects.none)}
text="UNIX time value"
/>
<Period />
</Line>

View file

@ -0,0 +1,16 @@
<script>
import { Line, InfoWord, Text, Period } from "../typography"
import subjects from "../subjects"
export let setExplanationSubject
</script>
<Line>
<Text value="Will be treated as a " />
<InfoWord
on:mouseenter={() => setExplanationSubject(subjects.stringsAsDates)}
on:mouseleave={() => setExplanationSubject(subjects.none)}
text="UNIX time or ISO 8601 value"
/>
<Period />
</Line>

View file

@ -0,0 +1,16 @@
<script>
import { Line, InfoWord, Text, Period } from "../typography"
import subjects from "../subjects"
export let setExplanationSubject
</script>
<Line>
<Text value="Will be converted to a number, ignoring any " />
<InfoWord
on:mouseenter={() => setExplanationSubject(subjects.stringsAsNumbers)}
on:mouseleave={() => setExplanationSubject(subjects.none)}
text="non-numerical values"
/>
<Period />
</Line>

View file

@ -0,0 +1,59 @@
<script>
import { Line, InfoWord, DocumentationLink, Text } from "../typography"
import subjects from "../subjects"
import * as explanation from "../explanation"
export let setExplanationSubject
export let support
const getIcon = support => {
if (support === explanation.support.unsupported) {
return "Alert"
} else if (support === explanation.support.supported) {
return "CheckmarkCircle"
}
return "AlertCheck"
}
const getColor = support => {
if (support === explanation.support.unsupported) {
return "var(--red)"
} else if (support === explanation.support.supported) {
return "var(--green)"
}
return "var(--yellow)"
}
const getText = support => {
if (support === explanation.support.unsupported) {
return "Not compatible"
} else if (support === explanation.support.supported) {
return "Compatible"
}
return "Partially compatible"
}
$: icon = getIcon(support)
$: color = getColor(support)
$: text = getText(support)
</script>
<Line>
<InfoWord
on:mouseenter={() => setExplanationSubject(subjects.support)}
on:mouseleave={() => setExplanationSubject(subjects.none)}
{icon}
{color}
{text}
/>
<Text value=" with this " />
<DocumentationLink
href="https://docs.budibase.com/docs/chart"
icon="GraphPie"
text="Chart component"
/>
<Text value=" input." />
</Line>

View file

@ -0,0 +1,8 @@
export { default as Column } from "./Column.svelte"
export { default as NotRequired } from "./NotRequired.svelte"
export { default as StringAsNumber } from "./StringAsNumber.svelte"
export { default as Support } from "./Support.svelte"
export { default as JSONPrimitivesOnly } from "./JSONPrimitivesOnly.svelte"
export { default as DateAsNumber } from "./DateAsNumber.svelte"
export { default as NumberAsDate } from "./NumberAsDate.svelte"
export { default as StringAsDate } from "./StringAsDate.svelte"

View file

@ -0,0 +1,13 @@
const subjects = {
column: Symbol("details-modal-column"),
support: Symbol("details-modal-support"),
stringsAsNumbers: Symbol("details-modal-strings-as-numbers"),
datesAsNumbers: Symbol("details-modal-dates-as-numbers"),
numbersAsDates: Symbol("explanation-numbers-as-dates"),
stringsAsDates: Symbol("explanation-strings-as-dates"),
notRequired: Symbol("details-modal-not-required"),
scalarJsonOnly: Symbol("explanation-scalar-json-only"),
none: Symbol("details-modal-none"),
}
export default subjects

View file

@ -0,0 +1,14 @@
<span class="comma">,</span>
<style>
.comma {
color: var(--grey-6);
font-size: 17px;
display: inline block;
margin-left: 2px;
margin-right: 2px;
flex-shrink: 0;
position: relative;
bottom: 2px;
}
</style>

View file

@ -0,0 +1,66 @@
<script>
import { Icon } from "@budibase/bbui"
export let icon
export let text
export let href
export let disabled = false
</script>
<a
class:disabled
tabindex="0"
{href}
rel="noopener noreferrer"
target="_blank"
class="link"
>
<Icon size="XS" name={icon} />
<span class="text">
<slot>
{text}
</slot>
</span>
</a>
<style>
.text {
color: var(--ink);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.link {
display: inline-flex;
box-sizing: border-box;
padding: 1px 0 2px;
filter: brightness(100%);
align-items: center;
overflow: hidden;
flex-shrink: 0;
border-radius: 0;
border-bottom: 1px solid var(--blue);
transition: filter 300ms;
margin-right: 0px;
}
.link:hover {
filter: brightness(120%);
}
.disabled {
filter: brightness(100%);
border-bottom: 1px solid var(--grey-6);
}
.link :global(svg) {
margin-right: 3px;
color: var(--blue);
}
.disabled :global(svg) {
color: var(--grey-6);
}
</style>

View file

@ -0,0 +1,78 @@
<script>
import { Icon } from "@budibase/bbui"
export let icon = null
export let color = null
export let text
export let href = null
</script>
{#if href !== null}
<a
tabindex="0"
{href}
rel="noopener noreferrer"
target="_blank"
class="infoWord"
style:color
style:border-color={color}
on:mouseenter
on:mouseleave
>
{#if icon}
<Icon size="XS" name={icon} />
{/if}
<span class="text">
<slot>
{text}
</slot>
</span>
</a>
{:else}
<div
role="tooltip"
class="infoWord"
style:color
style:border-color={color}
on:mouseenter
on:mouseleave
>
{#if icon}
<Icon size="XS" name={icon} />
{/if}
<span class="text">
<slot>
{text}
</slot>
</span>
</div>
{/if}
<style>
.infoWord {
display: inline-flex;
box-sizing: border-box;
padding: 1px 0 2px;
filter: brightness(100%);
overflow: hidden;
transition: filter 300ms;
align-items: center;
border-bottom: 1px solid var(--grey-6);
}
.infoWord:hover {
filter: brightness(120%);
}
.infoWord :global(svg) {
color: inherit;
margin-right: 4px;
}
.text {
color: var(--ink);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
</style>

View file

@ -0,0 +1,41 @@
<script>
export let noWrap = false
</script>
<div class="line">
<span class="bullet"></span>
<div class="content" class:noWrap>
<slot />
</div>
</div>
<style>
.line {
color: var(--ink);
align-items: flex-start;
display: flex;
margin-bottom: 10px;
}
.bullet {
color: var(--grey-6);
font-size: 17px;
display: inline block;
margin-right: 10px;
}
.content {
line-height: 17px;
min-width: 0;
align-items: center;
display: flex;
flex-wrap: wrap;
row-gap: 3px;
}
.noWrap {
display: flex;
flex-wrap: nowrap;
}
</style>

View file

@ -0,0 +1,13 @@
<span class="period">.</span>
<style>
.period {
color: var(--grey-6);
font-size: 20px;
display: inline block;
margin-left: 2px;
flex-shrink: 0;
position: relative;
bottom: 2px;
}
</style>

View file

@ -0,0 +1,9 @@
<span class="space">{" "}</span>
<style>
.space {
white-space: pre;
width: 3px;
flex-shrink: 0;
}
</style>

View file

@ -0,0 +1,64 @@
<script>
import Comma from "./Comma.svelte"
import Period from "./Period.svelte"
import Space from "./Space.svelte"
export let value = null
const punctuation = [" ", ",", "."]
// TODO regex might work here now
const getWords = value => {
if (typeof value !== "string") {
return []
}
const newWords = []
let lastIndex = 0
const makeWord = i => {
// No word to make, multiple spaces, spaces at start of text etc
if (i - lastIndex > 0) {
newWords.push(value.substring(lastIndex, i))
}
lastIndex = i + 1
}
value.split("").forEach((character, i) => {
if (punctuation.includes(character)) {
makeWord(i)
newWords.push(character)
}
})
makeWord(value.length)
return newWords
}
$: words = getWords(value)
</script>
{#each words as word}
{#if word === " "}
<Space />
{:else if word === ","}
<Comma />
{:else if word === "."}
<Period />
{:else}
<span class="text">
{word}
</span>
{/if}
{/each}
<style>
.text {
/* invisible properties to match other inline text elements that do have borders. If we don't match here we run into subpixel issues */
box-sizing: border-box;
border-bottom: 1px solid transparent;
padding: 1px 0 2px;
}
</style>

View file

@ -0,0 +1,7 @@
export { default as Space } from "./Space.svelte"
export { default as Comma } from "./Comma.svelte"
export { default as Period } from "./Period.svelte"
export { default as Text } from "./Text.svelte"
export { default as InfoWord } from "./InfoWord.svelte"
export { default as DocumentationLink } from "./DocumentationLink.svelte"
export { default as Line } from "./Line.svelte"

View file

@ -1,12 +1,22 @@
<script>
import { Select } from "@budibase/bbui"
import { Select, ContextTooltip } from "@budibase/bbui"
import { getDatasourceForProvider, getSchemaForDatasource } from "dataBinding"
import { selectedScreen } from "stores/builder"
import { createEventDispatcher } from "svelte"
import { Explanation } from "./Explanation"
import { debounce } from "lodash"
import { params } from "@roxi/routify"
import { Constants } from "@budibase/frontend-core"
import { FIELDS } from "constants/backend"
export let componentInstance = {}
export let value = ""
export let placeholder
export let explanation
let contextTooltipAnchor = null
let currentOption = null
let contextTooltipVisible = false
const dispatch = createEventDispatcher()
$: datasource = getDatasourceForProvider($selectedScreen, componentInstance)
@ -32,6 +42,77 @@
boundValue = getValidValue(value.detail, options)
dispatch("change", boundValue)
}
const updateTooltip = debounce((e, option) => {
if (option == null) {
contextTooltipVisible = false
} else {
contextTooltipAnchor = e?.target
currentOption = option
contextTooltipVisible = true
}
}, 200)
const onOptionMouseenter = (e, option) => {
updateTooltip(e, option)
}
const onOptionMouseleave = e => {
updateTooltip(e, null)
}
const getOptionIcon = optionKey => {
const option = schema[optionKey]
if (!option) return ""
if (option.autocolumn) {
return "MagicWand"
}
const { type, subtype } = option
const result =
typeof Constants.TypeIconMap[type] === "object" && subtype
? Constants.TypeIconMap[type][subtype]
: Constants.TypeIconMap[type]
return result || "Text"
}
const getOptionIconTooltip = optionKey => {
const option = schema[optionKey]
const type = option?.type
const field = Object.values(FIELDS).find(f => f.type === type)
if (field) {
return field.name
}
return ""
}
</script>
<Select {placeholder} value={boundValue} on:change={onChange} {options} />
<Select
{placeholder}
value={boundValue}
on:change={onChange}
{options}
{onOptionMouseenter}
{onOptionMouseleave}
/>
{#if explanation}
<ContextTooltip
visible={contextTooltipVisible}
anchor={contextTooltipAnchor}
offset={20}
>
<Explanation
tableHref={`/builder/app/${$params.application}/data/table/${datasource?.tableId}`}
schema={schema[currentOption]}
columnIcon={getOptionIcon(currentOption)}
columnName={currentOption}
columnType={getOptionIconTooltip(currentOption)}
{explanation}
/>
</ContextTooltip>
{/if}

View file

@ -1,12 +1,22 @@
<script>
import { Multiselect } from "@budibase/bbui"
import { Multiselect, ContextTooltip } from "@budibase/bbui"
import { getDatasourceForProvider, getSchemaForDatasource } from "dataBinding"
import { selectedScreen } from "stores/builder"
import { createEventDispatcher } from "svelte"
import { Explanation } from "./Explanation"
import { FIELDS } from "constants/backend"
import { params } from "@roxi/routify"
import { debounce } from "lodash"
import { Constants } from "@budibase/frontend-core"
export let componentInstance = {}
export let value = ""
export let placeholder
export let explanation
let contextTooltipAnchor = null
let currentOption = null
let contextTooltipVisible = false
const dispatch = createEventDispatcher()
$: datasource = getDatasourceForProvider($selectedScreen, componentInstance)
@ -26,6 +36,84 @@
boundValue = getValidOptions(value.detail, options)
dispatch("change", boundValue)
}
const getOptionIcon = optionKey => {
const option = schema[optionKey]
if (!option) return ""
if (option.autocolumn) {
return "MagicWand"
}
const { type, subtype } = option
const result =
typeof Constants.TypeIconMap[type] === "object" && subtype
? Constants.TypeIconMap[type][subtype]
: Constants.TypeIconMap[type]
return result || "Text"
}
const getOptionIconTooltip = optionKey => {
const option = schema[optionKey]
const type = option?.type
const field = Object.values(FIELDS).find(f => f.type === type)
if (field) {
return field.name
} else if (type === "jsonarray") {
// `jsonarray` isn't present in the above FIELDS constant
return "JSON Array"
}
return ""
}
const updateTooltip = debounce((e, option) => {
if (option == null) {
contextTooltipVisible = false
} else {
contextTooltipAnchor = e?.target
currentOption = option
contextTooltipVisible = true
}
}, 200)
const onOptionMouseenter = (e, option) => {
updateTooltip(e, option)
}
const onOptionMouseleave = e => {
updateTooltip(e, null)
}
</script>
<Multiselect {placeholder} value={boundValue} on:change={setValue} {options} />
<Multiselect
iconPosition="right"
{placeholder}
value={boundValue}
on:change={setValue}
{options}
align="right"
{onOptionMouseenter}
{onOptionMouseleave}
/>
{#if explanation}
<ContextTooltip
visible={contextTooltipVisible}
anchor={contextTooltipAnchor}
offset={20}
>
<Explanation
tableHref={`/builder/app/${$params.application}/data/table/${datasource?.tableId}`}
schema={schema[currentOption]}
columnIcon={getOptionIcon(currentOption)}
columnName={currentOption}
columnType={getOptionIconTooltip(currentOption)}
{explanation}
/>
</ContextTooltip>
{/if}

View file

@ -21,7 +21,7 @@
} from "@budibase/bbui"
import AppActions from "components/deploy/AppActions.svelte"
import { API } from "api"
import { isActive, goto, layout, redirect } from "@roxi/routify"
import { isActive, url, goto, layout, redirect } from "@roxi/routify"
import { capitalise } from "helpers"
import { onMount, onDestroy } from "svelte"
import VerificationPromptBanner from "components/common/VerificationPromptBanner.svelte"
@ -69,7 +69,7 @@
// e.g. if one of your screens is selected on front end, then
// you browse to backend, when you click frontend, you will be
// brought back to the same screen.
const topItemNavigate = path => () => {
const topItemNavigate = path => {
const activeTopNav = $layout.children.find(c => $isActive(c.path))
if (activeTopNav) {
builderStore.setPreviousTopNavPath(
@ -136,21 +136,18 @@
<div class="top-nav">
{#if $appStore.initialised}
<div class="topleftnav">
<span class="back-to-apps">
<Icon
size="S"
hoverable
name="BackAndroid"
on:click={() => $goto("../../portal/apps")}
/>
</span>
<a href={$url("../../portal/apps")} class="linkWrapper back-to-apps">
<Icon size="S" hoverable name="BackAndroid" />
</a>
<Tabs {selected} size="M">
{#each $layout.children as { path, title }}
<TourWrap stepKeys={[`builder-${title}-section`]}>
<Tab
link
href={$url(path)}
quiet
selected={$isActive(path)}
on:click={topItemNavigate(path)}
on:click={() => topItemNavigate(path)}
title={capitalise(title)}
id={`builder-${title}-tab`}
/>
@ -201,6 +198,11 @@
<EnterpriseBasicTrialModal />
<style>
.linkWrapper {
text-decoration: none;
color: inherit;
}
.back-to-apps {
display: contents;
}

View file

@ -191,6 +191,9 @@
// Number fields
min: setting.min ?? null,
max: setting.max ?? null,
// Field select settings
explanation: setting.explanation,
}}
{bindings}
{componentBindings}

View file

@ -19,6 +19,7 @@ export const INITIAL_APP_META_STATE = {
showNotificationAction: false,
sidePanel: false,
},
typeSupportPresets: {},
features: {
componentValidation: false,
disableUserMetadata: false,
@ -79,6 +80,13 @@ export class AppMetaStore extends BudiStore {
}))
}
syncClientTypeSupportPresets(typeSupportPresets) {
this.update(state => ({
...state,
typeSupportPresets,
}))
}
async syncAppRoutes() {
const resp = await API.fetchAppRoutes()
this.update(state => ({

View file

@ -109,6 +109,7 @@ export class ComponentStore extends BudiStore {
// Sync client features to app store
appStore.syncClientFeatures(components.features)
appStore.syncClientTypeSupportPresets(components?.typeSupportPresets ?? {})
return components
}

View file

@ -91,6 +91,14 @@ describe("Application Meta Store", () => {
})
})
it("Sync type support information to state", async ctx => {
ctx.test.appStore.syncClientTypeSupportPresets({ preset: "information" })
expect(ctx.test.store.typeSupportPresets).toStrictEqual({
preset: "information",
})
})
it("Sync component feature flags to state", async ctx => {
ctx.test.appStore.syncClientFeatures(clientFeaturesResp)

View file

@ -42,6 +42,7 @@ vi.mock("stores/builder", async () => {
update: mockAppStore.update,
set: mockAppStore.set,
syncClientFeatures: vi.fn(),
syncClientTypeSupportPresets: vi.fn(),
}
const mockTableStore = writable()
const tables = {

View file

@ -13,6 +13,42 @@
"sidePanel": true,
"skeletonLoader": true
},
"typeSupportPresets": {
"numberLike": {
"supported": ["number", "boolean"],
"partialSupport": [
{ "type": "longform", "message": "stringAsNumber" },
{ "type": "string", "message": "stringAsNumber" },
{ "type": "bigint", "message": "stringAsNumber" },
{ "type": "options", "message": "stringAsNumber" },
{ "type": "formula", "message": "stringAsNumber" },
{ "type": "datetime", "message": "dateAsNumber"}
],
"unsupported": [
{ "type": "json", "message": "jsonPrimitivesOnly" }
]
},
"stringLike": {
"supported": ["string", "number", "bigint", "options", "longform", "boolean", "datetime"],
"unsupported": [
{ "type": "json", "message": "jsonPrimitivesOnly" }
]
},
"datetimeLike": {
"supported": ["datetime"],
"partialSupport": [
{ "type": "longform", "message": "stringAsDate" },
{ "type": "string", "message": "stringAsDate" },
{ "type": "options", "message": "stringAsDate" },
{ "type": "formula", "message": "stringAsDate" },
{ "type": "bigint", "message": "stringAsDate" },
{ "type": "number", "message": "numberAsDate"}
],
"unsupported": [
{ "type": "json", "message": "jsonPrimitivesOnly" }
]
}
},
"layout": {
"name": "Layout",
"description": "This component is specific only to layouts",
@ -1602,6 +1638,7 @@
]
},
"bar": {
"documentationLink": "https://docs.budibase.com/docs/bar-chart",
"name": "Bar Chart",
"description": "Bar chart",
"icon": "GraphBarVertical",
@ -1626,6 +1663,11 @@
"label": "Label column",
"key": "labelColumn",
"dependsOn": "dataProvider",
"explanation": {
"typeSupport": {
"preset": "stringLike"
}
},
"required": true
},
{
@ -1633,6 +1675,11 @@
"label": "Data columns",
"key": "valueColumns",
"dependsOn": "dataProvider",
"explanation": {
"typeSupport": {
"preset": "numberLike"
}
},
"required": true
},
{
@ -1760,6 +1807,7 @@
]
},
"line": {
"documentationLink": "https://docs.budibase.com/docs/line-chart",
"name": "Line Chart",
"description": "Line chart",
"icon": "GraphTrend",
@ -1784,6 +1832,11 @@
"label": "Label column",
"key": "labelColumn",
"dependsOn": "dataProvider",
"explanation": {
"typeSupport": {
"preset": "stringLike"
}
},
"required": true
},
{
@ -1791,6 +1844,11 @@
"label": "Data columns",
"key": "valueColumns",
"dependsOn": "dataProvider",
"explanation": {
"typeSupport": {
"preset": "numberLike"
}
},
"required": true
},
{
@ -1913,6 +1971,7 @@
]
},
"area": {
"documentationLink": "https://docs.budibase.com/docs/area-chart",
"name": "Area Chart",
"description": "Line chart",
"icon": "GraphAreaStacked",
@ -1937,6 +1996,11 @@
"label": "Label column",
"key": "labelColumn",
"dependsOn": "dataProvider",
"explanation": {
"typeSupport": {
"preset": "stringLike"
}
},
"required": true
},
{
@ -1944,6 +2008,11 @@
"label": "Data columns",
"key": "valueColumns",
"dependsOn": "dataProvider",
"explanation": {
"typeSupport": {
"preset": "numberLike"
}
},
"required": true
},
{
@ -2078,6 +2147,7 @@
]
},
"pie": {
"documentationLink": "https://docs.budibase.com/docs/pie-donut-chart",
"name": "Pie Chart",
"description": "Pie chart",
"icon": "GraphPie",
@ -2102,13 +2172,23 @@
"label": "Label column",
"key": "labelColumn",
"dependsOn": "dataProvider",
"explanation": {
"typeSupport": {
"preset": "stringLike"
}
},
"required": true
},
{
"type": "field",
"label": "Data columns",
"label": "Data column",
"key": "valueColumn",
"dependsOn": "dataProvider",
"explanation": {
"typeSupport": {
"preset": "numberLike"
}
},
"required": true
},
{
@ -2207,6 +2287,7 @@
]
},
"donut": {
"documentationLink": "https://docs.budibase.com/docs/pie-donut-chart",
"name": "Donut Chart",
"description": "Donut chart",
"icon": "GraphDonut",
@ -2231,6 +2312,11 @@
"label": "Label column",
"key": "labelColumn",
"dependsOn": "dataProvider",
"explanation": {
"typeSupport": {
"preset": "stringLike"
}
},
"required": true
},
{
@ -2238,6 +2324,11 @@
"label": "Data columns",
"key": "valueColumn",
"dependsOn": "dataProvider",
"explanation": {
"typeSupport": {
"preset": "numberLike"
}
},
"required": true
},
{
@ -2336,6 +2427,7 @@
]
},
"candlestick": {
"documentationLink": "https://docs.budibase.com/docs/candlestick-chart",
"name": "Candlestick Chart",
"description": "Candlestick chart",
"icon": "GraphBarVerticalStacked",
@ -2360,6 +2452,11 @@
"label": "Date column",
"key": "dateColumn",
"dependsOn": "dataProvider",
"explanation": {
"typeSupport": {
"preset": "datetimeLike"
}
},
"required": true
},
{
@ -2367,6 +2464,11 @@
"label": "Open column",
"key": "openColumn",
"dependsOn": "dataProvider",
"explanation": {
"typeSupport": {
"preset": "numberLike"
}
},
"required": true
},
{
@ -2374,6 +2476,11 @@
"label": "Close column",
"key": "closeColumn",
"dependsOn": "dataProvider",
"explanation": {
"typeSupport": {
"preset": "numberLike"
}
},
"required": true
},
{
@ -2381,6 +2488,11 @@
"label": "High column",
"key": "highColumn",
"dependsOn": "dataProvider",
"explanation": {
"typeSupport": {
"preset": "numberLike"
}
},
"required": true
},
{
@ -2388,6 +2500,11 @@
"label": "Low column",
"key": "lowColumn",
"dependsOn": "dataProvider",
"explanation": {
"typeSupport": {
"preset": "numberLike"
}
},
"required": true
},
{
@ -2427,6 +2544,7 @@
]
},
"histogram": {
"documentationLink": "https://docs.budibase.com/docs/histogram-chart",
"name": "Histogram Chart",
"description": "Histogram chart",
"icon": "Histogram",
@ -2434,7 +2552,6 @@
"width": 600,
"height": 400
},
"requiredAncestors": ["dataprovider"],
"settings": [
{
"type": "text",
@ -2452,6 +2569,11 @@
"label": "Data column",
"key": "valueColumn",
"dependsOn": "dataProvider",
"explanation": {
"typeSupport": {
"preset": "numberLike"
}
},
"required": true
},
{
@ -5313,6 +5435,11 @@
"label": "Label column",
"key": "labelColumn",
"dependsOn": "dataSource",
"explanation": {
"typeSupport": {
"preset": "stringLike"
}
},
"required": true
},
{
@ -5320,6 +5447,11 @@
"label": "Data column",
"key": "valueColumn",
"dependsOn": "dataSource",
"explanation": {
"typeSupport": {
"preset": "numberLike"
}
},
"required": true
}
]
@ -5338,6 +5470,11 @@
"label": "Label column",
"key": "labelColumn",
"dependsOn": "dataSource",
"explanation": {
"typeSupport": {
"preset": "stringLike"
}
},
"required": true
},
{
@ -5345,6 +5482,11 @@
"label": "Data column",
"key": "valueColumn",
"dependsOn": "dataSource",
"explanation": {
"typeSupport": {
"preset": "numberLike"
}
},
"required": true
}
]
@ -5363,6 +5505,11 @@
"label": "Label column",
"key": "labelColumn",
"dependsOn": "dataSource",
"explanation": {
"typeSupport": {
"preset": "stringLike"
}
},
"required": true
},
{
@ -5370,6 +5517,11 @@
"label": "Data columns",
"key": "valueColumns",
"dependsOn": "dataSource",
"explanation": {
"typeSupport": {
"preset": "numberLike"
}
},
"required": true
},
{
@ -5417,6 +5569,11 @@
"label": "Value column",
"key": "valueColumn",
"dependsOn": "dataSource",
"explanation": {
"typeSupport": {
"preset": "numberLike"
}
},
"required": true
},
{
@ -5458,6 +5615,11 @@
"label": "Label column",
"key": "labelColumn",
"dependsOn": "dataSource",
"explanation": {
"typeSupport": {
"preset": "stringLike"
}
},
"required": true
},
{
@ -5465,6 +5627,11 @@
"label": "Data columns",
"key": "valueColumns",
"dependsOn": "dataSource",
"explanation": {
"typeSupport": {
"preset": "numberLike"
}
},
"required": true
},
{
@ -5507,6 +5674,11 @@
"label": "Label columns",
"key": "labelColumn",
"dependsOn": "dataSource",
"explanation": {
"typeSupport": {
"preset": "stringLike"
}
},
"required": true
},
{
@ -5514,6 +5686,11 @@
"label": "Data columns",
"key": "valueColumns",
"dependsOn": "dataSource",
"explanation": {
"typeSupport": {
"preset": "numberLike"
}
},
"required": true
},
{
@ -5568,6 +5745,11 @@
"label": "Date column",
"key": "dateColumn",
"dependsOn": "dataSource",
"explanation": {
"typeSupport": {
"preset": "datetimeLike"
}
},
"required": true
},
{
@ -5575,6 +5757,11 @@
"label": "Open column",
"key": "openColumn",
"dependsOn": "dataSource",
"explanation": {
"typeSupport": {
"preset": "numberLike"
}
},
"required": true
},
{
@ -5582,6 +5769,11 @@
"label": "Close column",
"key": "closeColumn",
"dependsOn": "dataSource",
"explanation": {
"typeSupport": {
"preset": "numberLike"
}
},
"required": true
},
{
@ -5589,6 +5781,11 @@
"label": "High column",
"key": "highColumn",
"dependsOn": "dataSource",
"explanation": {
"typeSupport": {
"preset": "numberLike"
}
},
"required": true
},
{
@ -5596,6 +5793,11 @@
"label": "Low column",
"key": "lowColumn",
"dependsOn": "dataSource",
"explanation": {
"typeSupport": {
"preset": "numberLike"
}
},
"required": true
},
{

View file

@ -25,7 +25,7 @@
"@budibase/string-templates": "0.0.0",
"@budibase/types": "0.0.0",
"@spectrum-css/card": "3.0.3",
"apexcharts": "^3.22.1",
"apexcharts": "^3.48.0",
"dayjs": "^1.10.8",
"downloadjs": "1.4.7",
"html5-qrcode": "^2.2.1",
@ -33,7 +33,6 @@
"sanitize-html": "^2.7.0",
"screenfull": "^6.0.1",
"shortid": "^2.2.15",
"svelte-apexcharts": "^1.0.2",
"svelte-spa-router": "^4.0.1",
"atrament": "^4.3.0"
},

View file

@ -287,10 +287,23 @@
const dependsOnKey = setting.dependsOn.setting || setting.dependsOn
const dependsOnValue = setting.dependsOn.value
const realDependentValue = instance[dependsOnKey]
const sectionDependsOnKey =
setting.sectionDependsOn?.setting || setting.sectionDependsOn
const sectionDependsOnValue = setting.sectionDependsOn?.value
const sectionRealDependentValue = instance[sectionDependsOnKey]
if (dependsOnValue == null && realDependentValue == null) {
return false
}
if (dependsOnValue !== realDependentValue) {
if (dependsOnValue != null && dependsOnValue !== realDependentValue) {
return false
}
if (
sectionDependsOnValue != null &&
sectionDependsOnValue !== sectionRealDependentValue
) {
return false
}
}

View file

@ -32,7 +32,7 @@
// Bar/Line/Area
export let valueColumns
export let yAxisUnits
export let valueUnits
export let yAxisLabel
export let xAxisLabel
export let curve
@ -51,8 +51,6 @@
export let bucketCount
let dataProviderId
$: colors = c1 && c2 && c3 && c4 && c5 ? [c1, c2, c3, c4, c5] : null
</script>
<Block>
@ -84,8 +82,7 @@
dataLabels,
legend,
animate,
...colors,
yAxisUnits,
valueUnits,
yAxisLabel,
xAxisLabel,
stacked,
@ -98,6 +95,11 @@
lowColumn,
dateColumn,
bucketCount,
c1,
c2,
c3,
c4,
c5,
}}
/>
{/if}

View file

@ -1,25 +1,85 @@
<script>
import { getContext } from "svelte"
import { chart } from "svelte-apexcharts"
import Placeholder from "../Placeholder.svelte"
import ApexCharts from "apexcharts"
import { Icon } from "@budibase/bbui"
import { cloneDeep } from "./utils"
const { styleable, builderStore } = getContext("sdk")
const component = getContext("component")
export let options
// Apex charts directly modifies the options object with default properties and internal variables. These being present could unintentionally cause issues to the provider of this prop as the changes are reflected in that component as well. To prevent any issues we clone options here to provide a buffer.
$: optionsCopy = cloneDeep(options)
let chartElement
let chart
let currentType = null
const updateChart = async newOptions => {
// Line charts have issues transitioning between "datetime" and "category" types, and will ignore the provided formatters
// in certain scenarios. Rerendering the chart when the user changes label type fixes this, but unfortunately it does
// cause a little bit of jankiness with animations.
if (newOptions?.xaxis?.type && newOptions.xaxis.type !== currentType) {
await renderChart(chartElement)
} else {
await chart?.updateOptions(newOptions)
}
}
const renderChart = async newChartElement => {
try {
await chart?.destroy()
chart = new ApexCharts(newChartElement, optionsCopy)
currentType = optionsCopy?.xaxis?.type
await chart.render()
} catch (e) {
// Apex for some reason throws this error when creating a new chart.
// It doesn't actually cause any issues with the function of the chart, so
// just suppress it so the console doesn't get spammed
if (
e.message !==
"Cannot read properties of undefined (reading 'parentNode')"
) {
throw e
}
}
}
$: noData = optionsCopy == null || optionsCopy?.series?.length === 0
// Call render chart upon changes to noData, as apex charts has issues with rendering upon changes automatically
// if the chart is hidden.
$: renderChart(chartElement, noData)
$: updateChart(optionsCopy)
</script>
{#if options}
{#key options.customColor}
<div use:chart={options} use:styleable={$component.styles} />
{/key}
{:else if $builderStore.inBuilder}
<div use:styleable={$component.styles}>
<Placeholder />
</div>
{/if}
{#key optionsCopy?.customColor}
<div
class:hide={noData}
use:styleable={$component.styles}
bind:this={chartElement}
/>
{#if $builderStore.inBuilder && noData}
<div
class="component-placeholder"
use:styleable={{
...$component.styles,
normal: {},
custom: null,
empty: true,
}}
>
<Icon name="Alert" color="var(--spectrum-global-color-static-red-600)" />
Add rows to your data source to start using your component
</div>
{/if}
{/key}
<style>
.hide {
display: none;
}
div :global(.apexcharts-legend-series) {
display: flex !important;
text-transform: capitalize;
@ -59,4 +119,25 @@
) {
padding-bottom: 0;
}
.component-placeholder {
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
color: var(--spectrum-global-color-gray-600);
font-size: var(--font-size-s);
padding: var(--spacing-xs);
gap: var(--spacing-s);
}
/* Common styles for all error states to use */
.component-placeholder :global(mark) {
background-color: var(--spectrum-global-color-gray-400);
padding: 0 4px;
border-radius: 2px;
}
.component-placeholder :global(.spectrum-Link) {
cursor: pointer;
}
</style>

View file

@ -1,195 +0,0 @@
export class ApexOptionsBuilder {
constructor() {
this.formatters = {
["Default"]: val => (isNaN(val) ? val : Math.round(val * 100) / 100),
["Thousands"]: val => `${Math.round(val / 1000)}K`,
["Millions"]: val => `${Math.round(val / 1000000)}M`,
}
this.options = {
series: [],
legend: {
show: false,
position: "top",
horizontalAlign: "right",
showForSingleSeries: true,
showForNullSeries: true,
showForZeroSeries: true,
},
chart: {
toolbar: {
show: false,
},
zoom: {
enabled: false,
},
},
xaxis: {
labels: {
formatter: this.formatters.Default,
},
},
yaxis: {
labels: {
formatter: this.formatters.Default,
},
},
}
}
setOption(path, value) {
if (value == null || value === "") {
return this
}
let tmp = this.options
for (let i = 0; i < path.length - 1; i++) {
const step = path[i]
if (!tmp[step]) {
tmp[step] = {}
}
tmp = tmp[step]
}
tmp[path[path.length - 1]] = value
return this
}
getOptions() {
return this.options
}
type(type) {
return this.setOption(["chart", "type"], type)
}
title(title) {
return this.setOption(["title", "text"], title)
}
colors(colors) {
if (!colors) {
delete this.options.colors
this.options["customColor"] = false
return this
}
this.options["customColor"] = true
return this.setOption(["colors"], colors)
}
width(width) {
return this.setOption(["chart", "width"], width || undefined)
}
height(height) {
return this.setOption(["chart", "height"], height || undefined)
}
xLabel(label) {
return this.setOption(["xaxis", "title", "text"], label)
}
yLabel(label) {
return this.setOption(["yaxis", "title", "text"], label)
}
xCategories(categories) {
return this.setOption(["xaxis", "categories"], categories)
}
yCategories(categories) {
return this.setOption(["yaxis", "categories"], categories)
}
series(series) {
return this.setOption(["series"], series)
}
horizontal(horizontal) {
return this.setOption(["plotOptions", "bar", "horizontal"], horizontal)
}
dataLabels(dataLabels) {
return this.setOption(["dataLabels", "enabled"], dataLabels)
}
animate(animate) {
return this.setOption(["chart", "animations", "enabled"], animate)
}
curve(curve) {
return this.setOption(["stroke", "curve"], curve)
}
gradient(gradient) {
const fill = {
type: "gradient",
gradient: {
shadeIntensity: 1,
opacityFrom: 0.7,
opacityTo: 0.9,
stops: [0, 90, 100],
},
}
return this.setOption(["fill"], gradient ? fill : undefined)
}
legend(legend) {
return this.setOption(["legend", "show"], legend)
}
legendPosition(position) {
return this.setOption(["legend", "position"], position)
}
stacked(stacked) {
return this.setOption(["chart", "stacked"], stacked)
}
labels(labels) {
return this.setOption(["labels"], labels)
}
xUnits(units) {
return this.setOption(
["xaxis", "labels", "formatter"],
this.formatters[units || "Default"]
)
}
yUnits(units) {
return this.setOption(
["yaxis", "labels", "formatter"],
this.formatters[units || "Default"]
)
}
clearXFormatter() {
delete this.options.xaxis.labels
return this
}
clearYFormatter() {
delete this.options.yaxis.labels
return this
}
xType(type) {
return this.setOption(["xaxis", "type"], type)
}
yType(type) {
return this.setOption(["yaxis", "type"], type)
}
yTooltip(yTooltip) {
return this.setOption(["yaxis", "tooltip", "enabled"], yTooltip)
}
palette(palette) {
if (!palette) {
return this
}
return this.setOption(
["theme", "palette"],
palette.toLowerCase().replace(/[\W]/g, "")
)
}
}

View file

@ -1,5 +1,159 @@
<script>
import LineChart from "./LineChart.svelte"
import ApexChart from "./ApexChart.svelte"
import { formatters, parsePalette } from "./utils"
export let title
export let dataProvider
export let labelColumn
export let valueColumns
export let xAxisLabel
export let yAxisLabel
export let height
export let width
export let animate
export let dataLabels
export let curve
export let legend
export let yAxisUnits
export let palette
export let c1, c2, c3, c4, c5
// Area specific props
export let area
export let stacked
export let gradient
$: series = getSeries(dataProvider, valueColumns)
$: categories = getCategories(dataProvider, labelColumn)
$: labelType =
dataProvider?.schema?.[labelColumn]?.type === "datetime"
? "datetime"
: "category"
$: xAxisFormatter = getFormatter(labelType, yAxisUnits, "x")
$: yAxisFormatter = getFormatter(labelType, yAxisUnits, "y")
$: fill = getFill(gradient)
$: options = {
series,
stroke: {
curve: curve.toLowerCase(),
},
colors: palette === "Custom" ? [c1, c2, c3, c4, c5] : [],
theme: {
palette: parsePalette(palette),
},
fill,
legend: {
show: legend,
position: "top",
horizontalAlign: "right",
showForSingleSeries: true,
showForNullSeries: true,
showForZeroSeries: true,
},
title: {
text: title,
},
dataLabels: {
enabled: dataLabels,
},
chart: {
height: height == null || height === "" ? "auto" : height,
width: width == null || width === "" ? "100%" : width,
type: "area",
stacked,
animations: {
enabled: animate,
},
toolbar: {
show: false,
},
zoom: {
enabled: false,
},
},
xaxis: {
type: labelType,
categories,
labels: {
formatter: xAxisFormatter,
},
title: {
text: xAxisLabel,
},
},
yaxis: {
labels: {
formatter: yAxisFormatter,
},
title: {
text: yAxisLabel,
},
},
}
const getSeries = (dataProvider, valueColumns = []) => {
const rows = dataProvider.rows ?? []
return valueColumns.map(column => ({
name: column,
data: rows.map(row => {
const value = row?.[column]
if (dataProvider?.schema?.[column]?.type === "datetime" && value) {
return Date.parse(value)
}
return value
}),
}))
}
const getCategories = (dataProvider, labelColumn) => {
const rows = dataProvider.rows ?? []
return rows.map(row => {
const value = row?.[labelColumn]
// If a nullish or non-scalar type, replace it with an empty string
if (!["string", "number", "boolean"].includes(typeof value)) {
return ""
}
return value
})
}
const getFormatter = (labelType, yAxisUnits, axis) => {
const isLabelAxis = axis === "x"
if (labelType === "datetime" && isLabelAxis) {
return formatters["Datetime"]
}
if (isLabelAxis) {
return formatters["Default"]
}
return formatters[yAxisUnits]
}
const getFill = gradient => {
if (gradient) {
return {
type: "gradient",
gradient: {
shadeIntensity: 1,
opacityFrom: 0.7,
opacityTo: 0.9,
stops: [0, 90, 100],
},
}
}
return { type: "solid" }
}
</script>
<LineChart {...$$props} area />
<ApexChart {options} />

View file

@ -1,11 +1,12 @@
<script>
import { ApexOptionsBuilder } from "./ApexOptionsBuilder"
import ApexChart from "./ApexChart.svelte"
import { formatters, parsePalette } from "./utils"
export let title
export let dataProvider
export let labelColumn
export let valueColumns
export let title
export let xAxisLabel
export let yAxisLabel
export let height
@ -19,116 +20,122 @@
export let c1, c2, c3, c4, c5
export let horizontal
$: options = setUpChart(
title,
dataProvider,
labelColumn,
valueColumns,
xAxisLabel,
yAxisLabel,
height,
width,
dataLabels,
animate,
legend,
stacked,
yAxisUnits,
palette,
horizontal,
c1 && c2 && c3 && c4 && c5 ? [c1, c2, c3, c4, c5] : null,
customColor
)
$: series = getSeries(dataProvider, valueColumns)
$: categories = getCategories(dataProvider, labelColumn)
$: customColor = palette === "Custom"
$: labelType =
dataProvider?.schema?.[labelColumn]?.type === "datetime"
? "datetime"
: "category"
$: xAxisFormatter = getFormatter(labelType, yAxisUnits, horizontal, "x")
$: yAxisFormatter = getFormatter(labelType, yAxisUnits, horizontal, "y")
const setUpChart = (
title,
dataProvider,
labelColumn,
valueColumns,
xAxisLabel,
yAxisLabel,
height,
width,
dataLabels,
animate,
legend,
stacked,
yAxisUnits,
palette,
horizontal,
colors,
customColor
) => {
const allCols = [labelColumn, ...(valueColumns || [null])]
if (
!dataProvider ||
!dataProvider.rows?.length ||
allCols.find(x => x == null)
) {
return null
}
$: options = {
series,
colors: palette === "Custom" ? [c1, c2, c3, c4, c5] : [],
theme: {
palette: parsePalette(palette),
},
legend: {
show: legend,
position: "top",
horizontalAlign: "right",
showForSingleSeries: true,
showForNullSeries: true,
showForZeroSeries: true,
},
title: {
text: title,
},
dataLabels: {
enabled: dataLabels,
},
chart: {
height: height == null || height === "" ? "auto" : height,
width: width == null || width === "" ? "100%" : width,
type: "bar",
stacked,
animations: {
enabled: animate,
},
toolbar: {
show: false,
},
zoom: {
enabled: false,
},
},
plotOptions: {
bar: {
horizontal,
},
},
// We can just always provide the categories to the xaxis and horizontal mode automatically handles "tranposing" the categories to the yaxis, but certain things like labels need to be manually put on a certain axis based on the selected mode. Titles do not need to be handled this way, they are exposed to the user as "X axis" and Y Axis" so flipping them would be confusing.
xaxis: {
type: labelType,
categories,
labels: {
formatter: xAxisFormatter,
},
title: {
text: xAxisLabel,
},
},
// Providing `type: "datetime"` normally makes Apex Charts parse unix time nicely with no additonal config, but bar charts in horizontal mode don't have a default setting for parsing the labels of dates, and will just spit out the unix time value. It also doesn't seem to respect any date based formatting properties passed in. So we'll just manually format the labels, the chart still sorts the dates correctly in any case
yaxis: {
labels: {
formatter: yAxisFormatter,
},
title: {
text: yAxisLabel,
},
},
}
// Fetch data
const { schema, rows } = dataProvider
const reducer = row => (valid, column) => valid && row[column] != null
const hasAllColumns = row => allCols.reduce(reducer(row), true)
const data = rows.filter(row => hasAllColumns(row)).slice(0, 100)
if (!schema || !data.length) {
return null
}
const getSeries = (dataProvider, valueColumns = []) => {
const rows = dataProvider.rows ?? []
// Initialise default chart
let builder = new ApexOptionsBuilder()
.type("bar")
.title(title)
.width(width)
.height(height)
.xLabel(xAxisLabel)
.yLabel(yAxisLabel)
.dataLabels(dataLabels)
.animate(animate)
.legend(legend)
.stacked(stacked)
.palette(palette)
.horizontal(horizontal)
.colors(customColor ? colors : null)
// Add data
let useDates = false
if (schema[labelColumn]) {
const labelFieldType = schema[labelColumn].type
if (horizontal) {
builder = builder.yType(labelFieldType).xUnits(yAxisUnits)
} else {
builder = builder.xType(labelFieldType).yUnits(yAxisUnits)
}
useDates = labelFieldType === "datetime"
}
const series = valueColumns.map(column => ({
return valueColumns.map(column => ({
name: column,
data: data.map(row => {
if (!useDates) {
return row[column]
} else {
return [row[labelColumn], row[column]]
data: rows.map(row => {
const value = row?.[column]
if (dataProvider?.schema?.[column]?.type === "datetime" && value) {
return Date.parse(value)
}
return value
}),
}))
builder = builder.series(series)
if (!useDates) {
builder = builder.xCategories(data.map(row => row[labelColumn]))
} else {
// Horizontal dates don't work anyway, but this is the correct logic
if (horizontal) {
builder = builder.clearYFormatter()
} else {
builder = builder.clearXFormatter()
}
const getCategories = (dataProvider, labelColumn) => {
const rows = dataProvider.rows ?? []
return rows.map(row => {
const value = row?.[labelColumn]
// If a nullish or non-scalar type, replace it with an empty string
if (!["string", "number", "boolean"].includes(typeof value)) {
return ""
}
return value
})
}
const getFormatter = (labelType, yAxisUnits, horizontal, axis) => {
const isLabelAxis =
(axis === "y" && horizontal) || (axis === "x" && !horizontal)
if (labelType === "datetime" && isLabelAxis) {
return formatters["Datetime"]
}
// Build chart options
return builder.getOptions()
if (isLabelAxis) {
return formatters["Default"]
}
return formatters[yAxisUnits]
}
</script>

View file

@ -1,6 +1,6 @@
<script>
import { ApexOptionsBuilder } from "./ApexOptionsBuilder"
import ApexChart from "./ApexChart.svelte"
import { formatters } from "./utils"
export let title
export let dataProvider
@ -16,78 +16,121 @@
export let animate
export let yAxisUnits
$: options = setUpChart(
title,
$: series = getSeries(
dataProvider,
dateColumn,
openColumn,
highColumn,
lowColumn,
closeColumn,
xAxisLabel,
yAxisLabel,
height,
width,
animate,
yAxisUnits
closeColumn
)
const setUpChart = (
title,
$: options = {
series,
title: {
text: title,
},
chart: {
height: height == null || height === "" ? "auto" : height,
width: width == null || width === "" ? "100%" : width,
type: "candlestick",
animations: {
enabled: animate,
},
toolbar: {
show: false,
},
zoom: {
enabled: false,
},
},
xaxis: {
tooltip: {
formatter: formatters["Datetime"],
},
type: "datetime",
title: {
text: xAxisLabel,
},
},
yaxis: {
labels: {
formatter: formatters[yAxisUnits],
},
title: {
text: yAxisLabel,
},
},
}
const getValueAsUnixTime = (dataprovider, dateColumn, row) => {
const value = row[dateColumn]
if (dataProvider?.schema?.[dateColumn]?.type === "datetime") {
return Date.parse(value)
}
if (typeof value === "number") {
return value
}
const isString = typeof value === "string"
// "2025" could be either an ISO 8601 datetime string or Unix time.
// There's no way to tell the user's intent without providing more
// granular controls.
// We'll just assume any string without dashes is Unix time.
if (isString && value.includes("-")) {
const unixTime = Date.parse(value)
if (isNaN(unixTime)) {
return null
}
return unixTime
}
if (isString) {
const unixTime = parseInt(value, 10)
if (isNaN(unixTime)) {
return null
}
return unixTime
}
return null
}
const getSeries = (
dataProvider,
dateColumn,
openColumn,
highColumn,
lowColumn,
closeColumn,
xAxisLabel,
yAxisLabel,
height,
width,
animate,
yAxisUnits
closeColumn
) => {
const allCols = [dateColumn, openColumn, highColumn, lowColumn, closeColumn]
if (
!dataProvider ||
!dataProvider.rows?.length ||
allCols.find(x => x == null)
) {
return null
}
const rows = dataProvider.rows ?? []
// Fetch data
const { schema, rows } = dataProvider
const reducer = row => (valid, column) => valid && row[column] != null
const hasAllColumns = row => allCols.reduce(reducer(row), true)
const data = rows.filter(row => hasAllColumns(row))
if (!schema || !data.length) {
return null
}
return [
{
data: rows.map(row => {
const open = parseFloat(row[openColumn])
const high = parseFloat(row[highColumn])
const low = parseFloat(row[lowColumn])
const close = parseFloat(row[closeColumn])
// Initialise default chart
let builder = new ApexOptionsBuilder()
.type("candlestick")
.title(title)
.width(width)
.height(height)
.xLabel(xAxisLabel)
.yLabel(yAxisLabel)
.animate(animate)
.yUnits(yAxisUnits)
.yTooltip(true)
.xType("datetime")
// Add data
const parseDate = d => (isNaN(d) ? Date.parse(d).valueOf() : parseInt(d))
const chartData = data.map(row => ({
x: parseDate(row[dateColumn]),
y: [row[openColumn], row[highColumn], row[lowColumn], row[closeColumn]],
}))
builder = builder.series([{ data: chartData }])
// Build chart options
return builder.getOptions()
return [
getValueAsUnixTime(dataProvider, dateColumn, row),
isNaN(open) ? 0 : open,
isNaN(high) ? 0 : high,
isNaN(low) ? 0 : low,
isNaN(close) ? 0 : close,
]
}),
},
]
}
</script>

View file

@ -1,5 +1,99 @@
<script>
import PieChart from "./PieChart.svelte"
import ApexChart from "./ApexChart.svelte"
import { formatters, parsePalette } from "./utils"
export let title
export let dataProvider
export let labelColumn
export let valueColumn
export let height
export let width
export let animate
export let dataLabels
export let legend
export let palette
export let c1, c2, c3, c4, c5
$: labelType =
dataProvider?.schema?.[labelColumn]?.type === "datetime"
? "datetime"
: "category"
$: series = getSeries(dataProvider, valueColumn)
$: labels = getLabels(dataProvider, labelColumn, labelType)
$: options = {
series,
labels,
colors: palette === "Custom" ? [c1, c2, c3, c4, c5] : [],
theme: {
palette: parsePalette(palette),
},
legend: {
show: legend,
position: "right",
horizontalAlign: "right",
showForSingleSeries: true,
showForNullSeries: true,
showForZeroSeries: true,
},
title: {
text: title,
},
dataLabels: {
enabled: dataLabels,
},
chart: {
height: height == null || height === "" ? "auto" : height,
width: width == null || width === "" ? "100%" : width,
type: "donut",
animations: {
enabled: animate,
},
toolbar: {
show: false,
},
zoom: {
enabled: false,
},
},
}
const getSeries = (dataProvider, valueColumn) => {
const rows = dataProvider.rows ?? []
return rows.map(row => {
const value = row?.[valueColumn]
if (dataProvider?.schema?.[valueColumn]?.type === "datetime" && value) {
return Date.parse(value)
}
// This chart doesn't automatically parse strings into numbers
const numValue = parseFloat(value)
if (isNaN(numValue)) {
return 0
}
return numValue
})
}
const getLabels = (dataProvider, labelColumn, labelType) => {
const rows = dataProvider.rows ?? []
return rows.map(row => {
const value = row?.[labelColumn]
// If a nullish or non-scalar type, replace it with an empty string
if (!["string", "number", "boolean"].includes(typeof value)) {
return ""
} else if (labelType === "datetime") {
return formatters["Datetime"](value)
}
return value
})
}
</script>
<PieChart {...$$props} donut />
<ApexChart {options} />

View file

@ -1,135 +1,154 @@
<script>
import { ApexOptionsBuilder } from "./ApexOptionsBuilder"
import ApexChart from "./ApexChart.svelte"
import { parsePalette } from "./utils"
export let title
export let dataProvider
export let valueColumn
export let title
export let xAxisLabel
export let yAxisLabel
export let height
export let width
export let dataLabels
export let animate
export let stacked
export let palette
export let c1, c2, c3, c4, c5
export let horizontal
export let bucketCount = 10
$: options = setUpChart(
title,
dataProvider,
valueColumn,
xAxisLabel || valueColumn,
yAxisLabel,
height,
width,
dataLabels,
animate,
palette,
horizontal,
c1 && c2 && c3 && c4 && c5 ? [c1, c2, c3, c4, c5] : null,
customColor,
bucketCount
)
$: series = getSeries(dataProvider, valueColumn, bucketCount)
$: customColor = palette === "Custom"
$: xAxisFormatter = getFormatter(horizontal, "x")
$: yAxisFormatter = getFormatter(horizontal, "y")
const setUpChart = (
title,
dataProvider,
valueColumn,
xAxisLabel, //freqAxisLabel
yAxisLabel, //valueAxisLabel
height,
width,
dataLabels,
animate,
palette,
horizontal,
colors,
customColor,
bucketCount
) => {
const allCols = [valueColumn]
if (
!dataProvider ||
!dataProvider.rows?.length ||
allCols.find(x => x == null)
) {
return null
}
$: options = {
series,
colors: palette === "Custom" ? [c1, c2, c3, c4, c5] : [],
theme: {
palette: parsePalette(palette),
},
title: {
text: title,
},
dataLabels: {
enabled: dataLabels,
},
chart: {
height: height == null || height === "" ? "auto" : height,
width: width == null || width === "" ? "100%" : width,
type: "bar",
stacked,
animations: {
enabled: animate,
},
toolbar: {
show: false,
},
zoom: {
enabled: false,
},
},
plotOptions: {
bar: {
horizontal,
},
},
xaxis: {
type: "category",
title: {
text: xAxisLabel,
},
labels: {
formatter: xAxisFormatter,
},
},
yaxis: {
decimalsInFloat: 0,
title: {
text: yAxisLabel,
},
labels: {
formatter: yAxisFormatter,
},
},
}
// Fetch data
const { schema, rows } = dataProvider
const reducer = row => (valid, column) => valid && row[column] != null
const hasAllColumns = row => allCols.reduce(reducer(row), true)
const data = rows.filter(row => hasAllColumns(row)).slice(0, 100)
if (!schema || !data.length) {
return null
}
const getSeries = (dataProvider, valueColumn, bucketCount) => {
const rows = dataProvider.rows ?? []
// Initialise default chart
let builder = new ApexOptionsBuilder()
.type("bar")
.title(title)
.width(width)
.height(height)
.xLabel(horizontal ? yAxisLabel : xAxisLabel)
.yLabel(horizontal ? xAxisLabel : yAxisLabel)
.dataLabels(dataLabels)
.animate(animate)
.palette(palette)
.horizontal(horizontal)
.colors(customColor ? colors : null)
const values = rows
.map(row => parseFloat(row[valueColumn]))
.filter(value => !isNaN(value))
const [min, max] = getValuesRange(values)
const buckets = getBuckets(min, max, bucketCount)
const counts = Array(bucketCount).fill(0)
if (horizontal) {
builder = builder.setOption(["plotOptions", "bar", "barHeight"], "90%")
} else {
builder = builder.setOption(["plotOptions", "bar", "columnWidth"], "99%")
}
values.forEach(value => {
const bucketIndex = buckets.findIndex(
bucket => bucket.min <= value && value <= bucket.max
)
// Pull occurences of the value.
let flatlist = data.map(row => {
return row[valueColumn]
counts[bucketIndex]++
})
// Build range buckets
let interval = Math.max(...flatlist) / bucketCount
let counts = Array(bucketCount).fill(0)
const series = buckets.map((bucket, index) => ({
x: `${bucket.min} ${bucket.max}`,
y: counts[index],
}))
// Assign row data to a bucket
let buckets = flatlist.reduce((acc, val) => {
let dest = Math.min(Math.floor(val / interval), bucketCount - 1)
acc[dest] = acc[dest] + 1
return acc
}, counts)
return [{ data: series }]
}
const rangeLabel = bucketIdx => {
return `${Math.floor(interval * bucketIdx)} - ${Math.floor(
interval * (bucketIdx + 1)
)}`
const getValuesRange = values => {
// Ensure min is nearest integer including the actual minimum e.g.`-10.2` -> `-11`
const min = Math.floor(Math.min(...values))
// Ensure max is nearest integer including the actual maximum e.g. `20.2` -> `21`
const max = Math.ceil(Math.max(...values))
return [min, max]
}
const getBuckets = (min, max, bucketCount) => {
// Assure bucketCount is >= 2 and an integer
bucketCount = bucketCount < 2 ? 2 : Math.floor(bucketCount)
const range = max - min
// Assure bucketSize is never a decimal value, we'll redistribute any size truncated here later
const bucketSize = Math.floor(range / bucketCount)
const bucketRemainder = range - bucketSize * bucketCount
const buckets = []
for (let i = 0; i < bucketCount; i++) {
const lastBucketMax = buckets?.[buckets.length - 1]?.max ?? min
// Distribute any remaining size, the remainder will never be larger than the number of buckets
const remainderPadding = i < bucketRemainder ? 1 : 0
buckets.push({
min: lastBucketMax,
max: lastBucketMax + bucketSize + remainderPadding,
})
}
const series = [
{
name: yAxisLabel,
data: Array.from({ length: buckets.length }, (_, i) => ({
x: rangeLabel(i),
y: buckets[i],
})),
},
]
return buckets
}
builder = builder.setOption(["xaxis", "labels"], {
formatter: x => {
return x + ""
},
})
const getFormatter = (horizontal, axis) => {
// Don't display decimals in between integers on the value axis
if ((horizontal && axis === "x") || (!horizontal && axis === "y")) {
return value => {
if (Math.floor(value) === value) {
return value
}
builder = builder.series(series)
// Returning an empty string or even a normal space here causes Apex Charts to push the value axis label of the screen
// This is an `em space`, `U+2003`
return ""
}
}
return builder.getOptions()
return value => value
}
</script>

View file

@ -1,8 +1,7 @@
<script>
import { ApexOptionsBuilder } from "./ApexOptionsBuilder"
import ApexChart from "./ApexChart.svelte"
import { formatters, parsePalette } from "./utils"
// Common props
export let title
export let dataProvider
export let labelColumn
@ -19,118 +18,117 @@
export let palette
export let c1, c2, c3, c4, c5
// Area specific props
export let area
export let stacked
export let gradient
$: series = getSeries(dataProvider, valueColumns)
$: categories = getCategories(dataProvider, labelColumn)
$: options = setUpChart(
title,
dataProvider,
labelColumn,
valueColumns,
xAxisLabel,
yAxisLabel,
height,
width,
animate,
dataLabels,
curve,
legend,
yAxisUnits,
palette,
area,
stacked,
gradient,
c1 && c2 && c3 && c4 && c5 ? [c1, c2, c3, c4, c5] : null,
customColor
)
$: labelType =
dataProvider?.schema?.[labelColumn]?.type === "datetime"
? "datetime"
: "category"
$: xAxisFormatter = getFormatter(labelType, yAxisUnits, "x")
$: yAxisFormatter = getFormatter(labelType, yAxisUnits, "y")
$: customColor = palette === "Custom"
$: options = {
series,
stroke: {
curve: curve.toLowerCase(),
},
colors: palette === "Custom" ? [c1, c2, c3, c4, c5] : [],
theme: {
palette: parsePalette(palette),
},
legend: {
show: legend,
position: "top",
horizontalAlign: "right",
showForSingleSeries: true,
showForNullSeries: true,
showForZeroSeries: true,
},
title: {
text: title,
},
dataLabels: {
enabled: dataLabels,
},
chart: {
height: height == null || height === "" ? "auto" : height,
width: width == null || width === "" ? "100%" : width,
type: "line",
animations: {
enabled: animate,
},
toolbar: {
show: false,
},
zoom: {
enabled: false,
},
},
xaxis: {
type: labelType,
categories,
labels: {
formatter: xAxisFormatter,
},
title: {
text: xAxisLabel,
},
},
yaxis: {
labels: {
formatter: yAxisFormatter,
},
title: {
text: yAxisLabel,
},
},
}
const setUpChart = (
title,
dataProvider,
labelColumn,
valueColumns,
xAxisLabel,
yAxisLabel,
height,
width,
animate,
dataLabels,
curve,
legend,
yAxisUnits,
palette,
area,
stacked,
gradient,
colors,
customColor
) => {
const allCols = [labelColumn, ...(valueColumns || [null])]
if (
!dataProvider ||
!dataProvider.rows?.length ||
allCols.find(x => x == null)
) {
return null
}
const getSeries = (dataProvider, valueColumns = []) => {
const rows = dataProvider.rows ?? []
// Fetch, filter and sort data
const { schema, rows } = dataProvider
const reducer = row => (valid, column) => valid && row[column] != null
const hasAllColumns = row => allCols.reduce(reducer(row), true)
const data = rows.filter(row => hasAllColumns(row))
if (!schema || !data.length) {
return null
}
// Initialise default chart
let builder = new ApexOptionsBuilder()
.title(title)
.type(area ? "area" : "line")
.width(width)
.height(height)
.xLabel(xAxisLabel)
.yLabel(yAxisLabel)
.dataLabels(dataLabels)
.animate(animate)
.curve(curve.toLowerCase())
.gradient(gradient)
.stacked(stacked)
.legend(legend)
.yUnits(yAxisUnits)
.palette(palette)
.colors(customColor ? colors : null)
// Add data
let useDates = false
if (schema[labelColumn]) {
const labelFieldType = schema[labelColumn].type
builder = builder.xType(labelFieldType)
useDates = labelFieldType === "datetime"
}
const series = valueColumns.map(column => ({
return valueColumns.map(column => ({
name: column,
data: data.map(row => {
if (!useDates) {
return row[column]
} else {
return [row[labelColumn], row[column]]
data: rows.map(row => {
const value = row?.[column]
if (dataProvider?.schema?.[column]?.type === "datetime" && value) {
return Date.parse(value)
}
return value
}),
}))
builder = builder.series(series)
if (!useDates) {
builder = builder.xCategories(data.map(row => row[labelColumn]))
} else {
builder = builder.clearXFormatter()
}
const getCategories = (dataProvider, labelColumn) => {
const rows = dataProvider.rows ?? []
return rows.map(row => {
const value = row?.[labelColumn]
// If a nullish or non-scalar type, replace it with an empty string
if (!["string", "number", "boolean"].includes(typeof value)) {
return ""
}
return value
})
}
const getFormatter = (labelType, yAxisUnits, axis) => {
const isLabelAxis = axis === "x"
if (labelType === "datetime" && isLabelAxis) {
return formatters["Datetime"]
}
// Build chart options
return builder.getOptions()
if (isLabelAxis) {
return formatters["Default"]
}
return formatters[yAxisUnits]
}
</script>

View file

@ -1,6 +1,6 @@
<script>
import { ApexOptionsBuilder } from "./ApexOptionsBuilder"
import ApexChart from "./ApexChart.svelte"
import { formatters, parsePalette } from "./utils"
export let title
export let dataProvider
@ -8,84 +8,91 @@
export let valueColumn
export let height
export let width
export let dataLabels
export let animate
export let dataLabels
export let legend
export let donut
export let palette
export let c1, c2, c3, c4, c5
$: options = setUpChart(
title,
dataProvider,
labelColumn,
valueColumn,
height,
width,
dataLabels,
animate,
legend,
donut,
palette,
c1 && c2 && c3 && c4 && c5 ? [c1, c2, c3, c4, c5] : null,
customColor
)
$: labelType =
dataProvider?.schema?.[labelColumn]?.type === "datetime"
? "datetime"
: "category"
$: series = getSeries(dataProvider, valueColumn)
$: labels = getLabels(dataProvider, labelColumn, labelType)
$: customColor = palette === "Custom"
$: options = {
series,
labels,
colors: palette === "Custom" ? [c1, c2, c3, c4, c5] : [],
theme: {
palette: parsePalette(palette),
},
legend: {
show: legend,
position: "right",
horizontalAlign: "right",
showForSingleSeries: true,
showForNullSeries: true,
showForZeroSeries: true,
},
title: {
text: title,
},
dataLabels: {
enabled: dataLabels,
},
chart: {
height: height == null || height === "" ? "auto" : height,
width: width == null || width === "" ? "100%" : width,
type: "pie",
animations: {
enabled: animate,
},
toolbar: {
show: false,
},
zoom: {
enabled: false,
},
},
}
const setUpChart = (
title,
dataProvider,
labelColumn,
valueColumn,
height,
width,
dataLabels,
animate,
legend,
donut,
palette,
colors,
customColor
) => {
if (
!dataProvider ||
!dataProvider.rows?.length ||
!labelColumn ||
!valueColumn
) {
return null
}
const getSeries = (dataProvider, valueColumn) => {
const rows = dataProvider.rows ?? []
// Fetch, filter and sort data
const { schema, rows } = dataProvider
const data = rows
.filter(row => row[labelColumn] != null && row[valueColumn] != null)
.slice(0, 100)
if (!schema || !data.length) {
return null
}
return rows.map(row => {
const value = row?.[valueColumn]
// Initialise default chart
let builder = new ApexOptionsBuilder()
.title(title)
.type(donut ? "donut" : "pie")
.width(width)
.height(height)
.dataLabels(dataLabels)
.animate(animate)
.legend(legend)
.legendPosition("right")
.palette(palette)
.colors(customColor ? colors : null)
if (dataProvider?.schema?.[valueColumn]?.type === "datetime" && value) {
return Date.parse(value)
}
// Add data if valid datasource
const series = data.map(row => parseFloat(row[valueColumn]))
const labels = data.map(row => row[labelColumn])
builder = builder.series(series).labels(labels)
// This chart doesn't automatically parse strings into numbers
const numValue = parseFloat(value)
if (isNaN(numValue)) {
return 0
}
// Build chart options
return builder.getOptions()
return numValue
})
}
const getLabels = (dataProvider, labelColumn, labelType) => {
const rows = dataProvider.rows ?? []
return rows.map(row => {
const value = row?.[labelColumn]
// If a nullish or non-scalar type, replace it with an empty string
if (!["string", "number", "boolean"].includes(typeof value)) {
return ""
} else if (labelType === "datetime") {
return formatters["Datetime"](value)
}
return value
})
}
</script>

View file

@ -0,0 +1,51 @@
export const formatters = {
["Default"]: val => val,
["Thousands"]: val => `${Math.round(val / 1000)}K`,
["Millions"]: val => `${Math.round(val / 1000000)}M`,
["Datetime"]: val => new Date(val).toLocaleString(),
}
export const parsePalette = paletteName => {
if (paletteName === "Custom") {
// return null in this case so that the palette option doesn't get consumed by Apex Charts
return null
}
const [_, number] = paletteName.split(" ")
return `palette${number}`
}
// Deep clone which copies function references
export const cloneDeep = value => {
const typesToNaiveCopy = ["string", "boolean", "number", "function", "symbol"]
if (value === null) {
return null
}
if (value === undefined) {
return undefined
}
if (typesToNaiveCopy.includes(typeof value)) {
return value
}
if (Array.isArray(value)) {
return value.map(element => cloneDeep(element))
}
// Only copy "pure" objects, we want to error on stuff like Maps or Sets
if (typeof value === "object" && value.constructor.name === "Object") {
const cloneObject = {}
Object.entries(value).forEach(([key, childValue]) => {
cloneObject[key] = cloneDeep(childValue)
})
return cloneObject
}
throw `Unsupported value: "${value}" of type: "${typeof value}"`
}

View file

@ -0,0 +1,31 @@
import { expect, describe, it, vi } from "vitest"
import { cloneDeep } from "./utils"
describe("utils", () => {
let context
beforeEach(() => {
vi.clearAllMocks()
context = {}
})
describe("cloneDeep", () => {
beforeEach(() => {
context.value = {
obj: { one: 1, two: 2 },
arr: [1, { first: null, second: undefined }, 2],
str: "test",
num: 123,
bool: true,
sym: Symbol("test"),
func: () => "some value",
}
context.cloneValue = cloneDeep(context.value)
})
it("to clone the object and not copy object references", () => {
expect(context.cloneValue.obj.one).toEqual(1)
expect(context.cloneValue.obj.two).toEqual(2)
})
})
})

View file

@ -108,7 +108,12 @@ export const getSettingsDefinition = definition => {
let settings = []
definition.settings?.forEach(setting => {
if (setting.section) {
settings = settings.concat(setting.settings || [])
settings = settings.concat(
(setting.settings || [])?.map(childSetting => ({
...childSetting,
sectionDependsOn: setting.dependsOn,
}))
)
} else {
settings.push(setting)
}

View file

@ -20,7 +20,8 @@ export async function fetchAppComponentDefinitions(ctx: UserCtx) {
const definitions: { [key: string]: any } = {}
for (let { manifest, library } of componentManifests) {
for (let key of Object.keys(manifest)) {
if (key === "features") {
// These keys are not components, and should not be preprended with the `@budibase/` prefix
if (key === "features" || key === "typeSupportPresets") {
definitions[key] = manifest[key]
} else {
const fullComponentName = `${library}/${key}`.toLowerCase()

361
yarn.lock
View file

@ -2499,11 +2499,31 @@
minimatch "^3.1.2"
strip-json-comments "^3.1.1"
"@eslint/eslintrc@^2.1.4":
version "2.1.4"
resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.4.tgz#388a269f0f25c1b6adc317b5a2c55714894c70ad"
integrity sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==
dependencies:
ajv "^6.12.4"
debug "^4.3.2"
espree "^9.6.0"
globals "^13.19.0"
ignore "^5.2.0"
import-fresh "^3.2.1"
js-yaml "^4.1.0"
minimatch "^3.1.2"
strip-json-comments "^3.1.1"
"@eslint/js@8.52.0":
version "8.52.0"
resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.52.0.tgz#78fe5f117840f69dc4a353adf9b9cd926353378c"
integrity sha512-mjZVbpaeMZludF2fsWLD0Z9gCref1Tk4i9+wddjRvpUNqqcndPkBD09N/Mapey0b3jaXbLm2kICwFv2E64QinA==
"@eslint/js@8.57.0":
version "8.57.0"
resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.57.0.tgz#a5417ae8427873f1dd08b70b3574b453e67b5f7f"
integrity sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==
"@fastify/busboy@^2.0.0":
version "2.1.0"
resolved "https://registry.yarnpkg.com/@fastify/busboy/-/busboy-2.1.0.tgz#0709e9f4cb252351c609c6e6d8d6779a8d25edff"
@ -2676,6 +2696,15 @@
debug "^4.1.1"
minimatch "^3.0.5"
"@humanwhocodes/config-array@^0.11.14":
version "0.11.14"
resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.14.tgz#d78e481a039f7566ecc9660b4ea7fe6b1fec442b"
integrity sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==
dependencies:
"@humanwhocodes/object-schema" "^2.0.2"
debug "^4.3.1"
minimatch "^3.0.5"
"@humanwhocodes/module-importer@^1.0.1":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c"
@ -2686,6 +2715,11 @@
resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz#e5211452df060fa8522b55c7b3c0c4d1981cb044"
integrity sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==
"@humanwhocodes/object-schema@^2.0.2":
version "2.0.3"
resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz#4a2868d75d6d6963e423bcf90b7fd1be343409d3"
integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==
"@hutson/parse-repository-url@^3.0.0":
version "3.0.2"
resolved "https://registry.yarnpkg.com/@hutson/parse-repository-url/-/parse-repository-url-3.0.2.tgz#98c23c950a3d9b6c8f0daed06da6c3af06981340"
@ -3216,16 +3250,35 @@
"@jridgewell/sourcemap-codec" "^1.4.10"
"@jridgewell/trace-mapping" "^0.3.9"
"@jridgewell/gen-mapping@^0.3.5":
version "0.3.5"
resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz#dcce6aff74bdf6dad1a95802b69b04a2fcb1fb36"
integrity sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==
dependencies:
"@jridgewell/set-array" "^1.2.1"
"@jridgewell/sourcemap-codec" "^1.4.10"
"@jridgewell/trace-mapping" "^0.3.24"
"@jridgewell/resolve-uri@3.1.0", "@jridgewell/resolve-uri@^3.0.3":
version "3.1.0"
resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78"
integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==
"@jridgewell/resolve-uri@^3.1.0":
version "3.1.2"
resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6"
integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==
"@jridgewell/set-array@^1.0.1":
version "1.1.2"
resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72"
integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==
"@jridgewell/set-array@^1.2.1":
version "1.2.1"
resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.2.1.tgz#558fb6472ed16a4c850b889530e6b36438c49280"
integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==
"@jridgewell/source-map@^0.3.2":
version "0.3.2"
resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.2.tgz#f45351aaed4527a298512ec72f81040c998580fb"
@ -3234,6 +3287,14 @@
"@jridgewell/gen-mapping" "^0.3.0"
"@jridgewell/trace-mapping" "^0.3.9"
"@jridgewell/source-map@^0.3.3":
version "0.3.6"
resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.6.tgz#9d71ca886e32502eb9362c9a74a46787c36df81a"
integrity sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==
dependencies:
"@jridgewell/gen-mapping" "^0.3.5"
"@jridgewell/trace-mapping" "^0.3.25"
"@jridgewell/sourcemap-codec@1.4.14":
version "1.4.14"
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24"
@ -3260,6 +3321,14 @@
"@jridgewell/resolve-uri" "3.1.0"
"@jridgewell/sourcemap-codec" "1.4.14"
"@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25":
version "0.3.25"
resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0"
integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==
dependencies:
"@jridgewell/resolve-uri" "^3.1.0"
"@jridgewell/sourcemap-codec" "^1.4.14"
"@js-joda/core@^5.5.3":
version "5.6.1"
resolved "https://registry.yarnpkg.com/@js-joda/core/-/core-5.6.1.tgz#03e2453d877b61c3f593cf031fd18b375bd548b6"
@ -3926,6 +3995,15 @@
"@rollup/pluginutils" "^5.0.1"
magic-string "^0.30.3"
"@rollup/plugin-terser@^0.4.4":
version "0.4.4"
resolved "https://registry.yarnpkg.com/@rollup/plugin-terser/-/plugin-terser-0.4.4.tgz#15dffdb3f73f121aa4fbb37e7ca6be9aeea91962"
integrity sha512-XHeJC5Bgvs8LfukDwWZp7yeqin6ns8RTl2B9avbejt6tZqsqvVoWI7ZTQrcNsfKEDWBTnTxM8nMDkO2IFFbd0A==
dependencies:
serialize-javascript "^6.0.1"
smob "^1.0.0"
terser "^5.17.4"
"@rollup/plugin-typescript@8.3.0":
version "8.3.0"
resolved "https://registry.yarnpkg.com/@rollup/plugin-typescript/-/plugin-typescript-8.3.0.tgz#bc1077fa5897b980fc27e376c4e377882c63e68b"
@ -3965,66 +4043,146 @@
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.12.0.tgz#38c3abd1955a3c21d492af6b1a1dca4bb1d894d6"
integrity sha512-+ac02NL/2TCKRrJu2wffk1kZ+RyqxVUlbjSagNgPm94frxtr+XDL12E5Ll1enWskLrtrZ2r8L3wED1orIibV/w==
"@rollup/rollup-android-arm-eabi@4.17.2":
version "4.17.2"
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.17.2.tgz#1a32112822660ee104c5dd3a7c595e26100d4c2d"
integrity sha512-NM0jFxY8bB8QLkoKxIQeObCaDlJKewVlIEkuyYKm5An1tdVZ966w2+MPQ2l8LBZLjR+SgyV+nRkTIunzOYBMLQ==
"@rollup/rollup-android-arm64@4.12.0":
version "4.12.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.12.0.tgz#3822e929f415627609e53b11cec9a4be806de0e2"
integrity sha512-OBqcX2BMe6nvjQ0Nyp7cC90cnumt8PXmO7Dp3gfAju/6YwG0Tj74z1vKrfRz7qAv23nBcYM8BCbhrsWqO7PzQQ==
"@rollup/rollup-android-arm64@4.17.2":
version "4.17.2"
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.17.2.tgz#5aeef206d65ff4db423f3a93f71af91b28662c5b"
integrity sha512-yeX/Usk7daNIVwkq2uGoq2BYJKZY1JfyLTaHO/jaiSwi/lsf8fTFoQW/n6IdAsx5tx+iotu2zCJwz8MxI6D/Bw==
"@rollup/rollup-darwin-arm64@4.12.0":
version "4.12.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.12.0.tgz#6c082de71f481f57df6cfa3701ab2a7afde96f69"
integrity sha512-X64tZd8dRE/QTrBIEs63kaOBG0b5GVEd3ccoLtyf6IdXtHdh8h+I56C2yC3PtC9Ucnv0CpNFJLqKFVgCYe0lOQ==
"@rollup/rollup-darwin-arm64@4.17.2":
version "4.17.2"
resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.17.2.tgz#6b66aaf003c70454c292cd5f0236ebdc6ffbdf1a"
integrity sha512-kcMLpE6uCwls023+kknm71ug7MZOrtXo+y5p/tsg6jltpDtgQY1Eq5sGfHcQfb+lfuKwhBmEURDga9N0ol4YPw==
"@rollup/rollup-darwin-x64@4.12.0":
version "4.12.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.12.0.tgz#c34ca0d31f3c46a22c9afa0e944403eea0edcfd8"
integrity sha512-cc71KUZoVbUJmGP2cOuiZ9HSOP14AzBAThn3OU+9LcA1+IUqswJyR1cAJj3Mg55HbjZP6OLAIscbQsQLrpgTOg==
"@rollup/rollup-darwin-x64@4.17.2":
version "4.17.2"
resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.17.2.tgz#f64fc51ed12b19f883131ccbcea59fc68cbd6c0b"
integrity sha512-AtKwD0VEx0zWkL0ZjixEkp5tbNLzX+FCqGG1SvOu993HnSz4qDI6S4kGzubrEJAljpVkhRSlg5bzpV//E6ysTQ==
"@rollup/rollup-linux-arm-gnueabihf@4.12.0":
version "4.12.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.12.0.tgz#48e899c1e438629c072889b824a98787a7c2362d"
integrity sha512-a6w/Y3hyyO6GlpKL2xJ4IOh/7d+APaqLYdMf86xnczU3nurFTaVN9s9jOXQg97BE4nYm/7Ga51rjec5nfRdrvA==
"@rollup/rollup-linux-arm-gnueabihf@4.17.2":
version "4.17.2"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.17.2.tgz#1a7641111be67c10111f7122d1e375d1226cbf14"
integrity sha512-3reX2fUHqN7sffBNqmEyMQVj/CKhIHZd4y631duy0hZqI8Qoqf6lTtmAKvJFYa6bhU95B1D0WgzHkmTg33In0A==
"@rollup/rollup-linux-arm-musleabihf@4.17.2":
version "4.17.2"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.17.2.tgz#c93fd632923e0fee25aacd2ae414288d0b7455bb"
integrity sha512-uSqpsp91mheRgw96xtyAGP9FW5ChctTFEoXP0r5FAzj/3ZRv3Uxjtc7taRQSaQM/q85KEKjKsZuiZM3GyUivRg==
"@rollup/rollup-linux-arm64-gnu@4.12.0":
version "4.12.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.12.0.tgz#788c2698a119dc229062d40da6ada8a090a73a68"
integrity sha512-0fZBq27b+D7Ar5CQMofVN8sggOVhEtzFUwOwPppQt0k+VR+7UHMZZY4y+64WJ06XOhBTKXtQB/Sv0NwQMXyNAA==
"@rollup/rollup-linux-arm64-gnu@4.17.2":
version "4.17.2"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.17.2.tgz#fa531425dd21d058a630947527b4612d9d0b4a4a"
integrity sha512-EMMPHkiCRtE8Wdk3Qhtciq6BndLtstqZIroHiiGzB3C5LDJmIZcSzVtLRbwuXuUft1Cnv+9fxuDtDxz3k3EW2A==
"@rollup/rollup-linux-arm64-musl@4.12.0":
version "4.12.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.12.0.tgz#3882a4e3a564af9e55804beeb67076857b035ab7"
integrity sha512-eTvzUS3hhhlgeAv6bfigekzWZjaEX9xP9HhxB0Dvrdbkk5w/b+1Sxct2ZuDxNJKzsRStSq1EaEkVSEe7A7ipgQ==
"@rollup/rollup-linux-arm64-musl@4.17.2":
version "4.17.2"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.17.2.tgz#8acc16f095ceea5854caf7b07e73f7d1802ac5af"
integrity sha512-NMPylUUZ1i0z/xJUIx6VUhISZDRT+uTWpBcjdv0/zkp7b/bQDF+NfnfdzuTiB1G6HTodgoFa93hp0O1xl+/UbA==
"@rollup/rollup-linux-powerpc64le-gnu@4.17.2":
version "4.17.2"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.17.2.tgz#94e69a8499b5cf368911b83a44bb230782aeb571"
integrity sha512-T19My13y8uYXPw/L/k0JYaX1fJKFT/PWdXiHr8mTbXWxjVF1t+8Xl31DgBBvEKclw+1b00Chg0hxE2O7bTG7GQ==
"@rollup/rollup-linux-riscv64-gnu@4.12.0":
version "4.12.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.12.0.tgz#0c6ad792e1195c12bfae634425a3d2aa0fe93ab7"
integrity sha512-ix+qAB9qmrCRiaO71VFfY8rkiAZJL8zQRXveS27HS+pKdjwUfEhqo2+YF2oI+H/22Xsiski+qqwIBxVewLK7sw==
"@rollup/rollup-linux-riscv64-gnu@4.17.2":
version "4.17.2"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.17.2.tgz#7ef1c781c7e59e85a6ce261cc95d7f1e0b56db0f"
integrity sha512-BOaNfthf3X3fOWAB+IJ9kxTgPmMqPPH5f5k2DcCsRrBIbWnaJCgX2ll77dV1TdSy9SaXTR5iDXRL8n7AnoP5cg==
"@rollup/rollup-linux-s390x-gnu@4.17.2":
version "4.17.2"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.17.2.tgz#f15775841c3232fca9b78cd25a7a0512c694b354"
integrity sha512-W0UP/x7bnn3xN2eYMql2T/+wpASLE5SjObXILTMPUBDB/Fg/FxC+gX4nvCfPBCbNhz51C+HcqQp2qQ4u25ok6g==
"@rollup/rollup-linux-x64-gnu@4.12.0":
version "4.12.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.12.0.tgz#9d62485ea0f18d8674033b57aa14fb758f6ec6e3"
integrity sha512-TenQhZVOtw/3qKOPa7d+QgkeM6xY0LtwzR8OplmyL5LrgTWIXpTQg2Q2ycBf8jm+SFW2Wt/DTn1gf7nFp3ssVA==
"@rollup/rollup-linux-x64-gnu@4.17.2":
version "4.17.2"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.17.2.tgz#b521d271798d037ad70c9f85dd97d25f8a52e811"
integrity sha512-Hy7pLwByUOuyaFC6mAr7m+oMC+V7qyifzs/nW2OJfC8H4hbCzOX07Ov0VFk/zP3kBsELWNFi7rJtgbKYsav9QQ==
"@rollup/rollup-linux-x64-musl@4.12.0":
version "4.12.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.12.0.tgz#50e8167e28b33c977c1f813def2b2074d1435e05"
integrity sha512-LfFdRhNnW0zdMvdCb5FNuWlls2WbbSridJvxOvYWgSBOYZtgBfW9UGNJG//rwMqTX1xQE9BAodvMH9tAusKDUw==
"@rollup/rollup-linux-x64-musl@4.17.2":
version "4.17.2"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.17.2.tgz#9254019cc4baac35800991315d133cc9fd1bf385"
integrity sha512-h1+yTWeYbRdAyJ/jMiVw0l6fOOm/0D1vNLui9iPuqgRGnXA0u21gAqOyB5iHjlM9MMfNOm9RHCQ7zLIzT0x11Q==
"@rollup/rollup-win32-arm64-msvc@4.12.0":
version "4.12.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.12.0.tgz#68d233272a2004429124494121a42c4aebdc5b8e"
integrity sha512-JPDxovheWNp6d7AHCgsUlkuCKvtu3RB55iNEkaQcf0ttsDU/JZF+iQnYcQJSk/7PtT4mjjVG8N1kpwnI9SLYaw==
"@rollup/rollup-win32-arm64-msvc@4.17.2":
version "4.17.2"
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.17.2.tgz#27f65a89f6f52ee9426ec11e3571038e4671790f"
integrity sha512-tmdtXMfKAjy5+IQsVtDiCfqbynAQE/TQRpWdVataHmhMb9DCoJxp9vLcCBjEQWMiUYxO1QprH/HbY9ragCEFLA==
"@rollup/rollup-win32-ia32-msvc@4.12.0":
version "4.12.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.12.0.tgz#366ca62221d1689e3b55a03f4ae12ae9ba595d40"
integrity sha512-fjtuvMWRGJn1oZacG8IPnzIV6GF2/XG+h71FKn76OYFqySXInJtseAqdprVTDTyqPxQOG9Exak5/E9Z3+EJ8ZA==
"@rollup/rollup-win32-ia32-msvc@4.17.2":
version "4.17.2"
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.17.2.tgz#a2fbf8246ed0bb014f078ca34ae6b377a90cb411"
integrity sha512-7II/QCSTAHuE5vdZaQEwJq2ZACkBpQDOmQsE6D6XUbnBHW8IAhm4eTufL6msLJorzrHDFv3CF8oCA/hSIRuZeQ==
"@rollup/rollup-win32-x64-msvc@4.12.0":
version "4.12.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.12.0.tgz#9ffdf9ed133a7464f4ae187eb9e1294413fab235"
integrity sha512-ZYmr5mS2wd4Dew/JjT0Fqi2NPB/ZhZ2VvPp7SmvPZb4Y1CG/LRcS6tcRo2cYU7zLK5A7cdbhWnnWmUjoI4qapg==
"@rollup/rollup-win32-x64-msvc@4.17.2":
version "4.17.2"
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.17.2.tgz#5a2d08b81e8064b34242d5cc9973ef8dd1e60503"
integrity sha512-TGGO7v7qOq4CYmSBVEYpI1Y5xDuCEnbVC5Vth8mOsW0gDSzxNrVERPc790IGHsrT2dQSimgMr9Ub3Y1Jci5/8w==
"@roxi/routify@2.18.0":
version "2.18.0"
resolved "https://registry.yarnpkg.com/@roxi/routify/-/routify-2.18.0.tgz#8f88bedd936312d0dbe44cbc11ab179b1f938ec2"
@ -6291,6 +6449,11 @@
js-yaml "^3.10.0"
tslib "^2.4.0"
"@yr/monotone-cubic-spline@^1.0.3":
version "1.0.3"
resolved "https://registry.yarnpkg.com/@yr/monotone-cubic-spline/-/monotone-cubic-spline-1.0.3.tgz#7272d89f8e4f6fb7a1600c28c378cc18d3b577b9"
integrity sha512-FQXkOta0XBSUPHndIKON2Y9JeQz5ZeMqLYZVVK93FliNBFm7LNMIZmY6FrMEB9XPcDbE2bekMbZD6kzDkxwYjA==
"@zerodevx/svelte-json-view@^1.0.7":
version "1.0.7"
resolved "https://registry.yarnpkg.com/@zerodevx/svelte-json-view/-/svelte-json-view-1.0.7.tgz#abf3efa71dedcb3e9d16bc9cc61d5ea98c8d00b1"
@ -6589,11 +6752,12 @@ anymatch@^3.0.3, anymatch@~3.1.2:
normalize-path "^3.0.0"
picomatch "^2.0.4"
apexcharts@^3.19.2, apexcharts@^3.22.1:
version "3.37.1"
resolved "https://registry.yarnpkg.com/apexcharts/-/apexcharts-3.37.1.tgz#50443d302fc7fc72aace9c6c4074baae017c6950"
integrity sha512-fmQ5Updeb/LASl+S1+mIxXUFxzY0Fa7gexfCs4o+OPP9f2NEBNjvybOtPrah44N4roK7U5o5Jis906QeEQu0cA==
apexcharts@^3.48.0:
version "3.49.1"
resolved "https://registry.yarnpkg.com/apexcharts/-/apexcharts-3.49.1.tgz#837d1d4fd80f2c092f0587e8fb6bbc31ad57a0f3"
integrity sha512-MqGtlq/KQuO8j0BBsUJYlRG8VBctKwYdwuBtajHgHTmSgUU3Oai+8oYN/rKCXwXzrUlYA+GiMgotAIbXY2BCGw==
dependencies:
"@yr/monotone-cubic-spline" "^1.0.3"
svg.draggable.js "^2.2.2"
svg.easing.js "^2.0.0"
svg.filter.js "^2.0.2"
@ -6945,6 +7109,18 @@ atomic-sleep@^1.0.0:
resolved "https://registry.yarnpkg.com/atomic-sleep/-/atomic-sleep-1.0.0.tgz#eb85b77a601fc932cfe432c5acd364a9e2c9075b"
integrity sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==
atrament@^4.3.0:
version "4.4.0"
resolved "https://registry.yarnpkg.com/atrament/-/atrament-4.4.0.tgz#01898c90d08941b0d64494f13a52f79e0c0ce5d1"
integrity sha512-kUlJvSrr8QF4eSQzkkBE77FsGUWXIKf1Zl0qv7BWki8B5vPYMVKDn+a8ayPqTX9IvJx6466uLnYFIksX91kFzw==
dependencies:
"@rollup/plugin-terser" "^0.4.4"
eslint "^8.56.0"
eslint-config-airbnb-base "^15.0.0"
eslint-plugin-import "^2.26.0"
rollup "^4.9.4"
rollup-plugin-web-worker-loader "^1.6.1"
available-typed-arrays@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7"
@ -8369,6 +8545,11 @@ configstore@^5.0.1:
write-file-atomic "^3.0.0"
xdg-basedir "^4.0.0"
confusing-browser-globals@^1.0.10:
version "1.0.11"
resolved "https://registry.yarnpkg.com/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz#ae40e9b57cdd3915408a2805ebd3a5585608dc81"
integrity sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==
console-control-strings@^1.0.0, console-control-strings@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e"
@ -10025,6 +10206,13 @@ es-get-iterator@^1.1.2:
isarray "^2.0.5"
stop-iteration-iterator "^1.0.0"
es-object-atoms@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.0.0.tgz#ddb55cd47ac2e240701260bc2a8e31ecb643d941"
integrity sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==
dependencies:
es-errors "^1.3.0"
es-set-tostringtag@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz#338d502f6f674301d710b80c8592de8a15f09cd8"
@ -10164,6 +10352,16 @@ eslint-compat-utils@^0.1.2:
resolved "https://registry.yarnpkg.com/eslint-compat-utils/-/eslint-compat-utils-0.1.2.tgz#f45e3b5ced4c746c127cf724fb074cd4e730d653"
integrity sha512-Jia4JDldWnFNIru1Ehx1H5s9/yxiRHY/TimCuUc0jNexew3cF1gI6CYZil1ociakfWO3rRqFjl1mskBblB3RYg==
eslint-config-airbnb-base@^15.0.0:
version "15.0.0"
resolved "https://registry.yarnpkg.com/eslint-config-airbnb-base/-/eslint-config-airbnb-base-15.0.0.tgz#6b09add90ac79c2f8d723a2580e07f3925afd236"
integrity sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig==
dependencies:
confusing-browser-globals "^1.0.10"
object.assign "^4.1.2"
object.entries "^1.1.5"
semver "^6.3.0"
eslint-import-resolver-node@^0.3.9:
version "0.3.9"
resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz#d4eaac52b8a2e7c3cd1903eb00f7e053356118ac"
@ -10180,6 +10378,29 @@ eslint-module-utils@^2.8.0:
dependencies:
debug "^3.2.7"
eslint-plugin-import@^2.26.0:
version "2.29.1"
resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz#d45b37b5ef5901d639c15270d74d46d161150643"
integrity sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==
dependencies:
array-includes "^3.1.7"
array.prototype.findlastindex "^1.2.3"
array.prototype.flat "^1.3.2"
array.prototype.flatmap "^1.3.2"
debug "^3.2.7"
doctrine "^2.1.0"
eslint-import-resolver-node "^0.3.9"
eslint-module-utils "^2.8.0"
hasown "^2.0.0"
is-core-module "^2.13.1"
is-glob "^4.0.3"
minimatch "^3.1.2"
object.fromentries "^2.0.7"
object.groupby "^1.0.1"
object.values "^1.1.7"
semver "^6.3.1"
tsconfig-paths "^3.15.0"
eslint-plugin-import@^2.29.0:
version "2.29.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.29.0.tgz#8133232e4329ee344f2f612885ac3073b0b7e155"
@ -10303,6 +10524,50 @@ eslint@^8.52.0:
strip-ansi "^6.0.1"
text-table "^0.2.0"
eslint@^8.56.0:
version "8.57.0"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.57.0.tgz#c786a6fd0e0b68941aaf624596fb987089195668"
integrity sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==
dependencies:
"@eslint-community/eslint-utils" "^4.2.0"
"@eslint-community/regexpp" "^4.6.1"
"@eslint/eslintrc" "^2.1.4"
"@eslint/js" "8.57.0"
"@humanwhocodes/config-array" "^0.11.14"
"@humanwhocodes/module-importer" "^1.0.1"
"@nodelib/fs.walk" "^1.2.8"
"@ungap/structured-clone" "^1.2.0"
ajv "^6.12.4"
chalk "^4.0.0"
cross-spawn "^7.0.2"
debug "^4.3.2"
doctrine "^3.0.0"
escape-string-regexp "^4.0.0"
eslint-scope "^7.2.2"
eslint-visitor-keys "^3.4.3"
espree "^9.6.1"
esquery "^1.4.2"
esutils "^2.0.2"
fast-deep-equal "^3.1.3"
file-entry-cache "^6.0.1"
find-up "^5.0.0"
glob-parent "^6.0.2"
globals "^13.19.0"
graphemer "^1.4.0"
ignore "^5.2.0"
imurmurhash "^0.1.4"
is-glob "^4.0.0"
is-path-inside "^3.0.3"
js-yaml "^4.1.0"
json-stable-stringify-without-jsonify "^1.0.1"
levn "^0.4.1"
lodash.merge "^4.6.2"
minimatch "^3.1.2"
natural-compare "^1.4.0"
optionator "^0.9.3"
strip-ansi "^6.0.1"
text-table "^0.2.0"
esm@^3.2.25:
version "3.2.25"
resolved "https://registry.yarnpkg.com/esm/-/esm-3.2.25.tgz#342c18c29d56157688ba5ce31f8431fbb795cc10"
@ -16072,6 +16337,16 @@ object-sizeof@2.6.1:
dependencies:
buffer "^6.0.3"
object.assign@^4.1.2:
version "4.1.5"
resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.5.tgz#3a833f9ab7fdb80fc9e8d2300c803d216d8fdbb0"
integrity sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==
dependencies:
call-bind "^1.0.5"
define-properties "^1.2.1"
has-symbols "^1.0.3"
object-keys "^1.1.1"
object.assign@^4.1.4:
version "4.1.4"
resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.4.tgz#9673c7c7c351ab8c4d0b516f4343ebf4dfb7799f"
@ -16082,6 +16357,15 @@ object.assign@^4.1.4:
has-symbols "^1.0.3"
object-keys "^1.1.1"
object.entries@^1.1.5:
version "1.1.8"
resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.8.tgz#bffe6f282e01f4d17807204a24f8edd823599c41"
integrity sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ==
dependencies:
call-bind "^1.0.7"
define-properties "^1.2.1"
es-object-atoms "^1.0.0"
object.fromentries@^2.0.7:
version "2.0.7"
resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.7.tgz#71e95f441e9a0ea6baf682ecaaf37fa2a8d7e616"
@ -18824,6 +19108,11 @@ rollup-plugin-visualizer@^5.12.0:
source-map "^0.7.4"
yargs "^17.5.1"
rollup-plugin-web-worker-loader@^1.6.1:
version "1.6.1"
resolved "https://registry.yarnpkg.com/rollup-plugin-web-worker-loader/-/rollup-plugin-web-worker-loader-1.6.1.tgz#9d7a27575b64b0780fe4e8b3bc87470d217e485f"
integrity sha512-4QywQSz1NXFHKdyiou16mH3ijpcfLtLGOrAqvAqu1Gx+P8+zj+3gwC2BSL/VW1d+LW4nIHC8F7d7OXhs9UdR2A==
rollup-pluginutils@^1.3.1:
version "1.5.2"
resolved "https://registry.yarnpkg.com/rollup-pluginutils/-/rollup-pluginutils-1.5.2.tgz#1e156e778f94b7255bfa1b3d0178be8f5c552408"
@ -18853,6 +19142,31 @@ rollup@^3.27.1:
optionalDependencies:
fsevents "~2.3.2"
rollup@^4.9.4:
version "4.17.2"
resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.17.2.tgz#26d1785d0144122277fdb20ab3a24729ae68301f"
integrity sha512-/9ClTJPByC0U4zNLowV1tMBe8yMEAxewtR3cUNX5BoEpGH3dQEWpJLr6CLp0fPdYRF/fzVOgvDb1zXuakwF5kQ==
dependencies:
"@types/estree" "1.0.5"
optionalDependencies:
"@rollup/rollup-android-arm-eabi" "4.17.2"
"@rollup/rollup-android-arm64" "4.17.2"
"@rollup/rollup-darwin-arm64" "4.17.2"
"@rollup/rollup-darwin-x64" "4.17.2"
"@rollup/rollup-linux-arm-gnueabihf" "4.17.2"
"@rollup/rollup-linux-arm-musleabihf" "4.17.2"
"@rollup/rollup-linux-arm64-gnu" "4.17.2"
"@rollup/rollup-linux-arm64-musl" "4.17.2"
"@rollup/rollup-linux-powerpc64le-gnu" "4.17.2"
"@rollup/rollup-linux-riscv64-gnu" "4.17.2"
"@rollup/rollup-linux-s390x-gnu" "4.17.2"
"@rollup/rollup-linux-x64-gnu" "4.17.2"
"@rollup/rollup-linux-x64-musl" "4.17.2"
"@rollup/rollup-win32-arm64-msvc" "4.17.2"
"@rollup/rollup-win32-ia32-msvc" "4.17.2"
"@rollup/rollup-win32-x64-msvc" "4.17.2"
fsevents "~2.3.2"
rollup@^4.9.6:
version "4.12.0"
resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.12.0.tgz#0b6d1e5f3d46bbcf244deec41a7421dc54cc45b5"
@ -19082,6 +19396,13 @@ serialize-javascript@^4.0.0:
dependencies:
randombytes "^2.1.0"
serialize-javascript@^6.0.1:
version "6.0.2"
resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.2.tgz#defa1e055c83bf6d59ea805d8da862254eb6a6c2"
integrity sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==
dependencies:
randombytes "^2.1.0"
server-destroy@1.0.1, server-destroy@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/server-destroy/-/server-destroy-1.0.1.tgz#f13bf928e42b9c3e79383e61cc3998b5d14e6cdd"
@ -19280,6 +19601,11 @@ smart-buffer@^4.2.0:
resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae"
integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==
smob@^1.0.0:
version "1.5.0"
resolved "https://registry.yarnpkg.com/smob/-/smob-1.5.0.tgz#85d79a1403abf128d24d3ebc1cdc5e1a9548d3ab"
integrity sha512-g6T+p7QO8npa+/hNx9ohv1E5pVCmWrVCUzUXJyLdMmftX6ER0oiWY/w9knEonLpnOp6b6FenKnMfR8gqwWdwig==
snowflake-promise@^4.5.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/snowflake-promise/-/snowflake-promise-4.5.0.tgz#ceba611d27b3792966bc752c545760e0ce168c1c"
@ -20039,13 +20365,6 @@ supports-preserve-symlinks-flag@^1.0.0:
resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
svelte-apexcharts@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/svelte-apexcharts/-/svelte-apexcharts-1.0.2.tgz#4e000f8b8f7c901c05658c845457dfc8314d54c1"
integrity sha512-6qlx4rE+XsonZ0FZudfwqOQ34Pq+3wpxgAD75zgEmGoYhYBJcwmikTuTf3o8ZBsZue9U/pAwhNy3ed1Bkq1gmA==
dependencies:
apexcharts "^3.19.2"
svelte-dnd-action@^0.9.8:
version "0.9.22"
resolved "https://registry.yarnpkg.com/svelte-dnd-action/-/svelte-dnd-action-0.9.22.tgz#003eee9dddb31d8c782f6832aec8b1507fff194d"
@ -20355,6 +20674,16 @@ terser@^5.0.0:
commander "^2.20.0"
source-map-support "~0.5.20"
terser@^5.17.4:
version "5.31.0"
resolved "https://registry.yarnpkg.com/terser/-/terser-5.31.0.tgz#06eef86f17007dbad4593f11a574c7f5eb02c6a1"
integrity sha512-Q1JFAoUKE5IMfI4Z/lkE/E6+SwgzO+x4tq4v1AyBLRj8VSYvRO6A/rQrPg1yud4g0En9EKI1TvFRF2tQFcoUkg==
dependencies:
"@jridgewell/source-map" "^0.3.3"
acorn "^8.8.2"
commander "^2.20.0"
source-map-support "~0.5.20"
test-exclude@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e"
@ -20703,6 +21032,16 @@ tsconfig-paths@^3.10.1, tsconfig-paths@^3.14.2:
minimist "^1.2.6"
strip-bom "^3.0.0"
tsconfig-paths@^3.15.0:
version "3.15.0"
resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz#5299ec605e55b1abb23ec939ef15edaf483070d4"
integrity sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==
dependencies:
"@types/json5" "^0.0.29"
json5 "^1.0.2"
minimist "^1.2.6"
strip-bom "^3.0.0"
tsconfig-paths@^4.1.2, tsconfig-paths@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz#ef78e19039133446d244beac0fd6a1632e2d107c"