Skip to content
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

feat: add availability filter for the issues #189

Merged
merged 11 commits into from
Jan 5, 2025
Merged
17 changes: 10 additions & 7 deletions src/home/fetch-github/fetch-and-display-previews.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { applyAvatarsToIssues, renderGitHubIssues } from "../rendering/render-gi
import { renderOrgHeaderLabel } from "../rendering/render-org-header";
import { closeModal } from "../rendering/render-preview-modal";
import { filterIssuesBySearch } from "../sorting/filter-issues-by-search";
import { filterIssuesByAvailability } from "../sorting/filter-issues-by-availability";
import { Sorting } from "../sorting/generate-sorting-buttons";
import { sortIssuesController } from "../sorting/sort-issues-controller";
import { checkCacheIntegrityAndSyncTasks } from "./cache-integrity";
Expand All @@ -12,6 +13,12 @@ export type Options = {
ordering: "normal" | "reverse";
};

export let isFilteringAvailableIssues = true;

export function swapAvailabilityFilter() {
isFilteringAvailableIssues = !isFilteringAvailableIssues;
}

// start at view based on URL
export let isProposalOnlyViewer = new URLSearchParams(window.location.search).get("proposal") === "true";

Expand Down Expand Up @@ -87,20 +94,16 @@ export async function displayGitHubIssues({
const sortedIssues = sortIssuesController(cachedTasks, sorting, options);
let sortedAndFiltered = sortedIssues.filter(getProposalsOnlyFilter(isProposalOnlyViewer));
sortedAndFiltered = filterIssuesByOrganization(sortedAndFiltered);
sortedAndFiltered = isFilteringAvailableIssues ? filterIssuesByAvailability(sortedAndFiltered) : sortedAndFiltered;
renderGitHubIssues(sortedAndFiltered, skipAnimation);
applyAvatarsToIssues();
}

export async function searchDisplayGitHubIssues({
searchText,
skipAnimation = false,
}: {
searchText: string;
skipAnimation?: boolean;
}) {
export async function searchDisplayGitHubIssues({ searchText, skipAnimation = false }: { searchText: string; skipAnimation?: boolean }) {
const searchResult = filterIssuesBySearch(searchText);
let filteredIssues = searchResult.filter(getProposalsOnlyFilter(isProposalOnlyViewer));
filteredIssues = filterIssuesByOrganization(filteredIssues);
filteredIssues = isFilteringAvailableIssues ? filterIssuesByAvailability(filteredIssues) : filteredIssues;
renderGitHubIssues(filteredIssues, skipAnimation);
applyAvatarsToIssues();
}
8 changes: 8 additions & 0 deletions src/home/sorting/filter-issues-by-availability.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { GitHubIssue } from "../github-types";

export function filterIssuesByAvailability(issues: GitHubIssue[]) {
return issues.filter((issue) => {
if (issue.assignee) return false;
return true;
});
}
104 changes: 83 additions & 21 deletions src/home/sorting/sorting-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ import { displayGitHubIssues, searchDisplayGitHubIssues } from "../fetch-github/
import { renderErrorInModal } from "../rendering/display-popup-modal";
import { proposalViewToggle } from "../rendering/render-github-issues";
import { Sorting } from "./generate-sorting-buttons";
import { isFilteringAvailableIssues, swapAvailabilityFilter } from "../fetch-github/fetch-and-display-previews";

export class SortingManager {
private _lastChecked: HTMLInputElement | null = null;
private _toolBarFilters: HTMLElement;
private _filterTextBox: HTMLInputElement;
private _filtersDiv: HTMLElement;
private _sortingButtons: HTMLElement;
private _instanceId: string;
private _sortingState: { [key: string]: "unsorted" | "ascending" | "descending" } = {}; // Track state for each sorting option
Expand All @@ -20,8 +21,12 @@ export class SortingManager {

// Initialize sorting buttons first
this._sortingButtons = this._generateSortingButtons(sortingOptions);
// Then initialize filter text box
this._filterTextBox = this._generateFilterTextBox();
// Initialize filters div
this._filtersDiv = this._generateFiltersDiv();
// Add filter search box to filters div
this._filtersDiv.appendChild(this._generateFilterTextBox());
// Add filter available issues button to filters div
this._filtersDiv.appendChild(this._generateFilterAvailableIssuesButton());

// Initialize sorting states to 'unsorted' for all options
sortingOptions.forEach((option) => {
Expand All @@ -30,10 +35,16 @@ export class SortingManager {
}

public render() {
this._toolBarFilters.appendChild(this._filterTextBox);
this._toolBarFilters.appendChild(this._filtersDiv);
this._toolBarFilters.appendChild(this._sortingButtons);
}

private _generateFiltersDiv() {
const div = document.createElement("div");
div.className = "filters";
return div;
}

private _generateFilterTextBox() {
const textBox = document.createElement("input");
textBox.type = "text";
Expand Down Expand Up @@ -115,10 +126,19 @@ export class SortingManager {
return textBox;
}

private _resetSearchBar() {
const filterTextBox = this._filtersDiv.querySelector('input[type="text"]') as HTMLInputElement;
filterTextBox.value = "";
const newURL = new URL(window.location.href);
newURL.searchParams.delete("search");
window.history.replaceState({}, "", newURL.toString());
}

private _resetSortButtons() {
this._sortingButtons.querySelectorAll('input[type="radio"]').forEach((input) => {
if (input instanceof HTMLInputElement) {
input.checked = false;
this._sortingState[input.value] = "unsorted";
input.setAttribute("data-ordering", "");
}
});
Expand Down Expand Up @@ -148,6 +168,54 @@ export class SortingManager {
return buttons;
}

private _generateFilterAvailableIssuesButton() {
const input = document.createElement("input");
input.type = "button";
input.value = "Unassigned";
input.id = `filter-availability-${this._instanceId}`;

input.addEventListener("click", () => {
swapAvailabilityFilter();
input.value = isFilteringAvailableIssues ? "Unassigned" : "All Issues";

try {
// Clear search when applying the filter
this._resetSearchBar();

const { sortingOption, sortingOrder } = this._detectSortingState();
void displayGitHubIssues({
sorting: sortingOption as Sorting,
options: { ordering: sortingOrder },
});
} catch (error) {
renderErrorInModal(error as Error);
}
});

return input;
}

private _detectSortingState() {
let sortingOption;
let sortingOrder = "normal";

for (const option of Object.keys(this._sortingState)) {
const order = this._sortingState[option];

if (order !== "unsorted") {
sortingOption = option;

if (order === "descending") {
sortingOrder = "normal";
} else if (order === "ascending") {
sortingOrder = "reverse";
}
break;
}
}
return { sortingOption, sortingOrder };
}

private _createRadioButton(option: string): HTMLInputElement {
const input = document.createElement("input");
input.type = "radio";
Expand All @@ -168,35 +236,26 @@ export class SortingManager {
const currentOrdering = input.getAttribute("data-ordering");
let newOrdering: string;

// Reset sort buttons
this._resetSortButtons();

// Determine the new ordering based on the current state
if (currentOrdering === "normal") {
newOrdering = "reverse";
this._sortingState[option] = "ascending";
} else if (currentOrdering === "reverse") {
newOrdering = "disabled";
this._sortingState[option] = "unsorted";
} else {
newOrdering = "normal";
this._sortingState[option] = "descending";
}

// Apply the new ordering state
input.setAttribute("data-ordering", newOrdering);
input.parentElement?.childNodes.forEach((node) => {
if (node instanceof HTMLInputElement) {
node.setAttribute("data-ordering", "");
}
});

// Clear search when applying a different sort
this._filterTextBox.value = "";
const newURL = new URL(window.location.href);
newURL.searchParams.delete("search");
window.history.replaceState({}, "", newURL.toString());

// Reset other buttons
input.parentElement?.childNodes.forEach((node) => {
if (node instanceof HTMLInputElement) {
node.setAttribute("data-ordering", "");
}
});
this._resetSearchBar();

if (newOrdering === "disabled") {
this._lastChecked = null;
Expand All @@ -209,7 +268,10 @@ export class SortingManager {

// Apply the sorting based on the new state (normal or reverse)
try {
void displayGitHubIssues({ sorting: option as Sorting, options: { ordering: newOrdering } });
void displayGitHubIssues({
sorting: option as Sorting,
options: { ordering: newOrdering },
});
} catch (error) {
renderErrorCatch(error as ErrorEvent);
}
Expand Down
Loading
Loading