This commit is contained in:
Gerard Burns 2024-04-22 08:50:33 +01:00
parent 9d4283e9ba
commit 53553d8dd6
45 changed files with 685 additions and 594 deletions

View File

@ -202,8 +202,8 @@
aria-selected="true"
tabindex="0"
on:click={() => onSelectOption(getOptionValue(option, idx))}
on:mouseenter={(e) => onOptionMouseenter(e, option)}
on:mouseleave={(e) => onOptionMouseleave(e, option)}
on:mouseenter={e => onOptionMouseenter(e, option)}
on:mouseleave={e => onOptionMouseleave(e, option)}
class:is-disabled={!isOptionEnabled(option)}
>
{#if getOptionIcon(option, idx)}

View File

@ -5,7 +5,7 @@
export let anchor
export let visible = false
export let offset = 0;
export let offset = 0
$: target = getContext(Context.PopoverRoot) || "#app"
@ -16,12 +16,13 @@
const updatePosition = (anchor, tooltip) => {
if (anchor == null || tooltip == null) {
return;
return
}
requestAnimationFrame(() => {
const rect = anchor.getBoundingClientRect();
const windowOffset = (window.innerHeight - offset) - (tooltip.clientHeight + rect.y)
const rect = anchor.getBoundingClientRect()
const windowOffset =
window.innerHeight - offset - (tooltip.clientHeight + rect.y)
const tooltipWidth = tooltip.clientWidth
x = rect.x - tooltipWidth - offset
@ -32,11 +33,11 @@
$: updatePosition(anchor, tooltip)
const handleMouseenter = () => {
hovering = true;
hovering = true
}
const handleMouseleave = () => {
hovering = false;
hovering = false
}
</script>
@ -50,10 +51,7 @@
class="wrapper"
class:visible={visible || hovering}
>
<div
bind:this={tooltip}
class="tooltip"
>
<div bind:this={tooltip} class="tooltip">
<slot />
</div>
</div>
@ -62,7 +60,7 @@
<style>
.wrapper {
background-color: var(--background-alt);
box-shadow: 2px 2px 5px 0px rgba(0,0,0,0.42);
box-shadow: 2px 2px 5px 0px rgba(0, 0, 0, 0.42);
opacity: 0;
overflow: hidden;

View File

@ -1,7 +1,16 @@
<script>
import { ContextTooltip } from "@budibase/bbui"
import { StringsAsDates, NumbersAsDates, ScalarJsonOnly, Column, Support, NotRequired, StringsAsNumbers, DatesAsNumbers } from './subjects'
import subjects from '../subjects'
import {
StringsAsDates,
NumbersAsDates,
ScalarJsonOnly,
Column,
Support,
NotRequired,
StringsAsNumbers,
DatesAsNumbers,
} from "./subjects"
import subjects from "../subjects"
export let anchor
export let schema
@ -9,12 +18,7 @@
export let subject = subjects.none
</script>
<ContextTooltip
visible={subject !== subjects.none}
{anchor}
offset={20}
>
<ContextTooltip visible={subject !== subjects.none} {anchor} offset={20}>
<div class="explanationModalContent">
{#if subject === subjects.column}
<Column {columnName} {schema} />

View File

@ -1,118 +1,122 @@
<script>
import { tables } from "stores/builder"
import { BindingValue, Block, Subject, JSONValue, Property, Section } from './components'
import {
BindingValue,
Block,
Subject,
JSONValue,
Property,
Section,
} from "./components"
export let schema
export let columnName
const parseDate = (isoString) => {
const parseDate = isoString => {
if ([null, undefined, ""].includes(isoString)) {
return "None"
}
const unixTime = Date.parse(isoString);
const date = new Date(unixTime);
const unixTime = Date.parse(isoString)
const date = new Date(unixTime)
return date.toLocaleString();
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}
{#if schema.type === "string"}
<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}
name="Max Length"
value={schema?.constraints?.length?.maximum ?? "None"}
/>
{:else if schema.type === "datetime"}
<Property
name={`Option ${index + 1}`}
truncate
>
<span style:background-color={schema?.optionColors?.[option]} class="optionCircle" />{option}
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>
{/each}
{:else if schema.type === "json"}
<Property name="Schema">
<JSONValue
value={JSON.stringify(schema?.schema ?? {}, null, 2)}
{:else if schema.type === "formula"}
<Property name="Formula">
<BindingValue value={schema?.formula} />
</Property>
<Property
name="Formula type"
value={schema?.formulaType === "dynamic" ? "Dynamic" : "Static"}
/>
</Property>
{:else if schema.type === "formula"}
<Property name="Formula">
<BindingValue
value={schema?.formula}
{: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>
<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="Formula type"
value={schema?.formulaType === "dynamic" ? "Dynamic" : "Static"}
name="Required"
value={schema?.constraints?.presence?.allowEmpty === false ? "Yes" : "No"}
/>
{: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>
</Section>
</Subject>
<style>

View File

@ -1,51 +1,59 @@
<script>
import { onMount } from "svelte"
import { ExampleSection, ExampleLine, Block, Subject, Section } from './components'
import {
ExampleSection,
ExampleLine,
Block,
Subject,
Section,
} from "./components"
let timestamp = Date.now();
let timestamp = Date.now()
onMount(() => {
let run = true;
let run = true
const updateTimeStamp = () => {
timestamp = Date.now();
timestamp = Date.now()
if (run) {
setTimeout(updateTimeStamp, 200)
}
}
updateTimeStamp();
updateTimeStamp()
return () => {
run = false;
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.
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>
<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>

View File

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

View File

@ -1,55 +1,61 @@
<script>
import { onMount } from "svelte"
import { ExampleSection, ExampleLine, Block, Subject, Section } from './components'
import {
ExampleSection,
ExampleLine,
Block,
Subject,
Section,
} from "./components"
let timestamp = Date.now();
let timestamp = Date.now()
onMount(() => {
let run = true;
let run = true
const updateTimeStamp = () => {
timestamp = Date.now();
timestamp = Date.now()
if (run) {
setTimeout(updateTimeStamp, 200)
}
}
updateTimeStamp();
updateTimeStamp()
return () => {
run = false;
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.
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>
<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>
<Block>Now</Block>
</ExampleLine>
</ExampleSection>
</Subject>
<style>

View File

@ -1,31 +1,40 @@
<script>
import { ExampleSection, ExampleLine, Block, Subject, Section } from './components'
import {
ExampleSection,
ExampleLine,
Block,
Subject,
Section,
} from "./components"
export let schema
export let columnName
const maxScalarDescendantsToFind = 3;
const maxScalarDescendantsToFind = 3
const getScalarDescendants = (schema) => {
const newScalarDescendants = [];
const getScalarDescendants = schema => {
const newScalarDescendants = []
const getScalarDescendantFromSchema = (path, schema) => {
if (newScalarDescendants.length >= maxScalarDescendantsToFind) {
return;
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([...path, childName], childSchema)
)
}
}
Object.entries(schema?.schema ?? {}).forEach(([childName, childSchema]) =>
getScalarDescendantFromSchema([columnName, childName], childSchema))
Object.entries(schema?.schema ?? {}).forEach(([childName, childSchema]) =>
getScalarDescendantFromSchema([columnName, childName], childSchema)
)
return newScalarDescendants;
return newScalarDescendants
}
$: scalarDescendants = getScalarDescendants(schema)
@ -33,16 +42,21 @@
<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>.
<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:"
>
<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>
<Block truncate>{descendant.name}</Block><span class="separator"
>-</span
><Block truncate noShrink>{descendant.type}</Block>
</ExampleLine>
{/each}
</ExampleSection>

View File

@ -1,88 +1,92 @@
<script>
import { onMount } from "svelte"
import { ExampleSection, ExampleLine, Block, Subject, Section } from './components'
import {
ExampleSection,
ExampleLine,
Block,
Subject,
Section,
} from "./components"
let timestamp = Date.now();
$: iso = (new Date(timestamp)).toISOString();
let timestamp = Date.now()
$: iso = new Date(timestamp).toISOString()
onMount(() => {
let run = true;
let run = true
const updateTimeStamp = () => {
timestamp = Date.now();
timestamp = Date.now()
if (run) {
setTimeout(updateTimeStamp, 200)
}
}
updateTimeStamp();
updateTimeStamp()
return () => {
run = false;
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:
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.
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>
<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>
<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>
</div>
</Subject>
<style>
@ -90,7 +94,6 @@
margin: 0 5px;
}
.isoExamples :global(.block) {
word-break: break-all;
}

View File

@ -1,32 +1,49 @@
<script>
import { ExampleSection, ExampleLine, Block, Subject, Section } from './components'
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.
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:"
>
<ExampleSection heading="Examples:">
<ExampleLine>
<Block>"100"</Block><span class="separator">{"->"}</span><Block>100</Block>
<Block>"100"</Block><span class="separator">{"->"}</span><Block>100</Block
>
</ExampleLine>
<ExampleLine>
<Block>"100k"</Block><span class="separator">{"->"}</span><Block>100</Block>
<Block>"100k"</Block><span class="separator">{"->"}</span><Block
>100</Block
>
</ExampleLine>
<ExampleLine>
<Block>"100,000"</Block><span class="separator">{"->"}</span><Block>100</Block>
<Block>"100,000"</Block><span class="separator">{"->"}</span><Block
>100</Block
>
</ExampleLine>
<ExampleLine>
<Block>"100 million"</Block><span class="separator">{"->"}</span><Block>100</Block>
<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>
<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>
<Block>"One hundred"</Block><span class="separator">{"->"}</span><Block
>Error</Block
>
</ExampleLine>
</ExampleSection>
</Subject>

View File

@ -1,16 +1,14 @@
<script>
import { InfoWord } from '../../typography'
import { Subject, Section } from './components'
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>
<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
@ -18,14 +16,13 @@
color="var(--yellow)"
text="Partially compatible"
/>
<span class="body">Partially compatible with the input, but beware of other caveats mentioned.</span>
<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"
/>
<InfoWord icon="Alert" color="var(--red)" text="Not compatible" />
<span class="body">Imcompatible with the component.</span>
</Section>
</Subject>

View File

@ -1,14 +1,10 @@
<script>
import {
decodeJSBinding,
} from "@budibase/string-templates"
import { decodeJSBinding } from "@budibase/string-templates"
import CodeEditor from "components/common/CodeEditor/CodeEditor.svelte"
import {
EditorModes,
} from "components/common/CodeEditor"
import { EditorModes } from "components/common/CodeEditor"
import {
runtimeToReadableBinding,
getDatasourceForProvider
getDatasourceForProvider,
} from "dataBinding"
import { tables, selectedScreen, selectedComponent } from "stores/builder"
import { getBindings } from "components/backend/DataTable/formula"
@ -17,7 +13,7 @@
$: datasource = getDatasourceForProvider($selectedScreen, $selectedComponent)
$: tableId = datasource.tableId
$: table = $tables?.list?.find(table => table._id === tableId)
$: bindings = getBindings({ table });
$: bindings = getBindings({ table })
$: readableBinding = runtimeToReadableBinding(bindings, value)
@ -25,13 +21,13 @@
</script>
<div class="editor">
<CodeEditor
readonly
readonlyLineNumbers
value={isJs ? decodeJSBinding(readableBinding) : readableBinding}
jsBindingWrapping={isJs}
mode={isJs ? EditorModes.JS :EditorModes.Handlebars}
/>
<CodeEditor
readonly
readonlyLineNumbers
value={isJs ? decodeJSBinding(readableBinding) : readableBinding}
jsBindingWrapping={isJs}
mode={isJs ? EditorModes.JS : EditorModes.Handlebars}
/>
</div>
<style>

View File

@ -1,7 +1,7 @@
<li>
<div class="exampleLine">
<slot />
</div>
<div class="exampleLine">
<slot />
</div>
</li>
<style>

View File

@ -1,5 +1,5 @@
<script>
export let value;
export let value
</script>
<pre class="pre">

View File

@ -1,7 +1,7 @@
<script>
export let name;
export let value;
export let truncate = false;
export let name
export let value
export let truncate = false
</script>
<div class:truncate class="property">
@ -35,7 +35,6 @@
flex-shrink: 0;
}
.propertyDivider {
padding: 0 4px;
flex-shrink: 0;

View File

@ -2,10 +2,10 @@
import { onMount } from "svelte"
export let heading = ""
let body;
let body
const handleScroll = (e) => {
if (!body) return;
const handleScroll = e => {
if (!body) return
body.scrollTo({ top: body.scrollTop + e.deltaY, behavior: "smooth" })
}
@ -16,7 +16,7 @@
return () => {
window.removeEventListener("wheel", handleScroll)
}
});
})
</script>
<div class="heading">

View File

@ -1,11 +1,22 @@
<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 DetailsModal from "./DetailsModal/index.svelte"
import {
componentStore,
} from "stores/builder"
messages as messageConstants,
getExplanationMessagesAndSupport,
getExplanationWithPresets,
} from "./explanation"
import {
StringAsDate,
NumberAsDate,
Column,
Support,
NotRequired,
StringAsNumber,
JSONPrimitivesOnly,
DateAsNumber,
} from "./lines"
import subjects from "./subjects"
import { componentStore } from "stores/builder"
export let explanation
export let columnIcon
@ -16,30 +27,33 @@
export let schema
$: explanationWithPresets = getExplanationWithPresets(explanation, $componentStore.typeSupportPresets)
$: explanationWithPresets = getExplanationWithPresets(
explanation,
$componentStore.typeSupportPresets
)
let support
let messages = []
$: {
const explanationMessagesAndSupport = getExplanationMessagesAndSupport(schema, explanationWithPresets)
const explanationMessagesAndSupport = getExplanationMessagesAndSupport(
schema,
explanationWithPresets
)
support = explanationMessagesAndSupport.support
messages = explanationMessagesAndSupport.messages
}
let root = null;
let root = null
let detailsModalSubject = subjects.none
const setExplanationSubject = (option) => {
detailsModalSubject = option;
const setExplanationSubject = option => {
detailsModalSubject = option
root = root
}
</script>
<div
bind:this={root}
class="tooltipContents"
>
<div bind:this={root} class="tooltipContents">
<Column
{columnName}
{columnIcon}
@ -47,39 +61,24 @@
{tableHref}
{setExplanationSubject}
/>
<Support
{support}
{setExplanationSubject}
/>
<Support {support} {setExplanationSubject} />
{#if messages.includes(messageConstants.stringAsNumber)}
<StringAsNumber
{setExplanationSubject}
/>
<StringAsNumber {setExplanationSubject} />
{/if}
{#if messages.includes(messageConstants.notRequired)}
<NotRequired
{setExplanationSubject}
/>
<NotRequired {setExplanationSubject} />
{/if}
{#if messages.includes(messageConstants.jsonPrimitivesOnly)}
<JSONPrimitivesOnly
{setExplanationSubject}
/>
<JSONPrimitivesOnly {setExplanationSubject} />
{/if}
{#if messages.includes(messageConstants.dateAsNumber)}
<DateAsNumber
{setExplanationSubject}
/>
<DateAsNumber {setExplanationSubject} />
{/if}
{#if messages.includes(messageConstants.numberAsDate)}
<NumberAsDate
{setExplanationSubject}
/>
<NumberAsDate {setExplanationSubject} />
{/if}
{#if messages.includes(messageConstants.stringAsDate)}
<StringAsDate
{setExplanationSubject}
/>
<StringAsDate {setExplanationSubject} />
{/if}
</div>

View File

@ -11,21 +11,28 @@ export const messages = {
export const support = {
unsupported: Symbol("explanation-unsupported"),
partialSupport: Symbol("explanation-partialSupport"),
supported: Symbol("explanation-supported")
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?.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;
if (
explanation?.typeSupport?.partialSupport?.find(
mapping => mapping === type || mapping?.type === type
)
) {
return support.partialSupport
}
return support.unsupported
@ -36,17 +43,23 @@ const getSupportMessage = (type, explanation) => {
return null
}
const supported = explanation?.typeSupport?.supported?.find(mapping => mapping?.type === type)
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)
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)
const unsupported = explanation?.typeSupport?.unsupported?.find(
mapping => mapping?.type === type
)
if (unsupported) {
return messages[unsupported?.message]
}
@ -63,14 +76,14 @@ export const getExplanationMessagesAndSupport = (fieldSchema, explanation) => {
const isRequired = fieldSchema?.constraints?.presence?.allowEmpty === false
if (!isRequired) {
explanationMessagesAndSupport.messages.push(messages.notRequired);
explanationMessagesAndSupport.messages.push(messages.notRequired)
}
return explanationMessagesAndSupport;
return explanationMessagesAndSupport
} catch (e) {
return {
support: support.partialSupport,
messages: [messages.contextError]
messages: [messages.contextError],
}
}
}
@ -79,7 +92,7 @@ export const getExplanationWithPresets = (explanation, presets) => {
if (explanation?.typeSupport?.preset) {
return {
...explanation,
typeSupport: presets[explanation?.typeSupport?.preset]
typeSupport: presets[explanation?.typeSupport?.preset],
}
}

View File

@ -1,6 +1,12 @@
<script>
import { Line, InfoWord, DocumentationLink, Text, Period } from "../typography"
import subjects from '../subjects'
import {
Line,
InfoWord,
DocumentationLink,
Text,
Period,
} from "../typography"
import subjects from "../subjects"
export let columnName
export let columnIcon
@ -8,7 +14,7 @@
export let tableHref
export let setExplanationSubject
const getDocLink = (columnType) => {
const getDocLink = columnType => {
if (columnType === "Number") {
return "https://docs.budibase.com/docs/number"
}
@ -42,9 +48,7 @@
href={tableHref}
text={columnName}
/>
<Text
value=" is a "
/>
<Text value=" is a " />
<DocumentationLink
href={getDocLink(columnType)}
icon={columnIcon}

View File

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

View File

@ -1,16 +1,16 @@
<script>
import { Line, InfoWord, Text, Period } from "../typography"
import subjects from '../subjects'
import subjects from "../subjects"
export let setExplanationSubject
</script>
<Line>
<InfoWord
on:mouseenter={() => setExplanationSubject(subjects.scalarJsonOnly)}
on:mouseleave={() => setExplanationSubject(subjects.none)}
>Scalar JSON values</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"
/>

View File

@ -1,16 +1,15 @@
<script>
import { Line, InfoWord, DocumentationLink, Space, Text } from "../typography"
import subjects from '../subjects'
import subjects from "../subjects"
export let setExplanationSubject
</script>
<Line>
<Text value="No " />
<InfoWord
on:mouseenter={() => setExplanationSubject(subjects.notRequired)}
on:mouseleave={() => setExplanationSubject(subjects.none)}
on:mouseenter={() => setExplanationSubject(subjects.notRequired)}
on:mouseleave={() => setExplanationSubject(subjects.none)}
text="required"
/>
<Space />

View File

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

View File

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

View File

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

View File

@ -1,12 +1,12 @@
<script>
import { Line, InfoWord, DocumentationLink, Text } from "../typography"
import subjects from '../subjects'
import * as explanation from '../explanation'
import subjects from "../subjects"
import * as explanation from "../explanation"
export let setExplanationSubject
export let support
const getIcon = (support) => {
const getIcon = support => {
if (support === explanation.support.unsupported) {
return "Alert"
} else if (support === explanation.support.supported) {
@ -16,7 +16,7 @@
return "AlertCheck"
}
const getColor = (support) => {
const getColor = support => {
if (support === explanation.support.unsupported) {
return "var(--red)"
} else if (support === explanation.support.supported) {
@ -26,7 +26,7 @@
return "var(--yellow)"
}
const getText = (support) => {
const getText = support => {
if (support === explanation.support.unsupported) {
return "Not compatible"
} else if (support === explanation.support.supported) {
@ -36,9 +36,9 @@
return "Partially compatible"
}
$: icon = getIcon(support);
$: color = getColor(support);
$: text = getText(support);
$: icon = getIcon(support)
$: color = getColor(support)
$: text = getText(support)
</script>
<Line>

View File

@ -7,7 +7,7 @@ const subjects = {
stringsAsDates: Symbol("explanation-strings-as-dates"),
notRequired: Symbol("details-modal-not-required"),
scalarJsonOnly: Symbol("explanation-scalar-json-only"),
none: Symbol("details-modal-none")
none: Symbol("details-modal-none"),
}
export default subjects;
export default subjects

View File

@ -6,13 +6,7 @@
export let href
</script>
<a
tabindex="0"
{href}
rel="noopener noreferrer"
target="_blank"
class="link"
>
<a tabindex="0" {href} rel="noopener noreferrer" target="_blank" class="link">
<Icon size="XS" name={icon} />
<span class="text">
<slot>

View File

@ -7,7 +7,6 @@
export let href = null
</script>
{#if href !== null}
<a
tabindex="0"
@ -15,7 +14,7 @@
rel="noopener noreferrer"
target="_blank"
class="infoWord"
style:color={color}
style:color
style:border-color={color}
on:mouseenter
on:mouseleave
@ -33,7 +32,7 @@
<div
role="tooltip"
class="infoWord"
style:color={color}
style:color
style:border-color={color}
on:mouseenter
on:mouseleave

View File

@ -1,6 +1,7 @@
<script>
export let noWrap = false
</script>
<div class="line">
<span class="bullet"></span>
<div class="content" class:noWrap>

View File

@ -1,40 +1,40 @@
<script>
import Comma from "./Comma.svelte";
import Period from "./Period.svelte";
import Space from "./Space.svelte";
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) => {
const getWords = value => {
if (typeof value !== "string") {
return [];
return []
}
const newWords = [];
let lastIndex = 0;
const newWords = []
let lastIndex = 0
const makeWord = (i) => {
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));
newWords.push(value.substring(lastIndex, i))
}
lastIndex = i + 1;
lastIndex = i + 1
}
value.split("").forEach((character, i) => {
if (punctuation.includes(character)) {
makeWord(i);
newWords.push(character);
makeWord(i)
newWords.push(character)
}
})
makeWord(value.length);
makeWord(value.length)
return newWords;
return newWords
}
$: words = getWords(value)

View File

@ -3,11 +3,11 @@
import { getDatasourceForProvider, getSchemaForDatasource } from "dataBinding"
import { selectedScreen } from "stores/builder"
import { createEventDispatcher } from "svelte"
import { Explanation } from './Explanation'
import { Explanation } from "./Explanation"
import { debounce } from "lodash"
import { params } from "@roxi/routify"
import { Constants } from "@budibase/frontend-core"
import { FIELDS } from 'constants/backend'
import { FIELDS } from "constants/backend"
export let componentInstance = {}
export let value = ""
@ -45,20 +45,20 @@
const updateTooltip = debounce((e, option) => {
if (option == null) {
contextTooltipVisible = false;
contextTooltipVisible = false
} else {
contextTooltipAnchor = e?.target;
currentOption = option;
contextTooltipVisible = true;
contextTooltipAnchor = e?.target
currentOption = option
contextTooltipVisible = true
}
}, 200);
}, 200)
const onOptionMouseenter = (e, option) => {
updateTooltip(e, option);
updateTooltip(e, option)
}
const onOptionMouseleave = (e) => {
updateTooltip(e, null);
const onOptionMouseleave = e => {
updateTooltip(e, null)
}
const getOptionIcon = optionKey => {
const option = schema[optionKey]
@ -80,7 +80,7 @@
const getOptionIconTooltip = optionKey => {
const option = schema[optionKey]
const type = option?.type;
const type = option?.type
const field = Object.values(FIELDS).find(f => f.type === type)
if (field) {
@ -89,13 +89,12 @@
return ""
}
</script>
<Select
{placeholder}
value={boundValue}
on:change={onChange}
on:change={onChange}
{options}
{onOptionMouseenter}
{onOptionMouseleave}

View File

@ -1,12 +1,10 @@
<script>
import { Multiselect, ContextTooltip } from "@budibase/bbui"
import { getDatasourceForProvider, getSchemaForDatasource } from "dataBinding"
import { selectedScreen,
componentStore,
} from "stores/builder"
import { selectedScreen } from "stores/builder"
import { createEventDispatcher } from "svelte"
import { Explanation } from './Explanation'
import { FIELDS } from 'constants/backend'
import { Explanation } from "./Explanation"
import { FIELDS } from "constants/backend"
import { params } from "@roxi/routify"
import { debounce } from "lodash"
import { Constants } from "@budibase/frontend-core"
@ -59,7 +57,7 @@
const getOptionIconTooltip = optionKey => {
const option = schema[optionKey]
const type = option?.type;
const type = option?.type
const field = Object.values(FIELDS).find(f => f.type === type)
if (field) {
@ -75,20 +73,20 @@
const updateTooltip = debounce((e, option) => {
if (option == null) {
contextTooltipVisible = false;
contextTooltipVisible = false
} else {
contextTooltipAnchor = e?.target;
currentOption = option;
contextTooltipVisible = true;
contextTooltipAnchor = e?.target
currentOption = option
contextTooltipVisible = true
}
}, 200);
}, 200)
const onOptionMouseenter = (e, option) => {
updateTooltip(e, option);
updateTooltip(e, option)
}
const onOptionMouseleave = (e) => {
updateTooltip(e, null);
const onOptionMouseleave = e => {
updateTooltip(e, null)
}
</script>

View File

@ -104,7 +104,7 @@ export class ComponentStore extends BudiStore {
...state,
components,
customComponents,
typeSupportPresets: components?.typeSupportPresets ?? {}
typeSupportPresets: components?.typeSupportPresets ?? {},
}))
// Sync client features to app store

View File

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

View File

@ -1,8 +1,8 @@
<script>
import { getContext } from "svelte"
import ApexCharts from 'apexcharts'
import ApexCharts from "apexcharts"
import { Icon } from "@budibase/bbui"
import { cloneDeep } from "lodash";
import { cloneDeep } from "lodash"
const { styleable, builderStore } = getContext("sdk")
const component = getContext("component")
@ -10,24 +10,24 @@
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);
$: optionsCopy = cloneDeep(options)
let chartElement;
let chart;
let chartElement
let chart
let currentType = null
const updateChart = async (newOptions) => {
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);
if (newOptions?.xaxis?.type && newOptions.xaxis.type !== currentType) {
await renderChart(chartElement)
} else {
await chart?.updateOptions(newOptions)
}
}
const renderChart = async (newChartElement) => {
const renderChart = async newChartElement => {
try {
await chart?.destroy()
chart = new ApexCharts(newChartElement, optionsCopy)
@ -37,7 +37,10 @@
// 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')") {
if (
e.message !==
"Cannot read properties of undefined (reading 'parentNode')"
) {
throw e
}
}
@ -54,8 +57,16 @@
{#key optionsCopy?.customColor}
<div class:hide use:styleable={$component.styles} bind:this={chartElement} />
{#if $builderStore.inBuilder && noData }
<div class="component-placeholder" use:styleable={{ ...$component.styles, normal: {}, custom: null, empty: true }}>
{#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>

View File

@ -24,10 +24,12 @@
export let gradient
$: series = getSeries(dataProvider, valueColumns)
$: categories = getCategories(dataProvider, labelColumn);
$: categories = getCategories(dataProvider, labelColumn)
$: labelType = dataProvider?.schema?.[labelColumn]?.type === 'datetime' ?
"datetime" : "category"
$: labelType =
dataProvider?.schema?.[labelColumn]?.type === "datetime"
? "datetime"
: "category"
$: xAxisFormatter = getFormatter(labelType, valueUnits, "x")
$: yAxisFormatter = getFormatter(labelType, valueUnits, "y")
$: fill = getFill(gradient)
@ -35,11 +37,11 @@
$: options = {
series,
stroke: {
curve: curve.toLowerCase()
curve: curve.toLowerCase(),
},
colors: palette === "Custom" ? [c1, c2, c3, c4, c5] : [],
theme: {
palette: palette === "Custom" ? null : palette
palette: palette === "Custom" ? null : palette,
},
fill,
legend: {
@ -54,7 +56,7 @@
text: title,
},
dataLabels: {
enabled: dataLabels
enabled: dataLabels,
},
chart: {
height: height == null || height === "" ? "auto" : height,
@ -62,7 +64,7 @@
type: "area",
stacked,
animations: {
enabled: animate
enabled: animate,
},
toolbar: {
show: false,
@ -75,24 +77,24 @@
type: labelType,
categories,
labels: {
formatter: xAxisFormatter
formatter: xAxisFormatter,
},
title: {
text: xAxisLabel
}
text: xAxisLabel,
},
},
yaxis: {
labels: {
formatter: yAxisFormatter
formatter: yAxisFormatter,
},
title: {
text: yAxisLabel
}
}
text: yAxisLabel,
},
},
}
const getSeries = (dataProvider, valueColumns = []) => {
const rows = dataProvider.rows ?? [];
const rows = dataProvider.rows ?? []
return valueColumns.map(column => ({
name: column,
@ -100,7 +102,7 @@
const value = row?.[column]
if (dataProvider?.schema?.[column]?.type === "datetime" && value) {
return Date.parse(value);
return Date.parse(value)
}
return value
@ -109,7 +111,7 @@
}
const getCategories = (dataProvider, labelColumn) => {
const rows = dataProvider.rows ?? [];
const rows = dataProvider.rows ?? []
return rows.map(row => {
const value = row?.[labelColumn]
@ -119,7 +121,7 @@
return ""
}
return value;
return value
})
}
@ -137,7 +139,7 @@
return formatters[valueUnits]
}
const getFill = (gradient) => {
const getFill = gradient => {
if (gradient) {
return {
type: "gradient",
@ -146,11 +148,11 @@
opacityFrom: 0.7,
opacityTo: 0.9,
stops: [0, 90, 100],
}
},
}
}
return { type: 'solid' }
return { type: "solid" }
}
</script>

View File

@ -21,10 +21,12 @@
export let horizontal
$: series = getSeries(dataProvider, valueColumns)
$: categories = getCategories(dataProvider, labelColumn);
$: categories = getCategories(dataProvider, labelColumn)
$: labelType = dataProvider?.schema?.[labelColumn]?.type === 'datetime' ?
"datetime" : "category"
$: labelType =
dataProvider?.schema?.[labelColumn]?.type === "datetime"
? "datetime"
: "category"
$: xAxisFormatter = getFormatter(labelType, valueUnits, horizontal, "x")
$: yAxisFormatter = getFormatter(labelType, valueUnits, horizontal, "y")
@ -32,7 +34,7 @@
series,
colors: palette === "Custom" ? [c1, c2, c3, c4, c5] : [],
theme: {
palette: palette === "Custom" ? null : palette
palette: palette === "Custom" ? null : palette,
},
legend: {
show: legend,
@ -46,7 +48,7 @@
text: title,
},
dataLabels: {
enabled: dataLabels
enabled: dataLabels,
},
chart: {
height: height == null || height === "" ? "auto" : height,
@ -54,7 +56,7 @@
type: "bar",
stacked,
animations: {
enabled: animate
enabled: animate,
},
toolbar: {
show: false,
@ -65,33 +67,33 @@
},
plotOptions: {
bar: {
horizontal
}
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
formatter: xAxisFormatter,
},
title: {
text: xAxisLabel
}
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
formatter: yAxisFormatter,
},
title: {
text: yAxisLabel
}
}
text: yAxisLabel,
},
},
}
const getSeries = (dataProvider, valueColumns = []) => {
const rows = dataProvider.rows ?? [];
const rows = dataProvider.rows ?? []
return valueColumns.map(column => ({
name: column,
@ -99,7 +101,7 @@
const value = row?.[column]
if (dataProvider?.schema?.[column]?.type === "datetime" && value) {
return Date.parse(value);
return Date.parse(value)
}
return value
@ -108,7 +110,7 @@
}
const getCategories = (dataProvider, labelColumn) => {
const rows = dataProvider.rows ?? [];
const rows = dataProvider.rows ?? []
return rows.map(row => {
const value = row?.[labelColumn]
@ -118,12 +120,13 @@
return ""
}
return value;
return value
})
}
const getFormatter = (labelType, valueUnits, horizontal, axis) => {
const isLabelAxis = (axis === "y" && horizontal) || axis === "x" && !horizontal
const isLabelAxis =
(axis === "y" && horizontal) || (axis === "x" && !horizontal)
if (labelType === "datetime" && isLabelAxis) {
return formatters["Datetime"]
}

View File

@ -1,6 +1,6 @@
<script>
import ApexChart from "./ApexChart.svelte"
import formatters from './formatters';
import formatters from "./formatters"
export let title
export let dataProvider
@ -35,7 +35,7 @@
width: width == null || width === "" ? "100%" : width,
type: "candlestick",
animations: {
enabled: animate
enabled: animate,
},
toolbar: {
show: false,
@ -46,42 +46,42 @@
},
xaxis: {
tooltip: {
formatter: formatters["Datetime"]
formatter: formatters["Datetime"],
},
type: "datetime",
title: {
text: xAxisLabel
}
text: xAxisLabel,
},
},
yaxis: {
labels: {
formatter: formatters[valueUnits]
formatter: formatters[valueUnits],
},
title: {
text: yAxisLabel
}
}
text: yAxisLabel,
},
},
}
const getValueAsUnixTime = (dataprovider, dateColumn, row) => {
const value = row[dateColumn]
if (dataProvider?.schema?.[dateColumn]?.type === 'datetime') {
return Date.parse(value);
if (dataProvider?.schema?.[dateColumn]?.type === "datetime") {
return Date.parse(value)
}
if (typeof value === "number") {
return value;
return value
}
const isString = typeof value === "string";
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);
const unixTime = Date.parse(value)
if (isNaN(unixTime)) {
return null
@ -91,7 +91,7 @@
}
if (isString) {
const unixTime = parseInt(value, 10);
const unixTime = parseInt(value, 10)
if (isNaN(unixTime)) {
return null
@ -100,7 +100,7 @@
return unixTime
}
return null;
return null
}
const getSeries = (
@ -111,24 +111,26 @@
lowColumn,
closeColumn
) => {
const rows = dataProvider.rows ?? [];
const rows = dataProvider.rows ?? []
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])
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])
return [
getValueAsUnixTime(dataProvider, dateColumn, row),
isNaN(open) ? 0 : open,
isNaN(high) ? 0 : high,
isNaN(low) ? 0 : low,
isNaN(close) ? 0 : close,
]
})
}]
return [
getValueAsUnixTime(dataProvider, dateColumn, row),
isNaN(open) ? 0 : open,
isNaN(high) ? 0 : high,
isNaN(low) ? 0 : low,
isNaN(close) ? 0 : close,
]
}),
},
]
}
</script>

View File

@ -1,6 +1,6 @@
<script>
import ApexChart from "./ApexChart.svelte"
import formatters from "./formatters";
import formatters from "./formatters"
export let title
export let dataProvider
@ -14,17 +14,19 @@
export let palette
export let c1, c2, c3, c4, c5
$: labelType = dataProvider?.schema?.[labelColumn]?.type === 'datetime' ?
"datetime" : "category"
$: labelType =
dataProvider?.schema?.[labelColumn]?.type === "datetime"
? "datetime"
: "category"
$: series = getSeries(dataProvider, valueColumn)
$: labels = getLabels(dataProvider, labelColumn, labelType);
$: labels = getLabels(dataProvider, labelColumn, labelType)
$: options = {
series,
labels,
colors: palette === "Custom" ? [c1, c2, c3, c4, c5] : [],
theme: {
palette: palette === "Custom" ? null : palette
palette: palette === "Custom" ? null : palette,
},
legend: {
show: legend,
@ -38,14 +40,14 @@
text: title,
},
dataLabels: {
enabled: dataLabels
enabled: dataLabels,
},
chart: {
height: height == null || height === "" ? "auto" : height,
width: width == null || width === "" ? "100%" : width,
type: "donut",
animations: {
enabled: animate
enabled: animate,
},
toolbar: {
show: false,
@ -57,27 +59,27 @@
}
const getSeries = (dataProvider, valueColumn) => {
const rows = dataProvider.rows ?? [];
const rows = dataProvider.rows ?? []
return rows.map(row => {
const value = row?.[valueColumn]
if (dataProvider?.schema?.[valueColumn]?.type === "datetime" && value) {
return Date.parse(value);
return Date.parse(value)
}
// This chart doesn't automatically parse strings into numbers
const numValue = parseFloat(value);
const numValue = parseFloat(value)
if (isNaN(numValue)) {
return 0;
return 0
}
return numValue;
return numValue
})
}
const getLabels = (dataProvider, labelColumn, labelType) => {
const rows = dataProvider.rows ?? [];
const rows = dataProvider.rows ?? []
return rows.map(row => {
const value = row?.[labelColumn]
@ -89,7 +91,7 @@
return formatters["Datetime"](value)
}
return value;
return value
})
}
</script>

View File

@ -25,13 +25,13 @@
series,
colors: palette === "Custom" ? [c1, c2, c3, c4, c5] : [],
theme: {
palette: palette === "Custom" ? null : palette
palette: palette === "Custom" ? null : palette,
},
title: {
text: title,
},
dataLabels: {
enabled: dataLabels
enabled: dataLabels,
},
chart: {
height: height == null || height === "" ? "auto" : height,
@ -39,7 +39,7 @@
type: "bar",
stacked,
animations: {
enabled: animate
enabled: animate,
},
toolbar: {
show: false,
@ -50,51 +50,56 @@
},
plotOptions: {
bar: {
horizontal
}
horizontal,
},
},
xaxis: {
type: 'category',
type: "category",
title: {
text: xAxisLabel
text: xAxisLabel,
},
labels: {
formatter: xAxisFormatter
formatter: xAxisFormatter,
},
},
yaxis: {
decimalsInFloat: 0,
title: {
text: yAxisLabel
text: yAxisLabel,
},
labels: {
formatter: yAxisFormatter
formatter: yAxisFormatter,
},
}
},
}
const getSeries = (dataProvider, valueColumn, bucketCount) => {
const rows = dataProvider.rows ?? [];
const rows = dataProvider.rows ?? []
const values = rows.map(row => parseFloat(row[valueColumn])).filter(value => !isNaN(value))
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);
const counts = Array(bucketCount).fill(0)
values.forEach(value => {
const bucketIndex = buckets.findIndex(bucket => bucket.min <= value && value <= bucket.max)
const bucketIndex = buckets.findIndex(
bucket => bucket.min <= value && value <= bucket.max
)
counts[bucketIndex]++
});
})
const series = buckets.map((bucket, index) => ({ x: `${bucket.min} ${bucket.max}`, y: counts[index] }))
const series = buckets.map((bucket, index) => ({
x: `${bucket.min} ${bucket.max}`,
y: counts[index],
}))
return [
{ data: series }
]
return [{ data: series }]
}
const getValuesRange = (values) => {
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`
@ -110,7 +115,7 @@
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 bucketRemainder = range - bucketSize * bucketCount
const buckets = []
@ -121,28 +126,28 @@
buckets.push({
min: lastBucketMax,
max: lastBucketMax + bucketSize + remainderPadding
max: lastBucketMax + bucketSize + remainderPadding,
})
}
return buckets;
return buckets
}
const getFormatter = (horizontal, axis) => {
// Don't display decimals in between integers on the value axis
if ((horizontal && axis === "x") || (!horizontal && axis === "y")) {
return (value) => {
return value => {
if (Math.floor(value) === value) {
return value;
return value
}
// 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 ""
}
}
return (value) => value
return value => value
}
</script>

View File

@ -19,21 +19,23 @@
export let c1, c2, c3, c4, c5
$: series = getSeries(dataProvider, valueColumns)
$: categories = getCategories(dataProvider, labelColumn);
$: categories = getCategories(dataProvider, labelColumn)
$: labelType = dataProvider?.schema?.[labelColumn]?.type === 'datetime' ?
"datetime" : "category"
$: labelType =
dataProvider?.schema?.[labelColumn]?.type === "datetime"
? "datetime"
: "category"
$: xAxisFormatter = getFormatter(labelType, valueUnits, "x")
$: yAxisFormatter = getFormatter(labelType, valueUnits, "y")
$: options = {
series,
stroke: {
curve: curve.toLowerCase()
curve: curve.toLowerCase(),
},
colors: palette === "Custom" ? [c1, c2, c3, c4, c5] : [],
theme: {
palette: palette === "Custom" ? null : palette
palette: palette === "Custom" ? null : palette,
},
legend: {
show: legend,
@ -47,14 +49,14 @@
text: title,
},
dataLabels: {
enabled: dataLabels
enabled: dataLabels,
},
chart: {
height: height == null || height === "" ? "auto" : height,
width: width == null || width === "" ? "100%" : width,
type: "line",
animations: {
enabled: animate
enabled: animate,
},
toolbar: {
show: false,
@ -67,24 +69,24 @@
type: labelType,
categories,
labels: {
formatter: xAxisFormatter
formatter: xAxisFormatter,
},
title: {
text: xAxisLabel
}
text: xAxisLabel,
},
},
yaxis: {
labels: {
formatter: yAxisFormatter
formatter: yAxisFormatter,
},
title: {
text: yAxisLabel
}
}
text: yAxisLabel,
},
},
}
const getSeries = (dataProvider, valueColumns = []) => {
const rows = dataProvider.rows ?? [];
const rows = dataProvider.rows ?? []
return valueColumns.map(column => ({
name: column,
@ -92,7 +94,7 @@
const value = row?.[column]
if (dataProvider?.schema?.[column]?.type === "datetime" && value) {
return Date.parse(value);
return Date.parse(value)
}
return value
@ -101,7 +103,7 @@
}
const getCategories = (dataProvider, labelColumn) => {
const rows = dataProvider.rows ?? [];
const rows = dataProvider.rows ?? []
return rows.map(row => {
const value = row?.[labelColumn]
@ -111,7 +113,7 @@
return ""
}
return value;
return value
})
}

View File

@ -1,6 +1,6 @@
<script>
import ApexChart from "./ApexChart.svelte"
import formatters from "./formatters";
import formatters from "./formatters"
export let title
export let dataProvider
@ -14,17 +14,19 @@
export let palette
export let c1, c2, c3, c4, c5
$: labelType = dataProvider?.schema?.[labelColumn]?.type === 'datetime' ?
"datetime" : "category"
$: labelType =
dataProvider?.schema?.[labelColumn]?.type === "datetime"
? "datetime"
: "category"
$: series = getSeries(dataProvider, valueColumn)
$: labels = getLabels(dataProvider, labelColumn, labelType);
$: labels = getLabels(dataProvider, labelColumn, labelType)
$: options = {
series,
labels,
colors: palette === "Custom" ? [c1, c2, c3, c4, c5] : [],
theme: {
palette: palette === "Custom" ? null : palette
palette: palette === "Custom" ? null : palette,
},
legend: {
show: legend,
@ -38,14 +40,14 @@
text: title,
},
dataLabels: {
enabled: dataLabels
enabled: dataLabels,
},
chart: {
height: height == null || height === "" ? "auto" : height,
width: width == null || width === "" ? "100%" : width,
type: "pie",
animations: {
enabled: animate
enabled: animate,
},
toolbar: {
show: false,
@ -57,27 +59,27 @@
}
const getSeries = (dataProvider, valueColumn) => {
const rows = dataProvider.rows ?? [];
const rows = dataProvider.rows ?? []
return rows.map(row => {
const value = row?.[valueColumn]
if (dataProvider?.schema?.[valueColumn]?.type === "datetime" && value) {
return Date.parse(value);
return Date.parse(value)
}
// This chart doesn't automatically parse strings into numbers
const numValue = parseFloat(value);
const numValue = parseFloat(value)
if (isNaN(numValue)) {
return 0;
return 0
}
return numValue;
return numValue
})
}
const getLabels = (dataProvider, labelColumn, labelType) => {
const rows = dataProvider.rows ?? [];
const rows = dataProvider.rows ?? []
return rows.map(row => {
const value = row?.[labelColumn]
@ -89,7 +91,7 @@
return formatters["Datetime"](value)
}
return value;
return value
})
}
</script>

View File

@ -2,7 +2,7 @@ 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()
["Datetime"]: val => new Date(val).toLocaleString(),
}
export default formatters

View File

@ -101,14 +101,19 @@ export const propsUseBinding = (props, bindingKey) => {
/**
* Gets the definition of this component's settings from the manifest
*/
export const getSettingsDefinition = (definition) => {
export const getSettingsDefinition = definition => {
if (!definition) {
return []
}
let settings = []
definition.settings?.forEach(setting => {
if (setting.section) {
settings = settings.concat((setting.settings || [])?.map(childSetting => ({ ...childSetting, sectionDependsOn: setting.dependsOn })))
settings = settings.concat(
(setting.settings || [])?.map(childSetting => ({
...childSetting,
sectionDependsOn: setting.dependsOn,
}))
)
} else {
settings.push(setting)
}