linting
This commit is contained in:
parent
9d4283e9ba
commit
53553d8dd6
|
@ -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)}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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} />
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<li>
|
||||
<div class="exampleLine">
|
||||
<slot />
|
||||
</div>
|
||||
<div class="exampleLine">
|
||||
<slot />
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<style>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
export let value;
|
||||
export let value
|
||||
</script>
|
||||
|
||||
<pre class="pre">
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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],
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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 />
|
||||
|
|
|
@ -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"
|
||||
/>
|
||||
|
|
|
@ -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 />
|
||||
|
|
|
@ -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 />
|
||||
|
|
|
@ -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 />
|
||||
|
|
|
@ -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 />
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<script>
|
||||
export let noWrap = false
|
||||
</script>
|
||||
|
||||
<div class="line">
|
||||
<span class="bullet">•</span>
|
||||
<div class="content" class:noWrap>
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -104,7 +104,7 @@ export class ComponentStore extends BudiStore {
|
|||
...state,
|
||||
components,
|
||||
customComponents,
|
||||
typeSupportPresets: components?.typeSupportPresets ?? {}
|
||||
typeSupportPresets: components?.typeSupportPresets ?? {},
|
||||
}))
|
||||
|
||||
// Sync client features to app store
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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"]
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue