budibase/packages/builder/src/components/design/settings/controls/MultiFieldSelect.svelte

325 lines
8.4 KiB
Svelte

<script>
import { Icon, Heading, Multiselect, ContextTooltip } from "@budibase/bbui"
import { getDatasourceForProvider, getSchemaForDatasource } from "dataBinding"
import { selectedScreen } from "stores/builder"
import { createEventDispatcher } from "svelte"
import { validators, constants as validatorConstants } from "../fieldValidator";
import ChartFieldContext from './FieldContext/Chart.svelte'
import { FIELDS } from 'constants/backend'
import { goto, params } from "@roxi/routify"
import { debounce } from "lodash"
export let componentInstance = {}
export let value = ""
export let placeholder
export let fieldValidator
$: {
console.log($params)
}
let contextTooltipId = 0;
let contextTooltipAnchor = null
let currentOption = null
let previousOption = null
let contextTooltipVisible = false
let currentOptionSupport = {}
let previousOptionSupport = {}
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()
$: datasource = getDatasourceForProvider($selectedScreen, componentInstance)
$: console.log(datasource)
$: schema = getSchemaForDatasource($selectedScreen, datasource).schema
$: options = Object.keys(schema || {})
$: fieldSupport = getFieldSupport(schema, fieldValidator);
$: boundValue = getValidOptions(value, options)
const getValidOptions = (selectedOptions, allOptions) => {
// Fix the hardcoded default string value
if (!Array.isArray(selectedOptions)) {
selectedOptions = []
}
return selectedOptions.filter(val => allOptions.indexOf(val) !== -1)
}
const setValue = value => {
boundValue = getValidOptions(value.detail, options)
dispatch("change", boundValue)
}
const getOptionIcon = optionKey => {
const option = schema[optionKey]
if (!option) return ""
if (option.autocolumn) {
return "MagicWand"
}
const { type, subtype } = option
const result =
typeof TypeIconMap[type] === "object" && subtype
? TypeIconMap[type][subtype]
: TypeIconMap[type]
return result || "Text"
}
const getOptionIconTooltip = optionKey => {
const option = schema[optionKey]
const type = option?.type;
const field = Object.values(FIELDS).find(f => f.type === type)
if (field) {
return field.name
}
return ""
}
const isOptionEnabled = optionKey => {
return true;
// 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
}
const getSupportLevel = (optionKey) => {
const level = fieldSupport[optionKey]?.level;
if (level === validatorConstants.unsupported) {
return {
class: "supportLevelUnsupported",
iconColor: "var(--red)",
icon: "Alert",
iconTooltip: fieldSupport[optionKey]?.message,
text: "Not compatible"
}
}
if (level === validatorConstants.partialSupport) {
return {
class: "supportLevelPartialSupport",
iconColor: "var(--yellow)",
icon: "AlertCheck",
iconTooltip: fieldSupport[optionKey]?.message,
text: "Partially compatible"
}
}
if (level === validatorConstants.supported) {
return {
class: "supportLevelSupported",
iconColor: "var(--green)",
icon: "CheckmarkCircle",
iconTooltip: fieldSupport[optionKey]?.message,
text: "Compatible"
}
}
return {
class: "supportLevelPartialSupport",
iconColor: "var(--yellow)",
icon: "AlertCheck",
iconTooltip: "",
text: "Partially Compatible"
}
}
$: currentOptionSupport = getSupportLevel(currentOption)
$: previousOptionSupport = getSupportLevel(previousOption)
const updateTooltip = debounce((e, option) => {
if (option == null) {
contextTooltipVisible = false;
} else {
contextTooltipAnchor = e?.target;
previousOption = currentOption;
currentOption = option;
contextTooltipVisible = true;
currentOptionSupport = getSupportLevel(currentOption)
previousOptionSupport = getSupportLevel(previousOption)
}
}, 200);
const onOptionMouseenter = (e, option, idx) => {
updateTooltip(e, option);
}
const onOptionMouseleave = (e, option) => {
updateTooltip(e, null);
}
</script>
<Multiselect
iconPosition="right"
{isOptionEnabled}
{placeholder}
value={boundValue}
on:change={setValue}
{options}
align="right"
{onOptionMouseenter}
{onOptionMouseleave}
/>
<ContextTooltip
visible={contextTooltipVisible}
anchor={contextTooltipAnchor}
offset={20}
>
<ChartFieldContext
sidecar
tableHref={`/builder/app/${$params.application}/data/table/${datasource.tableId}`}
schema={schema[currentOption]}
support={fieldSupport[currentOption]}
supportLevelClass={currentOptionSupport.class}
supportLevelIcon={currentOptionSupport.icon}
supportLevelIconColor={currentOptionSupport.iconColor}
supportLevelIconTooltip={currentOptionSupport.iconTooltip}
supportLevelText={currentOptionSupport.text}
columnIcon={getOptionIcon(currentOption)}
columnName={currentOption}
columnType={getOptionIconTooltip(currentOption)}
errors={fieldSupport[currentOption]?.errors}
warnings={fieldSupport[currentOption]?.warnings}
/>
<ChartFieldContext
slot="previous"
support={fieldSupport[previousOption]}
supportLevelClass={previousOptionSupport.class}
supportLevelIcon={previousOptionSupport.icon}
supportLevelIconColor={previousOptionSupport.iconColor}
supportLevelIconTooltip={previousOptionSupport.iconTooltip}
supportLevelText={previousOptionSupport.text}
columnIcon={getOptionIcon(previousOption)}
columnName={previousOption}
columnType={getOptionIconTooltip(previousOption)}
errors={fieldSupport[previousOption]?.errors}
warnings={fieldSupport[previousOption]?.warnings}
/>
</ContextTooltip>
<style>
.tooltipContents {
max-width: 400px;
background-color: var(--spectrum-global-color-gray-200);
display: block;
padding: 0 0 12px 0 ;
border-radius: 5px;
box-sizing: border-box;
}
.tooltipContents.supportLevelUnsupported {
background-color: var(--red);
color: var(--ink)
}
.tooltipContents.supportLevelPartialSupport {
background-color: var(--yellow);
color: var(--ink)
}
.tooltipContents.supportLevelSupported {
background-color: var(--green);
color: var(--ink)
}
.contextTooltipHeader {
background-color: var(--background-alt);
color: var(--ink);
display: flex;
align-items: center;
height: var(--spectrum-alias-item-height-m);
padding: 0px var(--spectrum-alias-item-padding-m);
border-width: var(--spectrum-actionbutton-border-size);
border-radius: var(--spectrum-alias-border-radius-regular);
border: 1px solid
var(
--spectrum-actionbutton-m-border-color,
var(--spectrum-alias-border-color)
);
}
.contextTooltipContent {
padding: 0px 12px;
color: black;
}
.contextTooltipHeader :global(svg) {
margin-right: 5px;
}
.contextTooltipHeader :global(span) {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.supportLevel {
display: flex;
align-items: center;
height: var(--spectrum-alias-item-height-m);
padding: 0px var(--spectrum-alias-item-padding-m);
margin-bottom: 12px;
color: black;
}
.supportLevel :global(svg) {
margin-right: 5px;
}
.supportLevel.supportLevelUnsupported {
background-color: var(--red-light)
}
.supportLevel.supportLevelPartialSupport {
background-color: var(--yellow-light)
}
.supportLevel.supportLevelSupported {
background-color: var(--green-light)
}
</style>