Enhance FilterInput and QuickPropertyFilters with Quote Support and Improved Options
All checks were successful
farmcontrol/farmcontrol-ui/pipeline/head This commit looks good
All checks were successful
farmcontrol/farmcontrol-ui/pipeline/head This commit looks good
- Added support for quote tokens in the FilterInput component, allowing for better expression handling. - Updated QuickPropertyFilters to include new filter options for 'isEmpty' and 'isNotEmpty', enhancing filtering capabilities. - Improved CSS styles for quote elements to ensure consistent appearance in light mode. - Adjusted padding in ObjectTimelineItem for better layout consistency.
This commit is contained in:
parent
8438757db5
commit
56479ea358
@ -1780,6 +1780,11 @@ body.objectKanbanColumnResizing * {
|
|||||||
box-shadow: -2px -1px 8px 4px rgba(253, 253, 253, 0.12);
|
box-shadow: -2px -1px 8px 4px rgba(253, 253, 253, 0.12);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.light-mode .objectTimelineShadow .objectTimelineRowLabel,
|
||||||
|
.light-mode .objectTimelineShadow .objectTimelineRowHeaderLabel {
|
||||||
|
box-shadow: -2px -1px 8px 4px rgba(5, 5, 5, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
.objectTimelineRow:hover,
|
.objectTimelineRow:hover,
|
||||||
.objectTimelineRow:hover .objectTimelineRowLabel {
|
.objectTimelineRow:hover .objectTimelineRowLabel {
|
||||||
background-color: var(--color-table-header-bg);
|
background-color: var(--color-table-header-bg);
|
||||||
@ -2822,6 +2827,11 @@ body.objectKanbanColumnResizing * {
|
|||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.filter-input-quote,
|
||||||
|
[data-quote] {
|
||||||
|
color: color-mix(in srgb, var(--color-text) 40%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
.filter-input-operand-tag {
|
.filter-input-operand-tag {
|
||||||
margin-inline-end: 0;
|
margin-inline-end: 0;
|
||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
|
|||||||
@ -33,6 +33,7 @@ const PROPERTY_FILTER_PANEL_MAX_HEIGHT = 220
|
|||||||
// Longer symbols first so ".." / "<>" / ">=" match before "." / "<" / ">".
|
// Longer symbols first so ".." / "<>" / ">=" match before "." / "<" / ">".
|
||||||
// Wildcards (* ?) are highlighted in place; @ stays as plain text — none are operands.
|
// Wildcards (* ?) are highlighted in place; @ stays as plain text — none are operands.
|
||||||
const WILDCARDS = new Set(['*', '?'])
|
const WILDCARDS = new Set(['*', '?'])
|
||||||
|
const QUOTE = "'"
|
||||||
|
|
||||||
const OPERANDS = [
|
const OPERANDS = [
|
||||||
{
|
{
|
||||||
@ -138,6 +139,11 @@ const tokenize = (raw = '') => {
|
|||||||
text = ''
|
text = ''
|
||||||
tokens.push({ type: 'wildcard', value: value[i] })
|
tokens.push({ type: 'wildcard', value: value[i] })
|
||||||
i += 1
|
i += 1
|
||||||
|
} else if (value[i] === QUOTE) {
|
||||||
|
pushText(text)
|
||||||
|
text = ''
|
||||||
|
tokens.push({ type: 'quote', value: QUOTE })
|
||||||
|
i += 1
|
||||||
} else {
|
} else {
|
||||||
text += value[i]
|
text += value[i]
|
||||||
i += 1
|
i += 1
|
||||||
@ -164,6 +170,14 @@ const renderDateUnitNode = (value) => {
|
|||||||
return span
|
return span
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const renderQuoteNode = () => {
|
||||||
|
const span = document.createElement('span')
|
||||||
|
span.setAttribute('data-quote', QUOTE)
|
||||||
|
span.className = 'filter-input-quote'
|
||||||
|
span.textContent = QUOTE
|
||||||
|
return span
|
||||||
|
}
|
||||||
|
|
||||||
const normalize = (raw = '') =>
|
const normalize = (raw = '') =>
|
||||||
tokenize(raw)
|
tokenize(raw)
|
||||||
.map((token) => token.value)
|
.map((token) => token.value)
|
||||||
@ -414,7 +428,8 @@ const domMatchesTokens = (root, raw) => {
|
|||||||
return (
|
return (
|
||||||
node.getAttribute?.('data-operand') != null ||
|
node.getAttribute?.('data-operand') != null ||
|
||||||
node.getAttribute?.('data-wildcard') != null ||
|
node.getAttribute?.('data-wildcard') != null ||
|
||||||
node.getAttribute?.('data-date-unit') != null
|
node.getAttribute?.('data-date-unit') != null ||
|
||||||
|
node.getAttribute?.('data-quote') != null
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -449,6 +464,12 @@ const domMatchesTokens = (root, raw) => {
|
|||||||
(child.textContent ?? '') === token.value
|
(child.textContent ?? '') === token.value
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
if (token.type === 'quote') {
|
||||||
|
return (
|
||||||
|
child.getAttribute?.('data-quote') === token.value &&
|
||||||
|
(child.textContent ?? '') === token.value
|
||||||
|
)
|
||||||
|
}
|
||||||
return child.getAttribute?.('data-operand') === token.value
|
return child.getAttribute?.('data-operand') === token.value
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -645,6 +666,8 @@ const FilterInput = ({
|
|||||||
nextChildren.push(renderWildcardNode(item.value))
|
nextChildren.push(renderWildcardNode(item.value))
|
||||||
} else if (item.type === 'dateUnit') {
|
} else if (item.type === 'dateUnit') {
|
||||||
nextChildren.push(renderDateUnitNode(item.value))
|
nextChildren.push(renderDateUnitNode(item.value))
|
||||||
|
} else if (item.type === 'quote') {
|
||||||
|
nextChildren.push(renderQuoteNode())
|
||||||
} else {
|
} else {
|
||||||
nextChildren.push(takeChip(item.value))
|
nextChildren.push(takeChip(item.value))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -58,15 +58,13 @@ const ObjectTimelineItem = ({
|
|||||||
minWidth: position.isPoint ? undefined : 28,
|
minWidth: position.isPoint ? undefined : 28,
|
||||||
maxWidth: `calc(${100 - position.left}% - 4px)`,
|
maxWidth: `calc(${100 - position.left}% - 4px)`,
|
||||||
height: 30,
|
height: 30,
|
||||||
padding: '0 10px',
|
padding: '0 11px',
|
||||||
overflow: 'visible',
|
overflow: 'visible',
|
||||||
whiteSpace: 'nowrap',
|
whiteSpace: 'nowrap',
|
||||||
flex: '0 0 auto',
|
flex: '0 0 auto',
|
||||||
borderRadius: 8,
|
borderRadius: 8,
|
||||||
color: 'white',
|
color: 'white',
|
||||||
background: color,
|
background: color
|
||||||
boxShadow:
|
|
||||||
'0 1px 3px color-mix(in srgb, var(--color-primary) 35%, transparent)'
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
|
|||||||
@ -120,7 +120,9 @@ const buildExpression = (operator, rawValue, property) => {
|
|||||||
case 'isFalse':
|
case 'isFalse':
|
||||||
return 'false'
|
return 'false'
|
||||||
case 'isEmpty':
|
case 'isEmpty':
|
||||||
return ''
|
return "''"
|
||||||
|
case 'isNotEmpty':
|
||||||
|
return "<>''"
|
||||||
default:
|
default:
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
@ -158,7 +160,10 @@ const getFilterOptions = (category) => {
|
|||||||
case 'boolean':
|
case 'boolean':
|
||||||
return [
|
return [
|
||||||
{ key: 'isTrue', label: 'Is true' },
|
{ key: 'isTrue', label: 'Is true' },
|
||||||
{ key: 'isFalse', label: 'Is false' }
|
{ key: 'isFalse', label: 'Is false' },
|
||||||
|
{ type: 'divider' },
|
||||||
|
{ key: 'isEmpty', label: 'Is empty' },
|
||||||
|
{ key: 'isNotEmpty', label: 'Is not empty' }
|
||||||
]
|
]
|
||||||
case 'number':
|
case 'number':
|
||||||
return [
|
return [
|
||||||
@ -178,7 +183,10 @@ const getFilterOptions = (category) => {
|
|||||||
needsValue: true
|
needsValue: true
|
||||||
},
|
},
|
||||||
{ type: 'divider' },
|
{ type: 'divider' },
|
||||||
{ key: 'between', label: 'Between', needsRange: true }
|
{ key: 'between', label: 'Between', needsRange: true },
|
||||||
|
{ type: 'divider' },
|
||||||
|
{ key: 'isEmpty', label: 'Is empty' },
|
||||||
|
{ key: 'isNotEmpty', label: 'Is not empty' }
|
||||||
]
|
]
|
||||||
case 'date':
|
case 'date':
|
||||||
return [
|
return [
|
||||||
@ -188,6 +196,9 @@ const getFilterOptions = (category) => {
|
|||||||
{ key: 'greaterThan', label: 'After', needsValue: true },
|
{ key: 'greaterThan', label: 'After', needsValue: true },
|
||||||
{ key: 'between', label: 'Between', needsRange: true },
|
{ key: 'between', label: 'Between', needsRange: true },
|
||||||
{ type: 'divider' },
|
{ type: 'divider' },
|
||||||
|
{ key: 'isEmpty', label: 'Is empty' },
|
||||||
|
{ key: 'isNotEmpty', label: 'Is not empty' },
|
||||||
|
{ type: 'divider' },
|
||||||
{ key: 'yesterday', label: 'Yesterday', expression: '-1D' },
|
{ key: 'yesterday', label: 'Yesterday', expression: '-1D' },
|
||||||
{ key: 'today', label: 'Today', expression: 'TODAY' },
|
{ key: 'today', label: 'Today', expression: 'TODAY' },
|
||||||
{ key: 'tomorrow', label: 'Tomorrow', expression: '+1D' },
|
{ key: 'tomorrow', label: 'Tomorrow', expression: '+1D' },
|
||||||
@ -298,7 +309,10 @@ const getFilterOptions = (category) => {
|
|||||||
case 'object':
|
case 'object':
|
||||||
return [
|
return [
|
||||||
{ key: 'equals', label: 'Equals', needsValue: true },
|
{ key: 'equals', label: 'Equals', needsValue: true },
|
||||||
{ key: 'notEquals', label: 'Does not equal', needsValue: true }
|
{ key: 'notEquals', label: 'Does not equal', needsValue: true },
|
||||||
|
{ type: 'divider' },
|
||||||
|
{ key: 'isEmpty', label: 'Is empty' },
|
||||||
|
{ key: 'isNotEmpty', label: 'Is not empty' }
|
||||||
]
|
]
|
||||||
case 'text':
|
case 'text':
|
||||||
default:
|
default:
|
||||||
@ -319,7 +333,8 @@ const getFilterOptions = (category) => {
|
|||||||
{ key: 'equals', label: 'Equals', needsValue: true },
|
{ key: 'equals', label: 'Equals', needsValue: true },
|
||||||
{ key: 'notEquals', label: 'Does not equal', needsValue: true },
|
{ key: 'notEquals', label: 'Does not equal', needsValue: true },
|
||||||
{ type: 'divider' },
|
{ type: 'divider' },
|
||||||
{ key: 'isEmpty', label: 'Is empty' }
|
{ key: 'isEmpty', label: 'Is empty' },
|
||||||
|
{ key: 'isNotEmpty', label: 'Is not empty' }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -396,8 +411,8 @@ const QuickPropertyFilters = ({
|
|||||||
applyFilter(option.key)
|
applyFilter(option.key)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (option.key === 'isEmpty') {
|
if (option.key === 'isEmpty' || option.key === 'isNotEmpty') {
|
||||||
applyFilter('isEmpty')
|
applyFilter(option.key)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
setInputValue(null)
|
setInputValue(null)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user