lodash#zipObject TypeScript Examples

The following examples show how to use lodash#zipObject. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: download-ui5-resources.ts    From ui5-language-assistant with Apache License 2.0 6 votes vote down vote up
export async function addUi5Resources(
  version: TestModelVersion,
  folder: string
): Promise<void> {
  // CDN libraries (example URL):
  // https://sapui5-sapui5.dispatcher.us1.hana.ondemand.com/test-resources/sap/m/designtime/api.json
  // Older versions:
  // https://sapui5.hana.ondemand.com/1.71.14/test-resources/sap/m/designtime/api.json
  const baseUrl = `https://sapui5.hana.ondemand.com/${version}/test-resources/`;
  const libs = await getLibs(version);
  const nameToFile = zipObject(
    libs,
    map(libs, (_) => `${baseUrl}${_.replace(/\./g, "/")}/designtime/api.json`)
  );

  await mkdirs(folder);

  // Write files in parallel
  await Promise.all(
    map(nameToFile, (url, name) => {
      return writeUrlToFile(
        url,
        resolve(folder, `${name}.designtime.api.json`)
      );
    })
  );
}
Example #2
Source File: utils.ts    From prism-frontend with MIT License 6 votes vote down vote up
convertToTableData = (result: ExposedPopulationResult) => {
  const {
    key,
    groupBy,
    statistic,
    featureCollection: { features },
  } = result;

  const fields = uniq(features.map(f => f.properties && f.properties[key]));

  const featureProperties = features.map(feature => {
    return {
      [groupBy]: feature.properties?.[groupBy],
      [key]: feature.properties?.[key],
      [statistic]: feature.properties?.[statistic],
    };
  });

  const rowData = mapValues(_groupBy(featureProperties, groupBy), k => {
    return mapValues(_groupBy(k, key), v =>
      parseInt(
        v.map(x => x[statistic]).reduce((acc, value) => acc + value),
        10,
      ),
    );
  });

  const groupedRowData = Object.keys(rowData).map(k => {
    return {
      [groupBy]: k,
      ...rowData[k],
    };
  });

  const groupedRowDataWithAllLabels = groupedRowData.map(row => {
    const labelsWithoutValue = difference(fields, keysIn(row));
    const extras = labelsWithoutValue.map(k => ({ [k]: 0 }));
    return extras.length !== 0 ? assign(row, ...extras) : row;
  });

  const headlessRows = groupedRowDataWithAllLabels.map(row => {
    // TODO - Switch between MAX and SUM depending on the polygon source.
    // Then re-add "Total" to the list of columns
    // const total = fields.map(f => row[f]).reduce((a, b) => a + b);
    // return assign(row, { Total: total });
    return row;
  });

  const columns = [groupBy, ...fields]; // 'Total'
  const headRow = zipObject(columns, columns);
  const rows = [headRow, ...headlessRows];
  return { columns, rows };
}
Example #3
Source File: index.tsx    From reddium with MIT License 5 votes vote down vote up
Dropdown = ({
  id,
  dataObj,
  paramKey,
  paramVal,
  updateParams
}: DropdownProps) => {
  const [showDropdown, setShowDropdown] = useState(false);
  const dropdown = useRef<HTMLDivElement>(null);

  useEffect(() => {
    if (!showDropdown) return;
    function handleClick(e: any) {
      if (dropdown.current && !dropdown.current.contains(e.target)) {
        setShowDropdown(false);
      }
    }
    window.addEventListener("click", handleClick);
    return () => window.removeEventListener("click", handleClick);
  });

  return (
    <div className="w-full main-black">
      <button
        className="main-border mt-4 px-5 py-2 flex justify-between items-center w-48 max-w-full"
        onClick={() => setShowDropdown(!showDropdown)}
      >
        <div className="w-full flex flex-row justify-between items-center">
          <div>{dataObj[id]}</div>
          <div>
            <img className="ml-3" src="/down_arrow.svg" />
          </div>{" "}
        </div>
      </button>
      {showDropdown ? (
        <div
          className="dropdown-select w-48 max-w-full absolute z-10"
          ref={dropdown}
        >
          {zipObject(paramKey, paramVal)[id].map((value: string, ind) => (
            <div
              className="my-1 px-4 mx-1 p-2 cursor-pointer"
              key={ind}
              onClick={() => {
                updateParams({ ...dataObj, [id]: value });
                setShowDropdown(false);
              }}
            >
              {value}
            </div>
          ))}
        </div>
      ) : (
        <div></div>
      )}
    </div>
  );
}
Example #4
Source File: [[...params]].tsx    From reddium with MIT License 5 votes vote down vote up
PostPage = ({ post, comments, params, commentId }: any) => {
  // const [moreIndex, setMoreIndex] = useState(0);
  // const [commentList, setCommentList] = useState(comments);
  const [selectedParams, setSelectedParams] = useState({
    ...zipObject(COMMENT_PARAM_KEY, COMMENT_PARAM_DEFAULT),
    ...params
  });
  const filterPopular = () => {
    window.location.href = `/r/${params.subreddit}/comments/${params.postid}?sort=${selectedParams[SORT_PARAM]}`;
  };
  const returnToPost = () =>
    (window.location.href = `/r/${params.subreddit}/comments/${params.postid}`);

  useEffect(() => {
    if (typeof window !== 'undefined') {
      H.track(
        "Post", 
        {subredditName: params.subreddit, nsfw: post.over_18.toString()}
      );
    }
  });
  // const fetchMoreComments = async () => {
  //   const moreObject = comments[comments.length - 1];
  //   const moreComments = await getMoreCommentsClient({
  //     link_id: moreObject.parent_id,
  //     children: moreObject.children.slice(
  //       moreIndex,
  //       Math.min(moreIndex + 30, moreObject.children.length)
  //     )
  //   });
  //   if (moreIndex + 30 >= moreObject.children.length) {
  //     setMoreIndex(-1);
  //   } else {
  //     setMoreIndex(moreIndex + 30);
  //   }
  //   // setCommentList([...commentList, ...moreComments]);
  // };
  return (
    <PostLayout
      title={`${post.title} | ${params.subreddit}`}
      permalink={post.permalink}
      thumbnail={post.thumbnail}
      token={params.token}
    >
      <PostHeader {...params} />
      <section>
        <PostContent {...post} token={selectedParams.token} />
      </section>
      <section className="w-full mx-auto max-w-600 pb-10">
        <div className="sub-bottom-border mb-4 pt-4"></div>
        <div className="flex justify-start mb-8">
          <div className="max-width-filter flex w-full px-4">
            <Dropdown
              key={SORT_PARAM}
              id={SORT_PARAM}
              paramKey={COMMENT_PARAM_KEY}
              paramVal={COMMENT_PARAM_VALUES}
              dataObj={selectedParams}
              updateParams={setSelectedParams}
            />
            <button
              className="ml-2 my-4 p-2 cursor-pointer w-48 max-w-full btn-black text-white rounded"
              onClick={filterPopular}
            >
              Filter
            </button>
          </div>
        </div>
        <PostComments
          comments={comments}
          backToPost={commentId == "" ? "" : returnToPost}
          token={selectedParams.token}
        />
      </section>
    </PostLayout>
  );
}
Example #5
Source File: AuthorizedSearchEngine.ts    From backstage with Apache License 2.0 4 votes vote down vote up
async query(
    query: SearchQuery,
    options: QueryRequestOptions,
  ): Promise<IndexableResultSet> {
    const queryStartTime = Date.now();

    const conditionFetcher = new DataLoader(
      (requests: readonly QueryPermissionRequest[]) =>
        this.permissions.authorizeConditional(requests.slice(), options),
      {
        cacheKeyFn: ({ permission: { name } }) => name,
      },
    );

    const authorizer = new DataLoader(
      (requests: readonly AuthorizePermissionRequest[]) =>
        this.permissions.authorize(requests.slice(), options),
      {
        // Serialize the permission name and resourceRef as
        // a query string to avoid collisions from overlapping
        // permission names and resourceRefs.
        cacheKeyFn: ({ permission: { name }, resourceRef }) =>
          qs.stringify({ name, resourceRef }),
      },
    );

    const requestedTypes = query.types || Object.keys(this.types);

    const typeDecisions = zipObject(
      requestedTypes,
      await Promise.all(
        requestedTypes.map(type => {
          const permission = this.types[type]?.visibilityPermission;

          // No permission configured for this document type - always allow.
          if (!permission) {
            return { result: AuthorizeResult.ALLOW as const };
          }

          // Resource permission supplied, so we need to check for conditional decisions.
          if (isResourcePermission(permission)) {
            return conditionFetcher.load({ permission });
          }

          // Non-resource permission supplied - we can perform a standard authorization.
          return authorizer.load({ permission });
        }),
      ),
    );

    const authorizedTypes = requestedTypes.filter(
      type => typeDecisions[type]?.result !== AuthorizeResult.DENY,
    );

    const resultByResultFilteringRequired = authorizedTypes.some(
      type => typeDecisions[type]?.result === AuthorizeResult.CONDITIONAL,
    );

    // When there are no CONDITIONAL decisions for any of the requested
    // result types, we can skip filtering result by result by simply
    // skipping the types the user is not permitted to see, which will
    // be much more efficient.
    //
    // Since it's not currently possible to configure the page size used
    // by search engines, this detail means that a single user might see
    // a different page size depending on whether their search required
    // result-by-result filtering or not. We can fix this minor
    // inconsistency by introducing a configurable page size.
    //
    // cf. https://github.com/backstage/backstage/issues/9162
    if (!resultByResultFilteringRequired) {
      return this.searchEngine.query(
        { ...query, types: authorizedTypes },
        options,
      );
    }

    const { page } = decodePageCursor(query.pageCursor);
    const targetResults = (page + 1) * this.pageSize;

    let filteredResults: IndexableResult[] = [];
    let nextPageCursor: string | undefined;
    let latencyBudgetExhausted = false;

    do {
      const nextPage = await this.searchEngine.query(
        { ...query, types: authorizedTypes, pageCursor: nextPageCursor },
        options,
      );

      filteredResults = filteredResults.concat(
        await this.filterResults(nextPage.results, typeDecisions, authorizer),
      );

      nextPageCursor = nextPage.nextPageCursor;
      latencyBudgetExhausted =
        Date.now() - queryStartTime > this.queryLatencyBudgetMs;
    } while (
      nextPageCursor &&
      filteredResults.length < targetResults &&
      !latencyBudgetExhausted
    );

    return {
      results: filteredResults.slice(
        page * this.pageSize,
        (page + 1) * this.pageSize,
      ),
      previousPageCursor:
        page === 0 ? undefined : encodePageCursor({ page: page - 1 }),
      nextPageCursor:
        !latencyBudgetExhausted &&
        (nextPageCursor || filteredResults.length > targetResults)
          ? encodePageCursor({ page: page + 1 })
          : undefined,
    };
  }
