This commit is contained in:
Gerard Burns 2024-04-01 12:31:16 +01:00
parent ab40e3babd
commit 7eed50707e
8 changed files with 474 additions and 116 deletions

View File

@ -9,8 +9,10 @@
export let options = [] export let options = []
export let getOptionLabel = option => option export let getOptionLabel = option => option
export let getOptionValue = option => option export let getOptionValue = option => option
export let getOptionsIcon = () => null export let getOptionIcon = () => null
export let getOptionsIconToolip = () => null export let getOptionIconTooltip = () => null
export let getOptionTooltip = () => null
export let isOptionEnabled = () => true
export let readonly = false export let readonly = false
export let autocomplete = false export let autocomplete = false
export let sort = false export let sort = false
@ -19,6 +21,7 @@
export let customPopoverHeight export let customPopoverHeight
export let open = false export let open = false
export let loading export let loading
export let align
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
@ -82,8 +85,10 @@
<Picker <Picker
on:loadMore on:loadMore
{getOptionsIcon} {isOptionEnabled}
{getOptionsIconTooltip} {getOptionIcon}
{getOptionIconTooltip}
{getOptionTooltip}
{id} {id}
{disabled} {disabled}
{readonly} {readonly}
@ -101,4 +106,5 @@
{autoWidth} {autoWidth}
{customPopoverHeight} {customPopoverHeight}
{loading} {loading}
{align}
/> />

View File

