当前位置: 首页 > article >正文

Superset二次开发之源码DependencyList.tsx 分析

功能点

路径

superset-frontend\src\dashboard\components\nativeFilters\FiltersConfigModal\FiltersConfigForm\DependencyList.tsx

/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
import React, { useState } from 'react';
import { styled, t } from '@superset-ui/core';
import Icons from 'src/components/Icons';
import { Select } from 'src/components';
import { CollapsibleControl } from './CollapsibleControl';
import { INPUT_WIDTH } from './constants';
 
interface DependencyListProps {
  availableFilters: {
    label: string;
    value: string;
    type: string | undefined;
  }[];
  dependencies: string[];
  onDependenciesChange: (dependencies: string[]) => void;
  getDependencySuggestion: () => string;
  children?: JSX.Element;
}
 
const MainPanel = styled.div`
  display: flex;
  flex-direction: column;
`;
 
const AddFilter = styled.div`
  ${({ theme }) => `
    display: inline-flex;
    flex-direction: row;
    align-items: center;
    cursor: pointer;
    color: ${theme.colors.primary.base};
    &:hover {
      color: ${theme.colors.primary.dark1};
    }
  `}
`;
 
const DeleteFilter = styled(Icons.Trash)`
  ${({ theme }) => `
    cursor: pointer;
    margin-left: ${theme.gridUnit * 2}px;
    color: ${theme.colors.grayscale.base};
    &:hover {
      color: ${theme.colors.grayscale.dark1};
    }
  `}
`;
 
const RowPanel = styled.div`
  ${({ theme }) => `
    display: flex;
    flex-direction: row;
    align-items: center;
    margin-bottom: ${theme.gridUnit}px;
 
    & > div {
      width: ${INPUT_WIDTH}px;
    }
  `}
`;
 
const Label = styled.div`
  text-transform: uppercase;
  font-size: ${({ theme }) => theme.typography.sizes.s}px;
  color: ${({ theme }) => theme.colors.grayscale.base};
  margin-bottom: ${({ theme }) => theme.gridUnit}px;
`;
 
const Row = ({
  availableFilters,
  selection,
  onChange,
  onDelete,
}: {
  availableFilters: { label: string; value: string }[];
  selection: string;
  onChange: (id: string, value: string) => void;
  onDelete: (id: string) => void;
}) => {
  let value = availableFilters.find(e => e.value === selection);
  let options = availableFilters;
  if (!value) {
    value = { label: t('(deleted or invalid type)'), value: selection };
    options = [value, ...options];
  }
  return (
    <RowPanel>
      <Select
        ariaLabel={t('Limit type')}
        labelInValue
        options={options}
        onChange={option =>
          onChange(selection, (option as { value: string }).value)
        }
        value={value}
      />
      <DeleteFilter iconSize="xl" onClick={() => onDelete(selection)} />
    </RowPanel>
  );
};
 
const List = ({
  availableFilters = [],
  dependencies = [],
  onDependenciesChange,
}: DependencyListProps) => {
  const [rows, setRows] = useState<string[]>(dependencies);
 
  const updateRows = (newRows: string[]) => {
    setRows(newRows);
    onDependenciesChange(newRows);
  };
 
  const onAdd = () => {
    const filter = availableFilters.find(
      availableFilter => !rows.includes(availableFilter.value),
    );
    if (filter) {
      const newRows = [...rows];
      newRows.push(filter.value);
      updateRows(newRows);
    }
  };
 
  const onChange = (id: string, value: string) => {
    const indexOf = rows.findIndex(row => row === id);
    const newRows = [...rows];
    newRows[indexOf] = value;
    updateRows(newRows);
  };
 
  const onDelete = (id: string) => {
    const newRows = [...rows];
    newRows.splice(rows.indexOf(id), 1);
    updateRows(newRows);
  };
 
  if (availableFilters.length === 0) {
    return <span>{t('No available filters.')}</span>;
  }
 
  return (
    <>
      {rows.map(row => (
        <Row
          key={row}
          selection={row}
          availableFilters={availableFilters.filter(
            e => e.value === row || !rows.includes(e.value),
          )}
          onChange={onChange}
          onDelete={onDelete}
        />
      ))}
      {availableFilters.length > rows.length && (
        <AddFilter onClick={onAdd}>
          <Icons.PlusSmall />
          {t('Add filter')}
        </AddFilter>
      )}
    </>
  );
};
 