Example #6
Source File: UserOrUserGroupSelect.tsx    From next-basics with GNU General Public License v3.0 4 votes vote down vote up
export function LegacyUserSelectFormItem(
  props: UserSelectFormItemProps,
  ref: React.Ref<HTMLDivElement>
): React.ReactElement {
  const selectRef = useRef();
  const [selectedValue, setSelectedValue] = useState([]);
  const staticValue = useRef([]);
  const userShowKey: string[] = getInstanceNameKeys(props.objectMap["USER"]);
  const userGroupShowKey: string[] = getInstanceNameKeys(
    props.objectMap["USER_GROUP"]
  );

  const { t } = useTranslation(NS_FORMS);

  const getLabel = (
    objectId: "USER" | "USER_GROUP",
    instanceData: any
  ): string => {
    const showKey = objectId === "USER" ? userShowKey : userGroupShowKey;
    if (Array.isArray(showKey)) {
      const showName = showKey
        .map((key, index) => {
          if (index === 0) {
            return instanceData[key];
          } else {
            return instanceData[key] ? "(" + instanceData[key] + ")" : "";
          }
        })
        .join("");
      return showName;
    } else {
      return instanceData[showKey];
    }
  };

  const getStaticLabel = (label: string) => (
    <div style={{ color: "var(--bg-color-button-link)" }}>{label}</div>
  );

  const isDifferent = () => {
    const userOfValues = props.value?.selectedUser || [];
    const userGroupOfValues = props.value?.selectedUserGroup || [];
    const userOfSelectedValue = map(
      filter(selectedValue, (item) => !item.key.startsWith(":")),
      "key"
    );

    const userGroupOfSelectedValue = map(
      filter(selectedValue, (item) => item.key.startsWith(":")),
      "key"
    );

    return (
      !isEqual([...userOfValues].sort(), [...userOfSelectedValue].sort()) ||
      !isEqual(
        [...userGroupOfValues].sort(),
        [...userGroupOfSelectedValue].sort()
      )
    );
  };

  const initializeStaticList = () => {
    return groupBy(props.staticList, (v) =>
      startsWith(v, ":") ? "userGroup" : "user"
    );
  };

  // 后台搜索中
  const [fetching, setFetching] = useState(false);

  const [searchValue, setSearchValue] = useState();
  const [userList, setUserList] = useState([]);
  const [userGroupList, setUserGroupList] = useState([]);

  const [modalVisible, setModalVisible] = useState(false);
  const [modalObjectId, setModalObjectId] = useState(
    props.optionsMode === "group" ? "USER_GROUP" : "USER"
  );

  const triggerChange = (changedValue: any) => {
    props.onChange?.(
      isEmpty(changedValue.selectedUser) &&
        isEmpty(changedValue.selectedUserGroup)
        ? null
        : changedValue
    );
  };

  useEffect(() => {
    const initializeSelectedValue = async () => {
      if (props.value) {
        let selectedUser: any[] = [];
        let selectedUserGroup: any[] = [];
        const staticKeys = initializeStaticList();
        const user = compact(
          uniq([].concat(staticKeys.user).concat(props.value.selectedUser))
        );

        const userGroup = compact(
          uniq(
            []
              .concat(staticKeys.userGroup)
              .concat(props.value.selectedUserGroup)
          )
        );

        if (
          (staticKeys.user &&
            some(
              staticKeys.user,
              (v) => !props.value?.selectedUser?.includes(v)
            )) ||
          (staticKeys.userGroup &&
            some(
              staticKeys.userGroup,
              (v) => !props.value?.selectedUserGroup?.includes(v)
            ))
        ) {
          triggerChange({
            selectedUser: user,
            selectedUserGroup: userGroup,
          });
        }
        const staticValueToSet = [];
        if (user.length && props.optionsMode !== "group") {
          selectedUser = (
            await InstanceApi_postSearch("USER", {
              query: {
                name: {
                  $in: user,
                },
              },

              page: 1,
              page_size: user.length,
              fields: {
                ...zipObject(
                  userShowKey,
                  map(userShowKey, (v) => true)
                ),

                name: true,
              },
            })
          ).list;
        }
        if (userGroup.length && props.optionsMode !== "user") {
          selectedUserGroup = (
            await InstanceApi_postSearch("USER_GROUP", {
              query: {
                instanceId: {
                  // 默认带为":"+instanceId,这里查询的时候去掉前面的冒号
                  $in: map(userGroup, (v) => v.slice(1)),
                },
              },

              page: 1,
              page_size: userGroup.length,
              fields: {
                ...zipObject(
                  userGroupShowKey,
                  map(userGroupShowKey, (v) => true)
                ),

                name: true,
              },
            })
          ).list;
        }
        let labelValue = [
          ...map(selectedUser, (v) => {
            const labelText = getLabel("USER", v);
            const result = {
              key: v.name,
              label: props.staticList?.includes(v.name)
                ? getStaticLabel(labelText)
                : labelText,
            };

            if (props.staticList?.includes(v.name)) {
              staticValueToSet.push(result);
            }
            return result;
          }),
          ...map(selectedUserGroup, (v) => {
            const labelText = getLabel("USER_GROUP", v);
            const result = {
              key: ":" + v.instanceId,
              label: props.staticList?.includes(":" + v.instanceId)
                ? getStaticLabel(labelText)
                : labelText,
            };

            if (props.staticList?.includes(":" + v.instanceId)) {
              staticValueToSet.push(result);
            }
            return result;
          }),
        ];

        labelValue = [
          ...staticValueToSet,
          ...filter(labelValue, (v) => !props.staticList?.includes(v.key)),
        ];

        setSelectedValue(labelValue);
        staticValue.current = staticValueToSet;
      }
    };
    if (isDifferent()) {
      initializeSelectedValue();
    }
  }, [props.value]);

  const fetchInstanceList = async (
    objectId: "USER" | "USER_GROUP",
    keyword: string
  ) => {
    const showKey = objectId === "USER" ? userShowKey : userGroupShowKey;
    const showKeyQuery = {
      $or: map(uniq([...showKey, "name"]), (v) => ({
        [v]: { $like: `%${keyword}%` },
      })),
      ...(props.hideInvalidUser
        ? {
            state: "valid",
          }
        : {}),
    };
    return (
      await InstanceApi_postSearch(objectId, {
        page: 1,
        page_size: 20,
        fields: {
          ...zipObject(
            showKey,
            map(showKey, (v) => true)
          ),

          name: true,
        },
        query:
          props.userQuery && objectId === "USER"
            ? {
                ...props.userQuery,
                ...showKeyQuery,
              }
            : props.userGroupQuery && objectId === "USER_GROUP"
            ? {
                ...props.userGroupQuery,
                ...showKeyQuery,
              }
            : props.query || showKeyQuery
            ? {
                ...props.query,
                ...showKeyQuery,
              }
            : showKeyQuery,
      })
    ).list;
  };

  const searchUser = async (value: string) => {
    setUserList(await fetchInstanceList("USER", value));
  };

  // 用户组在instanceId前面加上:
  const searchUserGroup = async (value: string) => {
    const result = await fetchInstanceList("USER_GROUP", value);
    setUserGroupList(
      result.map((v) => {
        v.instanceId = ":" + v.instanceId;
        return v;
      })
    );
  };

  const searchUserOrUserGroupInstances = async (value) => {
    setSearchValue(value);
    setFetching(true);
    await Promise.all([
      ...(props.optionsMode !== "group" ? [searchUser(value)] : []),
      ...(props.optionsMode !== "user" ? [searchUserGroup(value)] : []),
    ]);

    setFetching(false);
  };

  const handleSelectChange = (originValue) => {
    const value = filter(originValue, (item) => {
      return !find(props.staticList, (v) => v === item.key);
    });
    value.unshift(...staticValue.current);
    setSelectedValue(value);
    const resultValue = {
      selectedUser: map(
        reject(value, (v) => {
          return startsWith(v.key, ":");
        }),
        "key"
      ),

      selectedUserGroup: map(
        filter(value, (v) => {
          return startsWith(v.key, ":");
        }),
        "key"
      ),
    };

    triggerChange(resultValue);
    if (searchValue !== "") {
      searchUserOrUserGroupInstances("");
    }
  };

  const handleFocus = () => {
    if (isNil(searchValue) || searchValue !== "") {
      searchUserOrUserGroupInstances("");
    }
  };

  const openModal = () => {
    setModalVisible(true);
  };

  const closeModal = () => {
    setModalVisible(false);
  };

  const handleInstancesSelected = (
    value: Record<string, any>[],
    objectId: string
  ): void => {
    let labelValue: any[] = [];
    if (objectId === "USER") {
      labelValue = [
        ...map(value, (v) => {
          const labelText = getLabel("USER", v);
          return {
            key: v.name,
            label: props.staticList?.includes(v.name)
              ? getStaticLabel(labelText)
              : labelText,
          };
        }),
      ];
    } else {
      labelValue = [
        ...map(value, (v) => {
          const labelText = getLabel("USER_GROUP", v);
          return {
            key: ":" + v.instanceId,
            label: props.staticList?.includes(":" + v.instanceId)
              ? getStaticLabel(labelText)
              : labelText,
          };
        }),
      ];
    }
    const resultSelectedValue = uniqBy(
      [...selectedValue, ...labelValue],
      "key"
    );

    setSelectedValue(resultSelectedValue);
    const resultValue = {
      selectedUser: map(
        reject(resultSelectedValue, (v) => {
          return startsWith(v.key, ":");
        }),
        "key"
      ),

      selectedUserGroup: map(
        filter(resultSelectedValue, (v) => {
          return startsWith(v.key, ":");
        }),
        "key"
      ),
    };

    triggerChange(resultValue);
  };

  const handleModalSelected = async (selectedKeys: string[]) => {
    if (selectedKeys?.length) {
      const instances = (
        await InstanceApi_postSearch(modalObjectId, {
          query: { instanceId: { $in: selectedKeys } },
          fields: { "*": true },
          page_size: selectedKeys.length,
        })
      ).list;
      handleInstancesSelected(instances, modalObjectId);
    }
    setModalVisible(false);
  };

  const toggleObjectId = () => {
    setModalObjectId(modalObjectId === "USER" ? "USER_GROUP" : "USER");
  };

  const title = (
    <div>
      {t(K.FILTER_FROM_CMDB, {
        type: modalObjectId === "USER" ? t(K.USERS) : t(K.USER_GROUPS),
      })}{" "}
      {props.optionsMode === "all" && (
        <Button type="link" onClick={toggleObjectId}>
          {t(K.SWITCH, {
            type: modalObjectId === "USER" ? t(K.USER_GROUPS) : t(K.USERS),
          })}{" "}
        </Button>
      )}
    </div>
  );

  // 快速选择我
  const addMeQuickly = async () => {
    const myUserName = getAuth().username;
    if (find(selectedValue, (v) => v.key === myUserName)) {
      // 如果已选择项中包含我,则不重新发起请求
      return;
    }
    const myUser = (
      await InstanceApi_postSearch("USER", {
        query: {
          name: {
            $eq: myUserName,
          },
        },

        page: 1,
        page_size: 1,
        fields: {
          ...zipObject(
            userShowKey,
            map(userShowKey, (v) => true)
          ),

          name: true,
        },
      })
    ).list;
    handleInstancesSelected(myUser, "USER");
  };

  const getRight = () => {
    const btnWidth =
      !props.hideAddMeQuickly && props.optionsMode !== "group" ? -34 : 0;
    const lineWidth =
      !props.hideAddMeQuickly &&
      props.optionsMode !== "group" &&
      !props.hideSelectByCMDB
        ? -1
        : 0;
    const iconWidth = props.hideSelectByCMDB ? 0 : -32;
    return btnWidth + lineWidth + iconWidth;
  };

  return (
    <div
      ref={ref}
      data-testid="wrapper"
      className={styles.UserOrUserGroupSelectContainer}
    >
      <Select
        className={styles.customSelect}
        ref={selectRef}
        allowClear={true}
        mode="multiple"
        labelInValue
        placeholder={props.placeholder}
        filterOption={false}
        value={selectedValue}
        onChange={handleSelectChange}
        onSearch={debounce((value) => {
          searchUserOrUserGroupInstances(value as string);
        }, 500)}
        onFocus={handleFocus}
        style={{ width: "100%" }}
        loading={fetching}
      >
        {props.optionsMode !== "group" && (
          <Select.OptGroup label={t(K.USERS_RESULT_LABEL)}>
            {userList.length > 0 ? (
              userList.map((d) => (
                <Select.Option value={d.name} key={d.name}>
                  {props.staticList?.includes(d.name)
                    ? getStaticLabel(getLabel("USER", d))
                    : getLabel("USER", d)}
                </Select.Option>
              ))
            ) : (
              <Select.Option value="empty-user" key="empty-user" disabled>
                {t(K.NO_DATA)}
              </Select.Option>
            )}
          </Select.OptGroup>
        )}

        {props.optionsMode !== "user" && (
          <Select.OptGroup label={t(K.USER_GROUPS_RESULT_LABEL)}>
            {userGroupList.length > 0 ? (
              userGroupList.map((d) => (
                <Select.Option value={d.instanceId} key={d.instanceId}>
                  {props.staticList?.includes(d.instanceId)
                    ? getStaticLabel(getLabel("USER_GROUP", d))
                    : getLabel("USER_GROUP", d)}
                </Select.Option>
              ))
            ) : (
              <Select.Option
                value="empty-user-group"
                key="empty-user-group"
                disabled
              >
                {t(K.NO_DATA)}
              </Select.Option>
            )}
          </Select.OptGroup>
        )}
      </Select>
      <div className={styles.extra} style={{ right: getRight() }}>
        {!props.hideAddMeQuickly && props.optionsMode !== "group" && (
          <Button
            type="link"
            onClick={addMeQuickly}
            style={{ fontSize: "16px" }}
          >
            <GeneralIcon icon={{ lib: "easyops", icon: "quick-add-me" }} />
          </Button>
        )}

        {!props.hideAddMeQuickly &&
          props.optionsMode !== "group" &&
          !props.hideSelectByCMDB && <Divider type="vertical" />}
        {!props.hideSelectByCMDB && (
          <Button
            type="link"
            icon={<SearchOutlined />}
            onClick={openModal}
          ></Button>
        )}
      </div>
      <InstanceListModal
        objectMap={props.objectMap}
        objectId={modalObjectId}
        visible={modalVisible}
        title={title}
        onSelected={handleModalSelected}
        onCancel={closeModal}
        showSizeChanger={true}
        {...(modalObjectId === "USER" && props.hideInvalidUser
          ? {
              presetConfigs: {
                query: {
                  state: "valid",
                },
              },
            }
          : {})}
      />
    </div>
  );
}
Example #7
Source File: constants.ts    From next-basics with GNU General Public License v3.0 4 votes vote down vote up
attrTypeBrickConfOfForms = (
  attr: Attribute
): Record<string, any> => {
  const attrType = attr.value.type;
  switch (attrType) {
    case "str":
      return [
        {
          id: uniqueId(`${attr.instanceId}-str`),
          brickConf: [
            {
              brick: "forms.general-input",
              properties: {
                name: attr.id,
                label: attr.name,
                required: attr.required,
                ...(attr.value.regex ? { pattern: attr.value.regex } : {}),
              },
              mountPoint: "items",
            },
          ],
        },
      ];
    case "int":
      return [
        {
          id: uniqueId(`${attr.instanceId}-int`),
          brickConf: [
            {
              brick: "forms.general-input-number",
              properties: {
                name: attr.id,
                label: attr.name,
                required: attr.required,
              },
              mountPoint: "items",
            },
          ],
        },
      ];
    case "date":
      return [
        {
          id: uniqueId(`${attr.instanceId}-date`),
          brickConf: [
            {
              brick: "forms.general-date-picker",
              properties: {
                name: attr.id,
                label: attr.name,
                required: attr.required,
              },
              mountPoint: "items",
            },
          ],
        },
      ];
    case "datetime":
      return [
        {
          id: uniqueId(`${attr.instanceId}-datetime`),
          brickConf: [
            {
              brick: "forms.general-time-picker",
              properties: {
                name: attr.id,
                label: attr.name,
                required: attr.required,
              },
              mountPoint: "items",
            },
          ],
        },
      ];
    case "enum":
      return [
        {
          id: uniqueId(`${attr.instanceId}-enum`),
          brickConf: [
            {
              brick: "forms.general-radio",
              properties: {
                name: attr.id,
                label: attr.name,
                required: attr.required,
                options:
                  attr.value?.regex?.map((v) => ({ label: v, value: v })) ?? [],
              },
              mountPoint: "items",
            },
          ],
        },
        {
          id: uniqueId(`${attr.instanceId}-enum`),
          brickConf: [
            {
              brick: "forms.general-select",
              properties: {
                name: attr.id,
                label: attr.name,
                required: attr.required,
                options:
                  attr.value?.regex?.map((v) => ({ label: v, value: v })) ?? [],
                allowClear: !attr.required,
                inputBoxStyle: {
                  width: 200,
                },
              },
              mountPoint: "items",
            },
          ],
        },
      ];
    case "arr":
      return [
        {
          id: uniqueId(`${attr.instanceId}-arr`),
          brickConf: [
            {
              brick: "forms.general-select",
              properties: {
                name: attr.id,
                label: attr.name,
                required: attr.required,
                mode: "tags",
                tokenSeparators: [",", " "],
              },
              mountPoint: "items",
            },
          ],
        },
      ];
    case "struct":
      return [
        {
          id: uniqueId(`${attr.instanceId}-struct`),
          brickConf: [
            // Todo(Lynette): slots
            {
              brick: "forms.general-structs-form-item",
              properties: {
                name: attr.id,
                label: attr.name,
                required: attr.required,
                multiple: false,
                btnText: `添加 ${attr.name}`,
                fieldsMap: zipObject(
                  map(attr.value.struct_define, "id"),
                  map(attr.value.struct_define, "name")
                ),
              },
              mountPoint: "items",
            },
          ],
        },
      ];
    case "structs":
      return [
        {
          id: uniqueId(`${attr.instanceId}-structs`),
          brickConf: [
            // Todo(Lynette): slots
            {
              brick: "forms.general-structs-form-item",
              properties: {
                name: attr.id,
                label: attr.name,
                required: attr.required,
                multiple: true,
                btnText: `添加 ${attr.name}`,
                fieldsMap: zipObject(
                  map(attr.value.struct_define, "id"),
                  map(attr.value.struct_define, "name")
                ),
              },
              mountPoint: "items",
            },
          ],
        },
      ];
    case "ip":
      return [
        {
          id: uniqueId(`${attr.instanceId}-ip`),
          brickConf: [
            {
              brick: "forms.general-input",
              properties: {
                name: attr.id,
                label: attr.name,
                required: attr.required,
              },
              mountPoint: "items",
            },
          ],
        },
      ];
    case "bool":
      return [
        {
          id: uniqueId(`${attr.instanceId}-bool`),
          brickConf: [
            {
              brick: "forms.general-radio",
              properties: {
                name: attr.id,
                label: attr.name,
                required: attr.required,
                options: [
                  {
                    label: "True",
                    value: true,
                  },
                  {
                    label: "False",
                    value: false,
                  },
                ],
              },
              mountPoint: "items",
            },
          ],
        },
        {
          id: uniqueId(`${attr.instanceId}-bool`),
          brickConf: [
            {
              brick: "forms.general-switch",
              properties: {
                name: attr.id,
                label: attr.name,
                required: attr.required,
              },
              mountPoint: "items",
            },
          ],
        },
      ];
    case "float":
      return [
        {
          id: uniqueId(`${attr.instanceId}-float`),
          brickConf: [
            {
              brick: "forms.general-input",
              properties: {
                name: attr.id,
                label: attr.name,
                required: attr.required,
              },
              mountPoint: "items",
            },
          ],
        },
      ];
    case "json":
      return [
        {
          id: uniqueId(`${attr.instanceId}-json`),
          brickConf: [
            {
              brick: "code-bricks.code-editor",
              properties: {
                name: attr.id,
                label: attr.name,
                required: attr.required,
                mode: "json",
              },
              mountPoint: "items",
            },
          ],
        },
      ];
  }
}
Example #8
Source File: [[...params]].tsx    From reddium with MIT License 4 votes vote down vote up
IndexPage = ({ trendingSubs, profile, params }: any) => {
  const [{ posts, after }, setPostData] = useState(LOADING_POST_LIST);
  const [selectedParams, setSelectedParams] = useState({
    ...zipObject(POPULAR_PARAM_KEY, POPULAR_PARAM_DEFAULT),
    ...params
  });
  const loader = useRef<HTMLDivElement>(null);

  if (typeof window !== "undefined" && profile?.name) {
    H.identify(profile.name, { id: profile.name });
  }

  useEffect(() => {
    getPopularPostsClient({ ...selectedParams, home: true }).then(res => {
      setPostData(res);
    });
  }, []);

  const filterPopular = () => {
    setPostData(LOADING_POST_LIST);
    window.location.href = `/${selectedParams.sort_type}?t=${selectedParams.t}&limit=${selectedParams.limit}`;
  };

  const fetchMorePosts = async () => {
    const next = await getPopularPosts({
      ...selectedParams,
      after: after
    });
    setPostData({ posts: [...posts, ...next.posts], after: next.after });
  };

  return (
    <Layout title="Reddium – Medium-themed Reddit client" token={params.token}>
      <div className="lg:w-auto lg:mx-12 mx-auto w-full flex main-container max-width-main pb-10 sm:mx-6">
        <MidContainer>
          <LargeCard {...posts[0]} />
        </MidContainer>
        <MidContainer>
          {posts.slice(1, 5).map((p: any, ind: number) => (
            <MidCard key={ind} {...p} />
          ))}
        </MidContainer>
        <MidContainer>
          <div className="h-full container-divide pl-8 block lg:hidden">
            <div className="mb-12">
              <p className="heading-text text-sm leading-4 uppercase tracking-wide">
                Popular posts
              </p>

              <Dropdown
                key={SORT_TYPE}
                id={SORT_TYPE}
                paramKey={POPULAR_PARAM_KEY}
                paramVal={POPULAR_PARAM_VALUES}
                dataObj={selectedParams}
                updateParams={setSelectedParams}
              />
              {selectedParams.sort_type == "top" ? (
                <Dropdown
                  key={TIME_FILTER}
                  id={TIME_FILTER}
                  paramKey={POPULAR_PARAM_KEY}
                  paramVal={POPULAR_PARAM_VALUES}
                  dataObj={selectedParams}
                  updateParams={setSelectedParams}
                />
              ) : (
                ""
              )}
              <button
                className="my-4 p-2 cursor-pointer w-48 max-w-full btn-black text-white rounded"
                onClick={filterPopular}
              >
                Filter
              </button>
            </div>
            <TrendingSubs {...trendingSubs} />
          </div>
        </MidContainer>
      </div>
      <div className="w-full main-container max-width-main pb-4 pt-10 sub-top-border hidden lg:w-auto lg:mx-12 lg:flex sm:mx-6">
        <MidContainer>
          <div className="h-full">
            <div className="mb-12">
              <p className="heading-text text-sm leading-4 uppercase tracking-wide sm:text-xs">
                Popular posts
              </p>

              <Dropdown
                key={SORT_TYPE}
                id={SORT_TYPE}
                paramKey={POPULAR_PARAM_KEY}
                paramVal={POPULAR_PARAM_VALUES}
                dataObj={selectedParams}
                updateParams={setSelectedParams}
              />
              {selectedParams.sort_type == "top" ? (
                <Dropdown
                  key={TIME_FILTER}
                  id={TIME_FILTER}
                  paramKey={POPULAR_PARAM_KEY}
                  paramVal={POPULAR_PARAM_VALUES}
                  dataObj={selectedParams}
                  updateParams={setSelectedParams}
                />
              ) : (
                ""
              )}
              <button
                className="my-4 p-2 cursor-pointer w-48 max-w-full btn-black text-white rounded"
                onClick={filterPopular}
              >
                Filter
              </button>
            </div>
          </div>
        </MidContainer>
        <MidContainer>
          <div className="h-full container-divide pl-8 sm:pl-0">
            <TrendingSubs {...trendingSubs} />
          </div>
        </MidContainer>
      </div>
      <div className="w-full flex main-container max-width-main pb-4 pt-10 sub-top-border lg:w-auto lg:mx-12 sm:mx-6">
        <div className="w-full flex mb-4 flex-row items-center">
          <img className="mr-3" src="trending.svg" />
          <div>
            <p className="heading-text text-sm leading-4 uppercase tracking-wide sm:text-xs">
              Trending on Reddit
            </p>
          </div>
        </div>
        <div className="w-full flex mb-4 flex-row items-start flex-wrap">
          {posts.slice(5, 11).map((p: any, ind: number) => (
            <RankedCard key={ind} rank={ind + 6} {...p} />
          ))}
        </div>
      </div>
      <div className="w-full flex main-container max-width-main pb-4 pt-10 sub-top-border posts-grid lg:w-auto lg:mx-12 md:block sm:mx-6">
        <div className="w-full mb-4 grid-left">
          {posts.slice(11, posts.length).map((p: any, ind: number) => (
            <WideCard key={ind} {...p} />
          ))}
          <div className="w-full text-center" ref={loader}>
            {/* <WideCard {...({} as Post)} /> */}
            <button
              className="my-4 mx-auto p-2 cursor-pointer w-48 max-w-full load-more main-black font-semibold rounded flex flex-row justify-between items-center"
              onClick={fetchMorePosts}
            >
              <div className="flex-grow text-center">Show More</div>
              <img className="ml-3" src="/down_arrow.svg" />
            </button>
          </div>
        </div>
        <div className="grid-right md:hidden">
          <div className="sticky top-8 p-8 about-bg flex flex-col">
            <div className="w-full flex mb-4 flex-row items-center">
              <img className="mr-3" src="bookmarks.svg" />
              <div>
                <p className="heading-text text-sm leading-4 uppercase tracking-wide sm:text-xs">
                  About Reddium
                </p>
              </div>
            </div>
            <div className="w-full pb-6">
              <p className="text-sm">
                Ever wanted to browse Reddit while studying at Starbucks? Or
                while sitting on the subway to work? Worried that people around
                you would judge the subreddits you browse and the posts you
                read?
                <br />
                <br />
                Reddium is a Medium-themed Reddit client. The Reddium interface
                converts Reddit posts, discussions, and memes into well-crafted
                articles. Medium's layout feels a little more readable than
                Reddit's, removing all distractions and clutter. It also
                bypasses Reddit's frustrating mobile browser.
                <br />
                <br />I hope you enjoy this project! Feel free to suggest any
                features or report bugs on GitHub.
              </p>
            </div>
            <div className="w-full pb-6 hidden">
              <img className="w-4/12 float-right" src="/signature.png" />
            </div>
            <a
              href="https://github.com/eightants/reddium/"
              target="_blank"
              rel="noopener noreferrer"
            >
              <button className="mt-2 mx-1 p-2 pl-0 pb-3 cursor-pointer w-full max-w-full btn-black text-white rounded">
                ✨ Star on GitHub
              </button>
            </a>
            <a
              href="https://ko-fi.com/eightants"
              target="_blank"
              rel="noopener noreferrer"
            >
              <button className="mt-2 mx-1 p-2 pl-0 pb-3 cursor-pointer w-full max-w-full btn-outline-black text-white rounded">
                ☕ Buy me a coffee
              </button>
            </a>
          </div>
        </div>
      </div>
    </Layout>
  );
}
Example #9
Source File: [[...params]].tsx    From reddium with MIT License 4 votes vote down vote up
SubredditPage = ({ postData, subredditInfo, params }: any) => {
  const [{ posts, after }, setPostData] = useState(postData);
  const [selectedParams, setSelectedParams] = useState({
    ...zipObject(POPULAR_PARAM_KEY, POPULAR_PARAM_DEFAULT),
    ...params
  });

  const filterPopular = () => {
    setPostData({ posts: new Array(15).fill({}) });
    window.location.href = `/r/${selectedParams.subreddit}/${selectedParams.sort_type}?t=${selectedParams.t}&limit=${selectedParams.limit}`;
  };

  const fetchMorePosts = async () => {
    const next = await getPopularPostsClient({
      ...selectedParams,
      after: after
    });
    setPostData({ posts: [...posts, ...next.posts], after: next.after });
  };

  useEffect(() => {
    if (typeof window !== 'undefined') {
      console.log(subredditInfo.over18)
      H.track(
        "Subreddit", 
        {subredditName: subredditInfo.display_name, nsfw: subredditInfo.over18.toString()}
      );
    }
  });

  return (
    <Subpage
      title={subredditInfo.display_name}
      subreddit={params.subreddit}
      token={params.token}
      backgroundColor="rgb(250,250,250)"
    >
      <Header {...params} {...subredditInfo} />
      <section className="w-full mx-auto max-width-sub pb-10 mt-6 lg:w-auto lg:mx-12 sm:mx-6">
        <header className="sub-bottom-border pb-2">
          <span className="text-lg sub-opacity-68">Featured</span>
        </header>
        <div className="my-6 flex flex-row flex-wrap sm-border-b sm:pb-4">
          {posts.slice(0, 3).map((p: any, ind: number) => (
            <SubGridCard key={ind} {...p} />
          ))}
        </div>
      </section>
      <section className="hidden mx-12 w-auto max-width-sub pb-10 mt-6 md:block sm:mx-6">
        <SubredditInfo {...subredditInfo} />
        <div className="h-full pt-8">
          <div className="mb-12">
            <p className="heading-text text-sm leading-4 uppercase tracking-wide">
              Popular posts
            </p>

            <Dropdown
              key={SORT_TYPE}
              id={SORT_TYPE}
              paramKey={POPULAR_PARAM_KEY}
              paramVal={POPULAR_PARAM_VALUES}
              dataObj={selectedParams}
              updateParams={setSelectedParams}
            />
            {selectedParams.sort_type == "top" ? (
              <Dropdown
                key={TIME_FILTER}
                id={TIME_FILTER}
                paramKey={POPULAR_PARAM_KEY}
                paramVal={POPULAR_PARAM_VALUES}
                dataObj={selectedParams}
                updateParams={setSelectedParams}
              />
            ) : (
              ""
            )}
            <button
              className="my-4 p-2 cursor-pointer w-48 max-w-full btn-black text-white rounded"
              onClick={filterPopular}
            >
              Filter
            </button>
          </div>
        </div>
      </section>
      <div className="w-full flex main-container max-width-sub pb-4 posts-grid md:block lg:w-auto lg:mx-12 sm:mx-6">
        <div className="w-full mb-4 grid-left">
          <header className="sub-bottom-border pb-2">
            <span className="text-lg sub-opacity-68">Trending</span>
          </header>
          {posts.slice(3, posts.length).map((p: any, ind: number) => (
            <SubWideCard key={ind} {...p} />
          ))}
          <div className="w-full text-center">
            <button
              className="my-4 mx-auto p-2 cursor-pointer w-48 max-w-full load-more main-black font-semibold rounded flex flex-row justify-between items-center"
              onClick={fetchMorePosts}
            >
              <div className="flex-grow text-center">Show More</div>
              <img className="ml-3" src="/down_arrow.svg" />
            </button>
          </div>
        </div>
        <div className="grid-right md:hidden">
          <div className="sticky top-8 p-8">
            <SubredditInfo {...subredditInfo} />
            <div className="h-full pt-8">
              <div className="mb-12">
                <p className="heading-text text-sm leading-4 uppercase tracking-wide">
                  Popular posts
                </p>

                <Dropdown
                  key={SORT_TYPE}
                  id={SORT_TYPE}
                  paramKey={POPULAR_PARAM_KEY}
                  paramVal={POPULAR_PARAM_VALUES}
                  dataObj={selectedParams}
                  updateParams={setSelectedParams}
                />
                {selectedParams.sort_type == "top" ? (
                  <Dropdown
                    key={TIME_FILTER}
                    id={TIME_FILTER}
                    paramKey={POPULAR_PARAM_KEY}
                    paramVal={POPULAR_PARAM_VALUES}
                    dataObj={selectedParams}
                    updateParams={setSelectedParams}
                  />
                ) : (
                  ""
                )}
                <button
                  className="my-4 p-2 cursor-pointer w-48 max-w-full btn-black text-white rounded"
                  onClick={filterPopular}
                >
                  Filter
                </button>
              </div>
            </div>
          </div>
        </div>
      </div>
    </Subpage>
  );
}
Example #10
Source File: index.tsx    From reddium with MIT License 4 votes vote down vote up
SearchPage = ({ searchRes, params }: any) => {
  const [{ items, after }, setSearchData] = useState(searchRes);
  const [searchTerm, setSearchTerm] = useState(params.q);
  const [selectedParams, setSelectedParams] = useState({
    ...zipObject(SEARCH_PARAM_KEY, SEARCH_PARAM_DEFAULT),
    ...params
  });

  const filterPopular = () => {
    window.location.href = `/search/?q=${searchTerm}&sort=${selectedParams.sort}&t=${selectedParams.t}`;
  };
  const fetchMorePosts = async () => {
    const next = await getSearchClient({
      ...params,
      after: after
    });
    setSearchData({ items: [...items, ...next.items], after: next.after });
  };

  const newSearch = () => (window.location.href = `/search/?q=${searchTerm}`);

  return (
    <div>
      <TitleHead title={`Search - Reddium`}>
        <meta
          name="description"
          content={`Results for '${params.q}' on Reddium. `}
        />
        <meta property="og:url" content={`${DOMAIN}/search/?q=${params.q}`} />
        <meta
          property="og:description"
          content={`Results for '${params.q}' on Reddium. `}
        />
        <meta property="og:image" content={`${DOMAIN}/reddium-sub.png`} />
      </TitleHead>
      <header className="navbar-shadow">
        <nav className=" flex items-center justify-center max-width-sub mx-auto z-50 h-16 lg:mx-12 sm:mx-6 sm:h-14">
          <div className="flex-grow flex items-center">
            <a href="/">
              <div className="pr-4 h-8 flex flex-row items-center cursor-pointer">
                <img className="w-12" src="reddium_symbol.svg" />
                <h1 className="ml-2 site-name text-3xl tracking-tighter text-black sm:hidden">
                  Reddium
                </h1>
              </div>
            </a>
          </div>
          <NavMenu token={params.token}/>
        </nav>
      </header>
      <section className="lg:w-auto lg:mx-12 sm:mx-2">
        <div className="w-full main-container max-width-sub pb-4 pt-10 px-8 sub-top-border lg:flex sm:px-0">
          <div className="px-4 pb-6 w-full">
            <input
              className="w-full search-input h-20 text-5xl main-black sm:text-3xl sm:h-16"
              placeholder="Search..."
              value={searchTerm}
              onChange={(e) => setSearchTerm(e.target.value)}
              onKeyDown={(e) => e.key === "Enter" && newSearch()}
            />
          </div>
          <div className="w-full">
            <nav className="px-4 mb-10 w-full">
              <ul className="whitespace-no-wrap text-sm w-full">
                <li className="sub-link-grey mr-6 inline-block">
                  <a
                    href={`/search/?q=${params.q}&type=`}
                    className={`link-black-hover ${
                      params.type == "sr,user" ? "" : "main-black font-bold"
                    }`}
                  >
                    Stories
                  </a>
                </li>
                <li className="sub-link-grey mr-6 inline-block">
                  <a
                    href={`/search/?q=${params.q}&type=sr%2Cuser`}
                    className={`link-black-hover ${
                      params.type == "sr,user" ? "main-black font-bold" : ""
                    }`}
                  >
                    Publications and People
                  </a>
                </li>
              </ul>
            </nav>
            <div className="main-black overflow-hidden w-full">
              <div className="w-9/12 px-4 float-left sm:w-full">
                <header className="sub-top-border sm:border-0">
                  <h3 className="pt-4 mb-10 uppercase tracking-wider sm:hidden">
                    {params.type == "sr,user"
                      ? "Publications and People"
                      : "Stories"}
                  </h3>
                </header>
                <div className="hidden md:block mb-8">
                  <Dropdown
                    key={SORT_PARAM}
                    id={SORT_PARAM}
                    paramKey={SEARCH_PARAM_KEY}
                    paramVal={SEARCH_PARAM_VALUES}
                    dataObj={selectedParams}
                    updateParams={setSelectedParams}
                  />
                  {selectedParams.sort != "new" ? (
                    <Dropdown
                      key={TIME_FILTER}
                      id={TIME_FILTER}
                      paramKey={SEARCH_PARAM_KEY}
                      paramVal={SEARCH_PARAM_VALUES}
                      dataObj={selectedParams}
                      updateParams={setSelectedParams}
                    />
                  ) : (
                    ""
                  )}
                  <button
                    className="my-4 p-2 cursor-pointer w-48 max-w-full btn-black text-white rounded"
                    onClick={filterPopular}
                  >
                    Filter
                  </button>
                </div>
                <div>
                  {items.length < 1 ? (
                    <h2 className="text-3xl leading-6 sm:text-2xl">
                      No results found.{" "}
                    </h2>
                  ) : (
                    items.map((item: any, ind: number) =>
                      item.kind == "t3" ? (
                        <SearchPost key={ind} {...item} token={params.token} />
                      ) : item.kind == "t5" ? (
                        <SubredditCard key={ind} {...item} />
                      ) : (
                        <UserCard key={ind} {...item} />
                      )
                    )
                  )}
                  {after ? (
                    <button
                      className="mb-8 mx-auto p-2 cursor-pointer w-48 max-w-full load-more main-black font-semibold rounded flex flex-row justify-between items-center"
                      onClick={fetchMorePosts}
                    >
                      <div className="flex-grow text-center">Show More</div>
                      <img className="ml-3" src="/down_arrow.svg" />
                    </button>
                  ) : (
                    <div className="py-10"></div>
                  )}
                </div>
              </div>
              <div className="w-3/12 px-4 float-right md:hidden">
                <header className="sub-top-border sm:border-0">
                  <h3 className="pt-4 mb-10 uppercase tracking-wider sm:hidden">
                    Filter
                  </h3>
                </header>
                <Dropdown
                  key={SORT_PARAM}
                  id={SORT_PARAM}
                  paramKey={SEARCH_PARAM_KEY}
                  paramVal={SEARCH_PARAM_VALUES}
                  dataObj={selectedParams}
                  updateParams={setSelectedParams}
                />
                {selectedParams.sort != "new" ? (
                  <Dropdown
                    key={TIME_FILTER}
                    id={TIME_FILTER}
                    paramKey={SEARCH_PARAM_KEY}
                    paramVal={SEARCH_PARAM_VALUES}
                    dataObj={selectedParams}
                    updateParams={setSelectedParams}
                  />
                ) : (
                  ""
                )}
                <button
                  className="my-4 p-2 cursor-pointer w-48 max-w-full btn-black text-white rounded"
                  onClick={filterPopular}
                >
                  Filter
                </button>
              </div>
            </div>
          </div>
        </div>
      </section>
    </div>
  );
}