Merge branch 'master' into budi-8195/unable-to-load-grid-when-using-external-postgres
This commit is contained in:
commit
7ebdb0fbcc
76 changed files with 3532 additions and 707 deletions
|
@ -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}
|
||||
/>
|
||||
|
|
|
@ -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)}
|
||||
|
|
|
@ -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)}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
/>
|
||||
|
|
|
@ -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>
|
||||
|
|
79
packages/bbui/src/Tooltip/Context.svelte
Normal file
79
packages/bbui/src/Tooltip/Context.svelte
Normal 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>
|
|
@ -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"
|
||||
|
|
|
@ -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}`
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -0,0 +1,12 @@
|
|||
<li>
|
||||
<div class="exampleLine">
|
||||
<slot />
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<style>
|
||||
.exampleLine {
|
||||
display: flex;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
</style>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -0,0 +1,11 @@
|
|||
<div class="section">
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.section {
|
||||
line-height: 20px;
|
||||
margin-bottom: 13px;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
|
@ -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>
|
|
@ -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"
|
|
@ -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"
|
|
@ -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>
|
|
@ -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
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
export { default as Explanation } from "./Explanation.svelte"
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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"
|
|
@ -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
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -0,0 +1,9 @@
|
|||
<span class="space">{" "}</span>
|
||||
|
||||
<style>
|
||||
.space {
|
||||
white-space: pre;
|
||||
width: 3px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
</style>
|
|
@ -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>
|
|
@ -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"
|
|
@ -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}
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -191,6 +191,9 @@
|
|||
// Number fields
|
||||
min: setting.min ?? null,
|
||||
max: setting.max ?? null,
|
||||
|
||||
// Field select settings
|
||||
explanation: setting.explanation,
|
||||
}}
|
||||
{bindings}
|
||||
{componentBindings}
|
||||
|
|
|
@ -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 => ({
|
||||
|
|
|
@ -108,6 +108,7 @@ export class ComponentStore extends BudiStore {
|
|||
|
||||
// Sync client features to app store
|
||||
appStore.syncClientFeatures(components.features)
|
||||
appStore.syncClientTypeSupportPresets(components?.typeSupportPresets ?? {})
|
||||
|
||||
return components
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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
|
||||
},
|
||||
{
|
||||
|
|
|
@ -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"
|
||||
},
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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, "")
|
||||
)
|
||||
}
|
||||
}
|
|
@ -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} />
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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} />
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
51
packages/client/src/components/app/charts/utils.js
Normal file
51
packages/client/src/components/app/charts/utils.js
Normal 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}"`
|
||||
}
|
31
packages/client/src/components/app/charts/utils.test.js
Normal file
31
packages/client/src/components/app/charts/utils.test.js
Normal 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)
|
||||
})
|
||||
})
|
||||
})
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
361
yarn.lock
|
@ -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"
|
||||
|
|
Loading…
Reference in a new issue