@ -11,6 +11,13 @@
import Tags from "../../Tags/Tags.svelte" import Tags from "../../Tags/Tags.svelte"
import Tag from "../../Tags/Tag.svelte" import Tag from "../../Tags/Tag.svelte"
import ProgressCircle from "../../ProgressCircle/ProgressCircle.svelte" import ProgressCircle from "../../ProgressCircle/ProgressCircle.svelte"
import {
default as AbsTooltip,
TooltipPosition,
TooltipType,
} from "../../Tooltip/AbsTooltip.svelte"
import ContextTooltip from "../../Tooltip/Context.svelte"
export let id = null export let id = null
export let disabled = false export let disabled = false
@ -27,6 +34,7 @@
export let getOptionValue = option => option export let getOptionValue = option => option
export let getOptionIcon = () => null export let getOptionIcon = () => null
export let getOptionIconTooltip = () => null export let getOptionIconTooltip = () => null
export let getOptionTooltip = () => null
export let useOptionIconImage = false export let useOptionIconImage = false
export let getOptionColour = () => null export let getOptionColour = () => null
export let getOptionSubtitle = () => null export let getOptionSubtitle = () => null
@ -48,6 +56,11 @@
let button let button
let component let component
let contextTooltipId = 0;
let contextTooltipAnchor = null
let contextTooltipOption = null
let contextTooltipVisible = false
$: sortedOptions = getSortedOptions(options, getOptionLabel, sort) $: sortedOptions = getSortedOptions(options, getOptionLabel, sort)
$: filteredOptions = getFilteredOptions( $: filteredOptions = getFilteredOptions(
sortedOptions, sortedOptions,
@ -103,6 +116,29 @@
onDestroy(() => { onDestroy(() => {
component?.removeEventListener("scroll", null) component?.removeEventListener("scroll", null)
}) })
const handleMouseenter = (e, option) => {
contextTooltipId += 1;
const invokedContextTooltipId = contextTooltipId
setTimeout(() => {
if (contextTooltipId === invokedContextTooltipId) {
contextTooltipAnchor = e.target;
contextTooltipOption = option;
contextTooltipVisible = true;
} else {
console.log("not long enough");
}
}, 400)
}
const handleMouseleave = (e, option) => {
setTimeout(() => {
if (option === contextTooltipOption) {
contextTooltipVisible = false;
}
}, 600)
}
</script> </script>
<button <button
@ -193,6 +229,8 @@
{#if filteredOptions.length} {#if filteredOptions.length}
{#each filteredOptions as option, idx} {#each filteredOptions as option, idx}
<li <li
on:mouseenter={(e) => handleMouseenter(e, option)}
on:mouseleave={(e) => handleMouseleave(e, option)}
class="spectrum-Menu-item" class="spectrum-Menu-item"
class:is-selected={isOptionSelected(getOptionValue(option, idx))} class:is-selected={isOptionSelected(getOptionValue(option, idx))}
role="option" role="option"
@ -203,7 +241,7 @@
> >
{#if getOptionIcon(option, idx)} {#if getOptionIcon(option, idx)}
<span class="option-extra icon"> <span class="option-extra icon">
{#if useoptioniconimage} {#if useOptionIconImage}
<img <img
src={getOptionIcon(option, idx)} src={getOptionIcon(option, idx)}
alt="icon" alt="icon"
@ -211,7 +249,7 @@
height="15" height="15"
/> />
{:else} {:else}
<Icon size="S" name={getOptionIcon(option, idx)} /> <Icon tooltip={getOptionIconTooltip(option)} size="S" name={getOptionIcon(option, idx)} />
{/if} {/if}
</span> </span>
{/if} {/if}
@ -236,7 +274,7 @@
</span> </span>
{/if} {/if}
<svg <svg
class="spectrum-Icon spectrum-UIIcon-Checkmark100 spectrum-Menu-checkmark spectrum-Menu-itemIcon" class="selectedIcon spectrum-Icon spectrum-UIIcon-Checkmark100 spectrum-Menu-checkmark spectrum-Menu-itemIcon"
focusable="false" focusable="false"
aria-hidden="true" aria-hidden="true"
> >
@ -260,8 +298,31 @@
{/if} {/if}
</div> </div>
</Popover> </Popover>
<ContextTooltip
visible={contextTooltipVisible}
anchor={contextTooltipAnchor}
>
<div
class="tooltipContents"
>
{contextTooltipOption}
</div>
</ContextTooltip>
<style> <style>
.tooltipContents {
width: 300px;
background-color: red;
}
.spectrum-Menu {
display: block;
}
.spectrum-Menu:hover .context {
display: block;
}
.spectrum-Picker { .spectrum-Picker {
width: 100%; width: 100%;
box-shadow: none; box-shadow: none;

View File

@ -13,12 +13,17 @@
export let options = [] export let options = []
export let getOptionLabel = option => option export let getOptionLabel = option => option
export let getOptionValue = option => option export let getOptionValue = option => option
export let getOptionIcon = () => null
export let getOptionIconTooltip = () => null
export let getOptionTooltip = () => null
export let isOptionEnabled = () => true
export let sort = false export let sort = false
export let autoWidth = false export let autoWidth = false
export let autocomplete = false export let autocomplete = false
export let searchTerm = null export let searchTerm = null
export let customPopoverHeight export let customPopoverHeight
export let helpText = null export let helpText = null
export let align
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
const onChange = e => { const onChange = e => {
@ -29,6 +34,10 @@
<Field {helpText} {label} {labelPosition} {error}> <Field {helpText} {label} {labelPosition} {error}>
<Multiselect <Multiselect
{isOptionEnabled}
{getOptionIcon}
{getOptionIconTooltip}
{getOptionTooltip}
{error} {error}
{disabled} {disabled}
{readonly} {readonly}
@ -41,6 +50,7 @@
{autoWidth} {autoWidth}
{autocomplete} {autocomplete}
{customPopoverHeight} {customPopoverHeight}
{align}
bind:searchTerm bind:searchTerm
on:change={onChange} on:change={onChange}
on:click on:click

View File

@ -0,0 +1,129 @@
<script>
import Portal from "svelte-portal"
export let tooltip
export let anchor
export let visible = false
export let hovering = false
let targetX = 0
let targetY = 0
let animationId = 0;
let x = 0;
let y = 0;
const updatePositionOnVisibilityChange = (visible, hovering) => {
if (!visible && !hovering) {
x = 0;
y = 0;
}
}
const updatePosition = (anchor, tooltip) => {
if (anchor == null) {
return;
}
const rect = anchor.getBoundingClientRect();
const tooltipWidth = tooltip?.getBoundingClientRect()?.width ?? 0;
targetX = rect.x - tooltipWidth
targetY = rect.y
animationId += 1
if (x === 0 && y === 0) {
x = targetX
y = targetY
}
}
const animate = (invokedAnimationId, xRate = null, yRate = null) => {
if (invokedAnimationId !== animationId) {
console.log("CANCEL ANIMATION ", invokedAnimationId, " ", animationId);
return;
}
console.log("animating");
const animationDurationInFrames = 10;
const xDelta = targetX - x
const yDelta = targetY - y
if (xRate === null) {
xRate = Math.abs(xDelta) / animationDurationInFrames
}
if (yRate === null) {
yRate = Math.abs(yDelta) / animationDurationInFrames
}
if (xDelta === 0 && yDelta === 0) return;
if (
(xDelta > 0 && xDelta <= xRate) ||
(xDelta < 0 && xDelta >= -xRate)
) {
x = targetX;
} else if (xDelta > 0) {
x = x + xRate;
} else if (xDelta < 0) {
x = x - xRate;
}
if (
(yDelta > 0 && yDelta <= yRate) ||
(yDelta < 0 && yDelta >= -yRate)
) {
y = targetY;
} else if (yDelta > 0) {
y = y + yRate;
} else if (yDelta < 0) {
y = y - yRate;
}
requestAnimationFrame(() => animate(invokedAnimationId, xRate, yRate))
}
$: updatePosition(anchor, tooltip)
$: updatePositionOnVisibilityChange(visible, hovering)
$: requestAnimationFrame(() => animate(animationId))
const handleMouseenter = (e) => {
hovering = true;
}
const handleMouseleave = (e) => {
hovering = false;
}
</script>
<Portal target=".spectrum">
<div
bind:this={tooltip}
on:mouseenter={handleMouseenter}
on:mouseleave={handleMouseleave}
style:top={`${y}px`}
style:left={`${x}px`}
class="tooltip"
class:visible={visible || hovering}
>
<slot />
</div>
</Portal>
<style>
.tooltip {
position: absolute;
z-index: 9999;
opacity: 0;
pointer-events: none;
}
.visible {
opacity: 1;
pointer-events: auto;
}
</style>

View File

@ -3,14 +3,36 @@
import { getDatasourceForProvider, getSchemaForDatasource } from "dataBinding" import { getDatasourceForProvider, getSchemaForDatasource } from "dataBinding"
import { selectedScreen } from "stores/builder" import { selectedScreen } from "stores/builder"
import { createEventDispatcher } from "svelte" import { createEventDispatcher } from "svelte"
import { validators, supported, partialSupport, unsupported } from "../fieldValidator";
export let componentInstance = {} export let componentInstance = {}
export let value = "" export let value = ""
export let placeholder export let placeholder
export let fieldValidator
$: {
console.log(fieldValidator);
}
const getFieldSupport = (schema, fieldValidator) => {
if (fieldValidator == null) {
return {}
}
const validator = validators[fieldValidator];
const fieldSupport = {}
Object.entries(schema || {}).forEach(([key, value]) => {
fieldSupport[key] = validator(value)
})
return fieldSupport
}
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
$: datasource = getDatasourceForProvider($selectedScreen, componentInstance) $: datasource = getDatasourceForProvider($selectedScreen, componentInstance)
$: schema = getSchemaForDatasource($selectedScreen, datasource).schema $: schema = getSchemaForDatasource($selectedScreen, datasource).schema
$: fieldSupport = getFieldSupport(schema, fieldValidator);
$: options = Object.keys(schema || {}) $: options = Object.keys(schema || {})
$: boundValue = getValidValue(value, options) $: boundValue = getValidValue(value, options)
@ -32,6 +54,32 @@
boundValue = getValidValue(value.detail, options) boundValue = getValidValue(value.detail, options)
dispatch("change", boundValue) dispatch("change", boundValue)
} }
const getOptionIcon = option => {
const support = fieldSupport[option]?.support;
if (support == null) return null;
if (support === supported) return null
if (support === partialSupport) return "Warning"
if (support === unsupported) return "Error"
}
const getOptionIconTooltip = option => {
}
const isOptionEnabled = option => {
const support = fieldSupport[option]?.support;
if (support == null) return true
if (support == unsupported) return false
return true
}
</script> </script>
<Select {placeholder} value={boundValue} on:change={onChange} {options} /> <Select
{isOptionEnabled}
{getOptionIcon}
{getOptionIconTooltip}
{placeholder} value={boundValue} on:change={onChange} {options} />

View File

@ -3,17 +3,60 @@
import { getDatasourceForProvider, getSchemaForDatasource } from "dataBinding" import { getDatasourceForProvider, getSchemaForDatasource } from "dataBinding"
import { selectedScreen } from "stores/builder" import { selectedScreen } from "stores/builder"
import { createEventDispatcher } from "svelte" import { createEventDispatcher } from "svelte"
import { validators, supported, partialSupport, unsupported } from "../fieldValidator";
export let componentInstance = {} export let componentInstance = {}
export let value = "" export let value = ""
export let placeholder export let placeholder
export let fieldValidator
const TypeIconMap = {
text: "Text",
options: "Dropdown",
datetime: "Date",
barcodeqr: "Camera",
longform: "TextAlignLeft",
array: "Dropdown",
number: "123",
boolean: "Boolean",
attachment: "AppleFiles",
link: "DataCorrelated",
formula: "Calculator",
json: "Brackets",
bigint: "TagBold",
bb_reference: {
user: "User",
users: "UserGroup",
},
}
const getFieldSupport = (schema, fieldValidator) => {
if (fieldValidator == null) {
return {}
}
const validator = validators[fieldValidator];
const fieldSupport = {}
Object.entries(schema || {}).forEach(([key, value]) => {
fieldSupport[key] = validator(value)
})
return fieldSupport
}
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
$: datasource = getDatasourceForProvider($selectedScreen, componentInstance) $: datasource = getDatasourceForProvider($selectedScreen, componentInstance)
$: schema = getSchemaForDatasource($selectedScreen, datasource).schema $: schema = getSchemaForDatasource($selectedScreen, datasource).schema
$: options = Object.keys(schema || {}) $: options = Object.keys(schema || {})
$: fieldSupport = getFieldSupport(schema, fieldValidator);
$: boundValue = getValidOptions(value, options) $: boundValue = getValidOptions(value, options)
$: {
console.log(schema)
}
const getValidOptions = (selectedOptions, allOptions) => { const getValidOptions = (selectedOptions, allOptions) => {
// Fix the hardcoded default string value // Fix the hardcoded default string value
if (!Array.isArray(selectedOptions)) { if (!Array.isArray(selectedOptions)) {
@ -26,6 +69,69 @@
boundValue = getValidOptions(value.detail, options) boundValue = getValidOptions(value.detail, options)
dispatch("change", boundValue) dispatch("change", boundValue)
} }
const foo = () => {
const support = fieldSupport[option]?.support;
if (support == null) return null;
if (support === supported) return null
if (support === partialSupport) return "AlertCircleFilled"
if (support === unsupported) return "AlertCircleFilled"
}
const getOptionIcon = optionKey => {
const option = schema[optionKey]
if (option.autocolumn) {
return "MagicWand"
}
const { type, subtype } = option
const result =
typeof TypeIconMap[type] === "object" && subtype
? TypeIconMap[type][subtype]
: TypeIconMap[type]
return result || "Text"
}
const getOptionIconTooltip = optionKey => {
const option = schema[optionKey]
return option.type;
}
const getOptionTooltip = optionKey => {
console.log(optionKey)
const support = fieldSupport[optionKey]?.support;
const message = fieldSupport[optionKey]?.message;
if (support === unsupported) return message
return null
}
const isOptionEnabled = optionKey => {
// Remain enabled if already selected, so it can be deselected
if (value?.includes(optionKey)) return true
const support = fieldSupport[optionKey]?.support;
if (support == null) return true
if (support === unsupported) return false
return true
}
</script> </script>
<Multiselect {placeholder} value={boundValue} on:change={setValue} {options} /> <Multiselect
iconPosition="right"
{isOptionEnabled}
{getOptionIcon}
{getOptionIconTooltip}
{getOptionTooltip}
{placeholder}
value={boundValue}
on:change={setValue}
{options}
align="right"
/>

View File

@ -2,7 +2,7 @@ export const unsupported = Symbol("values-validator-unsupported")
export const partialSupport = Symbol("values-validator-partial-support") export const partialSupport = Symbol("values-validator-partial-support")
export const supported = Symbol("values-validator-supported") export const supported = Symbol("values-validator-supported")
const validatorMap = { export const validators = {
chart: (fieldSchema) => { chart: (fieldSchema) => {
if ( if (
fieldSchema.type === "json" || fieldSchema.type === "json" ||
@ -14,7 +14,7 @@ const validatorMap = {
) { ) {
return { return {
support: unsupported, support: unsupported,
message: "This field cannot be used as a chart value" message: `"${fieldSchema.type}" columns cannot be used as a chart value long long long long long long long long long`
} }
} }
@ -38,5 +38,3 @@ const validatorMap = {
} }
} }
}; };
export default validatorMap;

View File

@ -1629,7 +1629,7 @@
"required": true "required": true
}, },
{ {
"type": "chartmultifield", "type": "multifield",
"label": "Data columns", "label": "Data columns",
"key": "valueColumns", "key": "valueColumns",
"dependsOn": "dataProvider", "dependsOn": "dataProvider",
@ -1788,7 +1788,7 @@
"required": true "required": true
}, },
{ {
"type": "chartmultifield", "type": "multifield",
"label": "Data columns", "label": "Data columns",
"key": "valueColumns", "key": "valueColumns",
"dependsOn": "dataProvider", "dependsOn": "dataProvider",
@ -1941,7 +1941,7 @@
"required": true "required": true
}, },
{ {
"type": "chartmultifield", "type": "multifield",
"label": "Data columns", "label": "Data columns",
"key": "valueColumns", "key": "valueColumns",
"dependsOn": "dataProvider", "dependsOn": "dataProvider",
@ -2106,7 +2106,7 @@
"required": true "required": true
}, },
{ {
"type": "chartfield", "type": "field",
"label": "Data column", "label": "Data column",
"key": "valueColumn", "key": "valueColumn",
"dependsOn": "dataProvider", "dependsOn": "dataProvider",
@ -2235,7 +2235,7 @@
"required": true "required": true
}, },
{ {
"type": "chartfield", "type": "field",
"label": "Data columns", "label": "Data columns",
"key": "valueColumn", "key": "valueColumn",
"dependsOn": "dataProvider", "dependsOn": "dataProvider",
@ -2364,28 +2364,28 @@
"required": true "required": true
}, },
{ {
"type": "chartfield", "type": "field",
"label": "Open column", "label": "Open column",
"key": "openColumn", "key": "openColumn",
"dependsOn": "dataProvider", "dependsOn": "dataProvider",
"required": true "required": true
}, },
{ {
"type": "chartfield", "type": "field",
"label": "Close column", "label": "Close column",
"key": "closeColumn", "key": "closeColumn",
"dependsOn": "dataProvider", "dependsOn": "dataProvider",
"required": true "required": true
}, },
{ {
"type": "chartfield", "type": "field",
"label": "High column", "label": "High column",
"key": "highColumn", "key": "highColumn",
"dependsOn": "dataProvider", "dependsOn": "dataProvider",
"required": true "required": true
}, },
{ {
"type": "chartfield", "type": "field",
"label": "Low column", "label": "Low column",
"key": "lowColumn", "key": "lowColumn",
"dependsOn": "dataProvider", "dependsOn": "dataProvider",
@ -2449,7 +2449,7 @@
"required": true "required": true
}, },
{ {
"type": "chartfield", "type": "field",
"label": "Data column", "label": "Data column",
"key": "valueColumn", "key": "valueColumn",
"dependsOn": "dataProvider", "dependsOn": "dataProvider",
@ -5266,7 +5266,7 @@
"required": true "required": true
}, },
{ {
"type": "chartfield", "type": "field",
"label": "Data column", "label": "Data column",
"key": "valueColumn", "key": "valueColumn",
"dependsOn": "dataSource", "dependsOn": "dataSource",
@ -5291,7 +5291,7 @@
"required": true "required": true
}, },
{ {
"type": "chartfield", "type": "field",
"label": "Data column", "label": "Data column",
"key": "valueColumn", "key": "valueColumn",
"dependsOn": "dataSource", "dependsOn": "dataSource",
@ -5316,7 +5316,7 @@
"required": true "required": true
}, },
{ {
"type": "chartmultifield", "type": "multifield",
"label": "Data columns", "label": "Data columns",
"key": "valueColumns", "key": "valueColumns",
"dependsOn": "dataSource", "dependsOn": "dataSource",
@ -5363,7 +5363,7 @@
}, },
"settings": [ "settings": [
{ {
"type": "chartfield", "type": "field",
"label": "Value column", "label": "Value column",
"key": "valueColumn", "key": "valueColumn",
"dependsOn": "dataSource", "dependsOn": "dataSource",
@ -5411,7 +5411,7 @@
"required": true "required": true
}, },
{ {
"type": "chartmultifield", "type": "multifield",
"label": "Data columns", "label": "Data columns",
"key": "valueColumns", "key": "valueColumns",
"dependsOn": "dataSource", "dependsOn": "dataSource",
@ -5460,7 +5460,7 @@
"required": true "required": true
}, },
{ {
"type": "chartmultifield", "type": "multifield",
"label": "Data columns", "label": "Data columns",
"key": "valueColumns", "key": "valueColumns",
"dependsOn": "dataSource", "dependsOn": "dataSource",
@ -5521,28 +5521,28 @@
"required": true "required": true
}, },
{ {
"type": "chartfield", "type": "field",
"label": "Open column", "label": "Open column",
"key": "openColumn", "key": "openColumn",
"dependsOn": "dataSource", "dependsOn": "dataSource",
"required": true "required": true
}, },
{ {
"type": "chartfield", "type": "field",
"label": "Close column", "label": "Close column",
"key": "closeColumn", "key": "closeColumn",
"dependsOn": "dataSource", "dependsOn": "dataSource",
"required": true "required": true
}, },
{ {
"type": "chartfield", "type": "field",
"label": "High column", "label": "High column",
"key": "highColumn", "key": "highColumn",
"dependsOn": "dataSource", "dependsOn": "dataSource",
"required": true "required": true
}, },
{ {
"type": "chartfield", "type": "field",
"label": "Low column", "label": "Low column",
"key": "lowColumn", "key": "lowColumn",
"dependsOn": "dataSource", "dependsOn": "dataSource",