Bin
2025-12-16 9e0b2ba2c317b1a86212f24cbae3195ad1f3dbfa
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
import { observer } from "mobx-react";
import { getRoot } from "mobx-state-tree";
import { useCallback, useMemo } from "react";
import { cn } from "../../../utils/bem";
import { debounce } from "../../../utils/debounce";
import { FilterDropdown } from "../FilterDropdown";
import * as FilterInputs from "../types";
import { allowedFilterOperations } from "../types/Utility";
import { Common } from "../types/Common";
 
/** @typedef {{
 * type: keyof typeof FilterInputs,
 * width: number
 * }} FieldConfig */
 
/**
 *
 * @param {{field: FieldConfig}} param0
 */
export const FilterOperation = observer(({ filter, field, operator, value, disabled }) => {
  const cellView = filter.cellView;
  const types = cellView?.customOperators ?? [
    ...(FilterInputs[filter.filter.currentType] ?? FilterInputs.String),
    ...Common,
  ];
 
  const selected = useMemo(() => {
    let result;
 
    if (operator) {
      result = types.find((t) => t.key === operator);
    }
 
    if (!result) {
      result = types[0];
    }
 
    filter.setOperator(result.key);
    return result;
  }, [operator, types, filter]);
 
  const saveFilter = useCallback(
    debounce(() => {
      filter.save(true);
    }, 300),
    [filter],
  );
 
  const onChange = (newValue) => {
    filter.setValue(newValue);
    saveFilter();
  };
 
  const onOperatorSelected = (selectedKey) => {
    filter.setOperator(selectedKey);
  };
  const availableOperators = filter.cellView?.filterOperators;
  const Input = selected?.input;
  let operatorList = allowedFilterOperations(types, getRoot(filter)?.SDK?.type);
  if (filter.filter.field.isAnnotationResultsFilterColumn) {
    // We want at most one of "equal" or "contains" per filter type
    // They resolve to the same backend query in this custom case
    const hasEqualOperators = operatorList.some((o) => ["equal", "not_equal"].includes(o.key));
    const allowedOperators = hasEqualOperators ? ["equal", "not_equal"] : ["contains", "not_contains"];
    operatorList = operatorList.filter((op) => allowedOperators.includes(op.key));
  }
  const operators = operatorList.map(({ key, label }) => {
    if (filter.filter.field.isAnnotationResultsFilterColumn) {
      if (filter.schema?.multiple ?? false) {
        if (key === "contains") label = "includes all";
        if (key === "not_contains") label = "does not include all";
      } else {
        if (key === "contains") label = "is";
        if (key === "not_contains") label = "is not";
      }
    }
    return { value: key, label };
  });
  const columnClass = cn("filterLine").elem("column");
 
  return Input ? (
    <>
      <div className={columnClass.mix("operation").toString()}>
        <FilterDropdown
          placeholder="条件"
          value={filter.operator}
          disabled={types.length === 1 || disabled}
          items={availableOperators ? operators.filter((op) => availableOperators.includes(op.value)) : operators}
          onChange={onOperatorSelected}
        />
      </div>
      <div className={columnClass.mix("value").toString()}>
        <Input
          {...field}
          key={`${filter.filter.id}-${filter.filter.currentType}`}
          schema={filter.schema}
          filter={filter}
          multiple={filter.schema?.multiple ?? false}
          value={value}
          onChange={onChange}
          size="small"
          disabled={disabled}
        />
      </div>
    </>
  ) : null;
});