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

Make search actions and options more discoverable #5430

Merged
merged 16 commits into from
Apr 22, 2024
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export class DropdownFilter extends FilterCollectionElement {

return html`
<div class="dropdown dropdown-filter">
<a class="btn btn-outline dropdown-toggle" href="#" role="button" id="dropdownMenuLink" data-bs-toggle="dropdown" aria-expanded="false">
<a class="token token-bordered" href="#" role="button" id="dropdownMenuLink" data-bs-toggle="dropdown" aria-expanded="false">
${this.getSelectedLabels().map( s => html`<i class="mdi mdi-circle mdi-12 mdi-colored-accent accent-${this.color(s)} left-icon"></i>`)}
${i18n.t(`js.dropdown.${this.multi?"multi":"single"}.${this.type}`)}
<i class="mdi mdi-chevron-down mdi-18 right-icon"></i>
Expand Down
7 changes: 5 additions & 2 deletions app/assets/javascripts/components/search/filter_tabs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { customElement, property } from "lit/decorators.js";
import { FilterCollectionElement, Label } from "components/search/filter_collection_element";
import { watchMixin } from "components/meta/watch_mixin";

type TabInfo = {id: string, name: string, title?: string, count?: number};

/**
* This component inherits from FilterCollectionElement.
* It represent a list of tabs, where each tab shows a filtered set of the search results
Expand All @@ -20,7 +22,7 @@ export class FilterTabs extends watchMixin(FilterCollectionElement) {
@property()
paramVal = (label: Label): string => label.id.toString();
@property({ type: Array })
labels: {id: string, name: string, title: string}[];
labels: TabInfo[];

processClick(e: Event, label: Label): void {
if (!this.isSelected(label)) {
Expand All @@ -42,9 +44,10 @@ export class FilterTabs extends watchMixin(FilterCollectionElement) {
<div class="card-tab">
<ul class="nav nav-tabs" role="tablist">
${this.labels.map(label => html`
<li role="presentation" data-bs-toggle="tooltip" data-bs-title="${label.title ? label.title : ""}" data-bs-trigger="hover">
<li role="presentation" data-bs-toggle="tooltip" title="${label.title ? label.title : ""}" data-bs-trigger="hover">
<a href="#" @click=${e => this.processClick(e, label)} class="${this.isSelected(label) ? "active" : ""}">
${label.name}
${label.count ? html`<span class="badge rounded-pill colored-secondary" id="${label.id}-count">${label.count}</span>` : ""}
</a>
</li>
`)}
Expand Down
11 changes: 10 additions & 1 deletion app/assets/javascripts/components/search/loading_bar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import { customElement, property } from "lit/decorators.js";
import { search } from "search";
import { DodonaElement } from "components/meta/dodona_element";
import { watchMixin } from "components/meta/watch_mixin";

Check warning on line 5 in app/assets/javascripts/components/search/loading_bar.ts

View check run for this annotation

Codecov / codecov/patch

app/assets/javascripts/components/search/loading_bar.ts#L5

Added line #L5 was not covered by tests

/**
* This component represents a loading bar.
Expand All @@ -10,7 +11,7 @@
* @element d-loading-bar
*/
@customElement("d-loading-bar")
export class LoadingBar extends DodonaElement {
export class LoadingBar extends watchMixin(DodonaElement) {
@property({ type: Boolean, attribute: "search-based" })
searchBased = false;

Expand All @@ -24,6 +25,14 @@
}
}

watch = {
searchBased: () => {
if (this.searchBased) {
search.loadingBars.push(this);
}
}
};

show(): void {
this.loading = true;
}
Expand Down
124 changes: 21 additions & 103 deletions app/assets/javascripts/components/search/search_actions.ts
Original file line number Diff line number Diff line change
@@ -1,79 +1,19 @@
import { html, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators.js";
import { Toast } from "toast";
import { fetch, ready } from "utilities";
import { fetch } from "utilities";
import { searchQueryState } from "state/SearchQuery";
import { DodonaElement } from "components/meta/dodona_element";
import { i18n } from "i18n/i18n";

export type SearchOption = {search: Record<string, string>, type?: string, text: string};
export type SearchAction = {
url?: string,
type?: string,
filterValue?: string,
text: string,
action?: string,
js?: string,
confirm?: string,
icon: string
};

const isSearchOption = (opt): opt is SearchOption => (opt as SearchOption).search !== undefined;
const isSearchAction = (act): act is SearchAction => (act as SearchAction).js !== undefined || (act as SearchAction).action !== undefined || (act as SearchAction).url !== undefined;

/**
* This component represents a SearchOption using a checkbox to be used in a dropdown list
* The checkbox tracks whether the searchoption is curently active
*
* @element d-search-option
*
* @prop {SearchOption} searchOption - the search option which can be activated or disabled
* @prop {number} key - unique identifier used to differentiate from other search options
*/
@customElement("d-search-option")
export class SearchOptionElement extends DodonaElement {
@property({ type: Object })
searchOption: SearchOption;
@property( { type: Number })
key: number;

get active(): boolean {
return Object.entries(this.searchOption.search).every(([key, value]) => {
return searchQueryState.queryParams.get(key) == value.toString();
});
}

performSearch(): void {
if (!this.active) {
Object.entries(this.searchOption.search).forEach(([key, value]) => {
searchQueryState.queryParams.set(key, value.toString());
});
} else {
Object.keys(this.searchOption.search).forEach(key => {
searchQueryState.queryParams.set(key, undefined);
});
}
}

render(): TemplateResult {
return html`
<li><span class="dropdown-item-text ">
<div class="form-check">
<input
class="form-check-input"
type="checkbox"
.checked=${this.active}
@click="${() => this.performSearch()}"
id="check-${this.searchOption.type}-${this.key}"
>
<label class="form-check-label" for="check-${this.searchOption.type}-${this.key}">
${this.searchOption.text}
</label>
</div>
</span></li>
`;
}
}

/**
* This component represents a dropdown containing a combination of SearchOptions and SearchActions
*
Expand All @@ -84,15 +24,9 @@
@customElement("d-search-actions")
export class SearchActions extends DodonaElement {
@property({ type: Array })
actions: (SearchOption|SearchAction)[] = [];

getSearchOptions(): Array<SearchOption> {
return this.actions.filter(isSearchOption);
}

getSearchActions(): Array<SearchAction> {
return this.actions.filter(isSearchAction);
}
actions: SearchAction[] = [];
@property({ type: String, attribute: "filter-param" })
filterParam = undefined;

async performAction(action: SearchAction): Promise<boolean> {
if (!action.action && !action.js && !action.url) {
Expand Down Expand Up @@ -130,39 +64,23 @@
return false;
}

get filteredActions(): SearchAction[] {
if (!this.filterParam) {
return this.actions;

Check warning on line 69 in app/assets/javascripts/components/search/search_actions.ts

View check run for this annotation

Codecov / codecov/patch

app/assets/javascripts/components/search/search_actions.ts#L69

Added line #L69 was not covered by tests
}

render(): TemplateResult {
return html`
<div class="dropdown actions" id="kebab-menu">
<a class="btn btn-icon dropdown-toggle" data-bs-toggle="dropdown">
<i class="mdi mdi-dots-vertical"></i>
</a>
<ul class="dropdown-menu dropdown-menu-end">
${this.getSearchOptions().length > 0 ? html`
<li><h6 class='dropdown-header'>${i18n.t("js.options")}</h6></li>
` : html``}
${this.getSearchOptions().map((opt, id) => html`
<d-search-option .searchOption=${opt}
.key=${id}>
</d-search-option>
`)}
const filterValue = searchQueryState.queryParams.get(this.filterParam);
return this.actions.filter(action => action.filterValue === undefined || action.filterValue === filterValue);
}