const DependencyList = ({
  availableFilters = [],
  dependencies = [],
  onDependenciesChange,
  getDependencySuggestion,
  children,
}: DependencyListProps) => {
  const hasAvailableFilters = availableFilters.length > 0;
  const hasDependencies = dependencies.length > 0;
 
  const onCheckChanged = (value: boolean) => {
    const newDependencies: string[] = [];
    if (value && !hasDependencies && hasAvailableFilters) {
      newDependencies.push(getDependencySuggestion());
    }
    onDependenciesChange(newDependencies);
  };
 
  return (
    <MainPanel>
      <CollapsibleControl
        title={t('Values are dependent on other filters')}
        initialValue={hasDependencies}
        onChange={onCheckChanged}
        tooltip={t(
          'Values selected in other filters will affect the filter options to only show relevant values',
        )}
      >
        {hasDependencies && <Label>{t('Values dependent on')}</Label>}
        <List
          availableFilters={availableFilters}
          dependencies={dependencies}
          onDependenciesChange={onDependenciesChange}
          getDependencySuggestion={getDependencySuggestion}
        />
        {children}
      </CollapsibleControl>
    </MainPanel>
  );
};
 
export default DependencyList;

组件功能:

  • 显示当前过滤器所依赖的其他过滤器列表
  • 允许用户添加或删除依赖关系
  • 提供依赖关系的可视化展示

 主要组件结构:

export const DependencyList: React.FC<DependencyListProps> = ({
  dependencies = [],
  onRemove,
  onAdd,
  getFilterTitle,
}) => {
  // ... 组件实现
}

这个组件接收依赖列表、删除和添加依赖的回调函数,以及获取过滤器标题的函数作为props。

依赖项渲染:

{dependencies.map(dependency => (
  <StyledItem key={dependency}>
    <StyledItemContent>
      <FilterValue>{getFilterTitle(dependency)}</FilterValue>
    </StyledItemContent>
    <StyledTrashIcon
      name="trash"
      onClick={() => onRemove(dependency)}
    />
  </StyledItem>
))}

这段代码遍历依赖列表,为每个依赖项渲染一个包含标题和删除图标的项目。

添加依赖功能

<StyledAdd onClick={onAdd}>
<PlusOutlined />
<span>{t('Add Dependent')}</span>
</StyledAdd>

这部分代码渲染了一个"添加依赖"按钮,点击时触发onAdd回调。

样式和布局

文件中使用了多个样式化组件(如StyledItem, StyledItemContent等)来定制组件的外观。

国际化

使用t函数进行文本国际化,支持多语言

类型定义

type DependencyListProps = {

    dependencies?: string[];
    onRemove: (id: string) => void;
    onAdd: () => void;
    getFilterTitle: (id: string) => string;
};

总结

这个DependencyList组件是实现"Values are dependent on other filters"逻辑的重要部分。它提供了一个用户界面,允许配置和管理过滤器之间的依赖关系。通过这个组件,用户可以直观地看到和修改过滤器的依赖结构,从而实现动态且相互关联的过滤系统


http://www.kler.cn/a/305987.html

相关文章:

  • 基于springboot的汽车租赁管理系统的设计与实现
  • Linux源码阅读笔记-V4L2框架基础介绍
  • HP G10服务器ESXI6.7告警提示ramdisk tmp已满
  • 深度学习中的感受野:从基础概念到多层次特征提取
  • 关于GCC内联汇编(也可以叫内嵌汇编)的简单学习
  • redis bind 127.0.0.1和bind 10.34.56.78的区别
  • 828华为云征文 | 云服务器Flexus X实例:部署 Gitea,拥有自己的Git仓库,管理本地代码
  • 微服务之间的安全通信
  • Xorbits Inference(Xinference):一款性能强大且功能全面的大模型部署与分布式推理框架
  • TCP/IP网络模型分层
  • PCL 点云随机渲染颜色
  • 3285、找到稳定山的下标
  • 华为CNA VRM搭建(使用vmware worfstartion搭建)
  • 【Python】开发环境配置
  • Python的Scapy库详解
  • 关于 OceanBase 4.x 中被truncate的 table 不再支持进回收站的原因
  • 聚观早报 | 2025款比亚迪汉上市;iPhone 16天猫全球同步首发
  • GEO数据的下载和处理|GEO数据转换为Gene symbol|GEO注释文件提取symbol|查看样本标签|查看GEO数据疾病或正常|生物信息基础
  • 后端开发刷题 | 矩阵的最小路径和
  • 语言模型中的多模态链式推理(论文复现)
  • CSS—4
  • Go语言现代web开发defer 延迟执行
  • 【Linux 20】进程控制
  • 网络原理 IP协议与以太网协议
  • 匹配行最大值替换为最小值公式
  • 反射是一个新的AI模型,可以在一台性能良好的笔记本上运行并在测试中击败GPT-4o