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

功能点

|----------------------------------------------------------------------------|
| |

路径

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

TypeScript 复制代码
/**
 * 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;

组件功能:

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

主要组件结构:

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

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

依赖项渲染:

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

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

添加依赖功能

TypeScript 复制代码
<StyledAdd onClick={onAdd}>
<PlusOutlined />
<span>{t('Add Dependent')}</span>
</StyledAdd>

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

样式和布局

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

国际化

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

类型定义

TypeScript 复制代码
type DependencyListProps = {

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

总结

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

相关推荐
高山我梦口香糖2 分钟前
[react] <NavLink>自带激活属性
前端·javascript·react.js
撸码到无法自拔6 分钟前
React:组件、状态与事件处理的完整指南
前端·javascript·react.js·前端框架·ecmascript
高山我梦口香糖7 分钟前
[react]不能将类型“string | undefined”分配给类型“To”。 不能将类型“undefined”分配给类型“To”
前端·javascript·react.js
代码cv移动工程师10 分钟前
HTML语法规范
前端·html
Elena_Lucky_baby32 分钟前
实现路由懒加载的方式有哪些?
前端·javascript·vue.js
Domain-zhuo33 分钟前
如何利用webpack来优化前端性能?
前端·webpack·前端框架·node.js·ecmascript
理想不理想v37 分钟前
webpack如何自定义插件?示例
前端·webpack·node.js
小华同学ai1 小时前
ShowDoc:Star12.3k,福利项目,个人小团队的在线文档“简单、易用、轻量化”还专门针对API文档、技术文档做了优化
前端·程序员·github
王解1 小时前
Vue CLI 脚手架创建项目流程详解 (2)
前端·javascript·vue.js
刘大浪1 小时前
vue.js滑动到顶便锁定位置
前端·javascript·vue.js