${this.getSearchActions().length > 0 ? html`
<li><h6 class='dropdown-header'>${i18n.t("js.actions")}</h6></li>
` : html``}
${this.getSearchActions().map(action => html`
<li>
<a class="action dropdown-item"
data-type="${action.type}"
@click=${() => this.performAction(action)}
>
<i class='mdi mdi-${action.icon} mdi-18'></i>
${action.text}
</a>
</li>
`)}
</ul>
</div>
`;
render(): TemplateResult[] {
return this.filteredActions.map(action => html`
<a class="btn btn-outline with-icon ml-2"
@click=${() => this.performAction(action)}
>
<i class='mdi mdi-${action.icon} mdi-18'></i>
${action.text}
</a>
`);
}
}
88 changes: 88 additions & 0 deletions app/assets/javascripts/components/search/search_option.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { customElement, property } from "lit/decorators.js";
import { DodonaElement } from "components/meta/dodona_element";
import { searchQueryState } from "state/SearchQuery";
import { html, TemplateResult } from "lit";

Check warning on line 4 in app/assets/javascripts/components/search/search_option.ts

View check run for this annotation

Codecov / codecov/patch

app/assets/javascripts/components/search/search_option.ts#L1-L4

Added lines #L1 - L4 were not covered by tests
import { i18n } from "i18n/i18n";

export type Option = {param: string, label: string};

/**
* This component represents a boolean option for search using a checkbox
* The checkbox tracks whether the search option is curently active
*
* @element d-search-option
*
* @prop {string} param - the name of the search parameter
* @prop {string} label - the label to be displayed next to the checkbox
*/
@customElement("d-search-option")
export class SearchOption extends DodonaElement {
@property({ type: String })
param = "";
@property({ type: String })
label = "";

get active(): boolean {
return searchQueryState.queryParams.get(this.param) !== undefined;
}

toggle(): void {

Check warning on line 29 in app/assets/javascripts/components/search/search_option.ts

View check run for this annotation

Codecov / codecov/patch

app/assets/javascripts/components/search/search_option.ts#L29

Added line #L29 was not covered by tests
if (this.active) {
searchQueryState.queryParams.set(this.param, undefined);
} else {
searchQueryState.queryParams.set(this.param, "true");

Check warning on line 33 in app/assets/javascripts/components/search/search_option.ts

View check run for this annotation

Codecov / codecov/patch

app/assets/javascripts/components/search/search_option.ts#L31-L33

Added lines #L31 - L33 were not covered by tests
}
}

render(): TemplateResult {
return html`
<div class="form-check">
<input
class="form-check-input"
type="checkbox"
.checked=${this.active}
@click="${() => this.toggle()}"

