Skip to content

List / Recommendation でコンテスト種別を指定する #1504

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions atcoder-problems-frontend/src/pages/ListPage/ListTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ import {
useProblemModelMap,
useRatingInfo,
} from "../../api/APIClient";
import { classifyContest } from "../../utils/ContestClassifier";
import { getLikeContestCategory } from "../../utils/LikeContestUtils";

export const INF_POINT = 1e18;

Expand Down Expand Up @@ -113,6 +115,8 @@ interface Props {
| "Only Rated"
| "Only Unrated"
| "Only Unrated without Difficulty";
contestCategoryFilterState: string;
mergeLikeContest: boolean;
fromDifficulty: number;
toDifficulty: number;
filteredSubmissions: Submission[];
Expand Down Expand Up @@ -629,6 +633,18 @@ export const ListTable: React.FC<Props> = (props) => {
return !isRated && !hasDifficulty;
}
})
.filter((row): boolean => {
if (props.contestCategoryFilterState === "All") return true;
if (!row.contest) return false;
const contest = contestMap?.get(row.contest.id);
const contestCategory = classifyContest(contest);
return (
props.contestCategoryFilterState === contestCategory ||
(props.mergeLikeContest &&
getLikeContestCategory(props.contestCategoryFilterState) ===
contestCategory)
);
})
.filter((row) => {
const difficulty = isProblemModelWithDifficultyModel(row.problemModel)
? row.problemModel.difficulty
Expand Down
52 changes: 50 additions & 2 deletions atcoder-problems-frontend/src/pages/ListPage/ProblemList.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Range } from "immutable";
import React from "react";
import { React, useMemo, useState } from "react";
import { Link, useHistory, useLocation } from "react-router-dom";
import {
Button,
Expand All @@ -24,15 +24,19 @@ import {
} from "../../utils/ProblemSelection";
import { useLoginState } from "../../api/InternalAPIClient";
import { useMergedProblemMap } from "../../api/APIClient";
import { ContestCategories } from "../../utils/ContestClassifier";
import { isLikeContestCategory } from "../../utils/LikeContestUtils";
import { INF_POINT, ListTable, StatusFilter, statusFilters } from "./ListTable";

export const FilterParams = {
FromPoint: "fromPo",
ToPoint: "toPo",
Status: "status",
Rated: "rated",
Category: "category",
FromDifficulty: "fromDiff",
ToDifficulty: "toDiff",
mergeLikeContest: "mergeLikeContest",
Language: "Lang",
} as const;

Expand All @@ -55,12 +59,14 @@ const RATED_FILTERS = [
] as const;
type RatedFilter = typeof RATED_FILTERS[number];

const categoryFilters = ["All", ...ContestCategories] as const;
type CategoryFilter = typeof categoryFilters[number];
interface Props {
userId: string;
submissions: Submission[];
}

export const ProblemList: React.FC<Props> = (props) => {
export const ProblemList: React.FC<Props> = (props: Props) => {
const location = useLocation();
const history = useHistory();

Expand Down Expand Up @@ -91,6 +97,10 @@ export const ProblemList: React.FC<Props> = (props) => {
const ratedFilterState: RatedFilter =
RATED_FILTERS.find((x) => x === searchParams.get(FilterParams.Rated)) ??
"All";
const contestCategoryFilterState: CategoryFilter =
categoryFilters.find(
(x) => x === searchParams.get(FilterParams.Category)
) ?? "All";

const languages = ["All"].concat(
Array.from(
Expand All @@ -112,6 +122,7 @@ export const ProblemList: React.FC<Props> = (props) => {
searchParams.get(FilterParams.ToDifficulty) || INF_POINT.toString(),
10
);
const [mergeLikeContest, setMergeLikeContest] = useState(true);
const mergedProblemMap =
useMergedProblemMap().data ?? new Map<ProblemId, MergedProblem>();
const points = Array.from(
Expand All @@ -130,6 +141,14 @@ export const ProblemList: React.FC<Props> = (props) => {

const loginState = useLoginState().data;
const isLoggedIn = UserState.isLoggedIn(loginState);
const filteredCategoryFilters = useMemo(() => {
return categoryFilters.filter(
(category) =>
category === "All" ||
!mergeLikeContest ||
!isLikeContestCategory(category)
);
}, [mergeLikeContest]);
return (
<>
<Row className="my-2 border-bottom">
Expand Down Expand Up @@ -210,6 +229,33 @@ export const ProblemList: React.FC<Props> = (props) => {
</DropdownMenu>
</UncontrolledDropdown>
</ButtonGroup>
<ButtonGroup className="mr-4">
<UncontrolledDropdown>
<DropdownToggle caret>
{contestCategoryFilterState === "All"
? "Category"
: contestCategoryFilterState}
</DropdownToggle>
<DropdownMenu>
{filteredCategoryFilters.map((value) => (
<DropdownItem
key={value}
tag={Link}
to={generatePathWithParams(location, {
[FilterParams.Category]: value,
})}
>
{value}
</DropdownItem>
))}
</DropdownMenu>
</UncontrolledDropdown>
<Button onClick={(): void => setMergeLikeContest(!mergeLikeContest)}>
{mergeLikeContest
? 'Merging "-Like" Contest'
: 'Unmerging "-Like" Contest'}
</Button>
</ButtonGroup>
<ButtonGroup className="mr-4">
<UncontrolledDropdown>
<DropdownToggle caret>
Expand Down Expand Up @@ -325,6 +371,8 @@ export const ProblemList: React.FC<Props> = (props) => {
toPoint={toPoint}
statusFilterState={statusFilterState}
ratedFilterState={ratedFilterState}
contestCategoryFilterState={contestCategoryFilterState}
mergeLikeContest={mergeLikeContest}
fromDifficulty={fromDifficulty}
toDifficulty={toDifficulty}
filteredSubmissions={props.submissions}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from "react";
import { React, useMemo } from "react";
import {
Button,
ButtonGroup,
Expand All @@ -9,7 +9,7 @@ import {
UncontrolledDropdown,
} from "reactstrap";
import { formatExcludeOption } from "../../../utils/LastSolvedTime";

import { isLikeContestCategory } from "../../../utils/LikeContestUtils";
const RECOMMEND_NUM_OPTIONS = [
{
text: "10",
Expand Down Expand Up @@ -47,80 +47,142 @@ const ExcludeOptions = [
] as const;
export type ExcludeOption = typeof ExcludeOptions[number];

const CategoryOptions = [
"All",
"ABC",
"ARC",
"AGC",
"ABC-Like",
"ARC-Like",
"AGC-Like",
"Other Sponsored",
] as const;
export type CategoryOption = typeof CategoryOptions[number];

interface Props {
recommendOption: RecommendOption;
onChangeRecommendOption: (option: RecommendOption) => void;

excludeOption: ExcludeOption;
onChangeExcludeOption: (option: ExcludeOption) => void;

categoryOption: CategoryOption;
onChangeCategoryOption: (option: CategoryOption) => void;

showExperimental: boolean;
onChangeExperimentalVisibility: (showExperimental: boolean) => void;
mergeLikeContest: boolean;
onChangeMergeLikeContest: (mergeLikeContest: boolean) => void;

showCount: number;
onChangeShowCount: (count: number) => void;
}

export const RecommendController = (props: Props) => (
<>
<div>
<ButtonGroup className="mr-3">
{RecommendOptions.map((type) => (
<Button
key={type}
active={props.recommendOption === type}
onClick={() => props.onChangeRecommendOption(type)}
>
{type}
</Button>
))}
</ButtonGroup>
<ButtonGroup className="mr-3">
<UncontrolledDropdown>
<DropdownToggle caret>
{formatExcludeOption(props.excludeOption)}
</DropdownToggle>
<DropdownMenu>
{ExcludeOptions.map((option) => (
<DropdownItem
key={option}
onClick={(): void => props.onChangeExcludeOption(option)}
>
{formatExcludeOption(option)}
</DropdownItem>
))}
</DropdownMenu>
</UncontrolledDropdown>
</ButtonGroup>
<CustomInput
type="switch"
id="switchRecommendExperimental"
inline
label={
<span role="img" aria-label="experimental">
🧪
</span>
}
checked={props.showExperimental}
onChange={() =>
props.onChangeExperimentalVisibility(!props.showExperimental)
}
/>
</div>
<UncontrolledDropdown direction="left">
<DropdownToggle caret>
{props.showCount === Number.POSITIVE_INFINITY ? "All" : props.showCount}
</DropdownToggle>
<DropdownMenu>
{RECOMMEND_NUM_OPTIONS.map(({ text, value }) => (
<DropdownItem
key={value}
onClick={(): void => props.onChangeShowCount(value)}
>
{text}
</DropdownItem>
))}
</DropdownMenu>
</UncontrolledDropdown>
</>
);
export const RecommendController = (props: Props) => {
const filteredCategories = useMemo(() => {
return CategoryOptions.filter(
(category) =>
category === "All" ||
!props.mergeLikeContest ||
!isLikeContestCategory(category)
);
}, [props.mergeLikeContest]);
return (
<>
<div>
<ButtonGroup className="mr-3">
{RecommendOptions.map((type) => (
<Button
key={type}
active={props.recommendOption === type}
onClick={() => props.onChangeRecommendOption(type)}
>
{type}
</Button>
))}
</ButtonGroup>
<ButtonGroup className="mr-3">
<UncontrolledDropdown>
<DropdownToggle caret>
{formatExcludeOption(props.excludeOption)}
</DropdownToggle>
<DropdownMenu>
{ExcludeOptions.map((option) => (
<DropdownItem
key={option}
onClick={(): void => props.onChangeExcludeOption(option)}
>
{formatExcludeOption(option)}
</DropdownItem>
))}
</DropdownMenu>
</UncontrolledDropdown>
</ButtonGroup>
<ButtonGroup className="mr-3">
<UncontrolledDropdown>
<DropdownToggle caret>
{props.categoryOption === "All"
? "Contest Category"
: props.categoryOption}
</DropdownToggle>
<DropdownMenu>
{filteredCategories.map((option) => (
<DropdownItem
key={option}
onClick={(): void => props.onChangeCategoryOption(option)}
>
{option}
</DropdownItem>
))}
</DropdownMenu>
</UncontrolledDropdown>
</ButtonGroup>
<CustomInput
type="switch"
id="switchRecommendExperimental"
inline
label={
<span role="img" aria-label="experimental">
🧪
</span>
}
checked={props.showExperimental}
onChange={() =>
props.onChangeExperimentalVisibility(!props.showExperimental)
}
/>
<CustomInput
type="switch"
id="switchMergeLikeContest"
inline
label={
<span role="img" aria-label="experimental">
Merge &quot;-Like&quot; Contests
</span>
}
checked={props.mergeLikeContest}
onChange={() =>
props.onChangeMergeLikeContest(!props.mergeLikeContest)
}
/>
</div>
<UncontrolledDropdown direction="left">
<DropdownToggle caret>
{props.showCount === Number.POSITIVE_INFINITY
? "All"
: props.showCount}
</DropdownToggle>
<DropdownMenu>
{RECOMMEND_NUM_OPTIONS.map(({ text, value }) => (
<DropdownItem
key={value}
onClick={(): void => props.onChangeShowCount(value)}
>
{text}
</DropdownItem>
))}
</DropdownMenu>
</UncontrolledDropdown>
</>
);
};
Loading