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);
|
||||
}
|
||||
|
||||
.light-mode .objectTimelineShadow .objectTimelineRowLabel,
|
||||
.light-mode .objectTimelineShadow .objectTimelineRowHeaderLabel {
|
||||
box-shadow: -2px -1px 8px 4px rgba(5, 5, 5, 0.06);
|
||||
}
|
||||
|
||||
.objectTimelineRow:hover,
|
||||
.objectTimelineRow:hover .objectTimelineRowLabel {
|
||||
background-color: var(--color-table-header-bg);
|
||||
@ -2822,6 +2827,11 @@ body.objectKanbanColumnResizing * {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.filter-input-quote,
|
||||
[data-quote] {
|
||||
color: color-mix(in srgb, var(--color-text) 40%, transparent);
|
||||
}
|
||||
|
||||
.filter-input-operand-tag {
|
||||
margin-inline-end: 0;
|
||||
margin-right: 0;
|
||||
|
||||
@ -33,6 +33,7 @@ const PROPERTY_FILTER_PANEL_MAX_HEIGHT = 220
|
||||
// Longer symbols first so ".." / "<>" / ">=" match before "." / "<" / ">".
|
||||
// Wildcards (* ?) are highlighted in place; @ stays as plain text — none are operands.
|
||||
const WILDCARDS = new Set(['*', '?'])
|
||||
const QUOTE = "'"
|
||||
|
||||
const OPERANDS = [
|
||||
{
|
||||
@ -138,6 +139,11 @@ const tokenize = (raw = '') => {
|
||||
text = ''
|
||||
tokens.push({ type: 'wildcard', value: value[i] })
|
||||
i += 1
|
||||
} else if (value[i] === QUOTE) {
|
||||
pushText(text)
|
||||
text = ''
|
||||
tokens.push({ type: 'quote', value: QUOTE })
|
||||
i += 1
|
||||
} else {
|
||||
text += value[i]
|
||||
i += 1
|
||||
@ -164,6 +170,14 @@ const renderDateUnitNode = (value) => {
|
||||
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 = '') =>
|
||||
tokenize(raw)
|
||||
.map((token) => token.value)
|
||||
@ -414,7 +428,8 @@ const domMatchesTokens = (root, raw) => {
|
||||
return (
|
||||
node.getAttribute?.('data-operand') != 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
|
||||
)
|
||||
}
|
||||
if (token.type === 'quote') {
|
||||
return (
|
||||
child.getAttribute?.('data-quote') === token.value &&
|
||||
(child.textContent ?? '') === token.value
|
||||
)
|
||||
}
|
||||
return child.getAttribute?.('data-operand') === token.value
|
||||
})
|
||||
}
|
||||
@ -645,6 +666,8 @@ const FilterInput = ({
|
||||
nextChildren.push(renderWildcardNode(item.value))
|
||||
} else if (item.type === 'dateUnit') {
|
||||
nextChildren.push(renderDateUnitNode(item.value))
|
||||
} else if (item.type === 'quote') {
|
||||
nextChildren.push(renderQuoteNode())
|
||||
} else {
|
||||
nextChildren.push(takeChip(item.value))
|
||||
}
|
||||
|
||||
@ -58,15 +58,13 @@ const ObjectTimelineItem = ({
|
||||
minWidth: position.isPoint ? undefined : 28,
|
||||
maxWidth: `calc(${100 - position.left}% - 4px)`,
|
||||
height: 30,
|
||||
padding: '0 10px',
|
||||
padding: '0 11px',
|
||||
overflow: 'visible',
|
||||
whiteSpace: 'nowrap',
|
||||
flex: '0 0 auto',
|
||||
borderRadius: 8,
|
||||
color: 'white',
|
||||
background: color,
|
||||
boxShadow:
|
||||
'0 1px 3px color-mix(in srgb, var(--color-primary) 35%, transparent)'
|
||||
background: color
|
||||
}}
|
||||
>
|
||||
<div
|
||||
|
||||
@ -120,7 +120,9 @@ const buildExpression = (operator, rawValue, property) => {
|
||||
case 'isFalse':
|
||||
return 'false'
|
||||
case 'isEmpty':
|
||||
return ''
|
||||
return "''"
|
||||
case 'isNotEmpty':
|
||||
return "<>''"
|
||||
default:
|
||||
return value
|
||||
}
|
||||
@ -158,7 +160,10 @@ const getFilterOptions = (category) => {
|
||||
case 'boolean':
|
||||
return [
|
||||
{ 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':
|
||||
return [
|
||||
@ -178,7 +183,10 @@ const getFilterOptions = (category) => {
|
||||
needsValue: true
|
||||
},
|
||||
{ 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':
|
||||
return [
|
||||
@ -188,6 +196,9 @@ const getFilterOptions = (category) => {
|
||||
{ key: 'greaterThan', label: 'After', needsValue: true },
|
||||
{ key: 'between', label: 'Between', needsRange: true },
|
||||
{ type: 'divider' },
|
||||
{ key: 'isEmpty', label: 'Is empty' },
|
||||
{ key: 'isNotEmpty', label: 'Is not empty' },
|
||||
{ type: 'divider' },
|
||||
{ key: 'yesterday', label: 'Yesterday', expression: '-1D' },
|
||||
{ key: 'today', label: 'Today', expression: 'TODAY' },
|
||||
{ key: 'tomorrow', label: 'Tomorrow', expression: '+1D' },
|
||||
@ -298,7 +309,10 @@ const getFilterOptions = (category) => {
|
||||
case 'object':
|
||||
return [
|
||||
{ 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':
|
||||
default:
|
||||
@ -319,7 +333,8 @@ const getFilterOptions = (category) => {
|
||||
{ key: 'equals', label: 'Equals', needsValue: true },
|
||||
{ key: 'notEquals', label: 'Does not equal', needsValue: true },
|
||||
{ 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)
|
||||
return
|
||||
}
|
||||
if (option.key === 'isEmpty') {
|
||||
applyFilter('isEmpty')
|
||||
if (option.key === 'isEmpty' || option.key === 'isNotEmpty') {
|
||||
applyFilter(option.key)
|
||||
return
|
||||
}
|
||||
setInputValue(null)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user