Check warning on line 44 in app/assets/javascripts/components/search/search_option.ts

View check run for this annotation

Codecov / codecov/patch

app/assets/javascripts/components/search/search_option.ts#L44

Added line #L44 was not covered by tests
id="check-${this.param}"
>
<label class="form-check-label" for="check-${this.param}">
${this.label}
</label>
</div>
`;
}
}

/**
* This component represents a list op boolean options for search
* The options are displayed in a dropdown
*
* @element d-search-options
*
* @prop {Option[]} options - the list of options to be displayed
*/
@customElement("d-search-options")
export class SearchOptions extends DodonaElement {
@property({ type: Array })
options: Option[] = [];

render(): TemplateResult {
if (this.options.length === 0) {
return html``;

Check warning on line 70 in app/assets/javascripts/components/search/search_option.ts

View check run for this annotation

Codecov / codecov/patch

app/assets/javascripts/components/search/search_option.ts#L70

Added line #L70 was not covered by tests
}

return html`
<div class="dropdown">
<a class="btn btn-icon dropdown-toggle" data-bs-toggle="dropdown">
<i class="mdi mdi-dots-vertical"></i>
</a>
<ul class="dropdown-menu dropdown-menu-end">
${this.options.map(o => html`
<li><span class="dropdown-item-text ">
<d-search-option param="${o.param}" label="${o.label}"></d-search-option>
</span></li>
`)}
</ul>
</div>
`;
}
}
4 changes: 3 additions & 1 deletion app/assets/javascripts/components/search/search_token.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ export class SearchToken extends FilterCollectionElement {
${ this.getSelectedLabels().map( label => html`
<div class="token accent-${this.color(label)}">
<span class="token-label">${label.name}</span>
<a href="#" class="close" tabindex="-1" @click=${e => this.processClick(e, label)}>×</a>
<a href="#" class="close" tabindex="-1" @click=${e => this.processClick(e, label)}>
<i class="mdi mdi-close mdi-18"></i>
</a>
</div>
`)}
`;
Expand Down
Loading