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

Refactor index.js to webcomponents #3498

Merged
merged 79 commits into from
May 2, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
79 commits
Select commit Hold shift + click to select a range
e7326c0
Add lit
jorg-vr Mar 24, 2022
a527d22
Create frontend sortbutons
jorg-vr Mar 24, 2022
a5fac1e
Allow sorting in the backend
jorg-vr Mar 25, 2022
b52e6ea
Refactor index.js to query param state with listeners
jorg-vr Mar 30, 2022
e8f6220
Initialise query params on page refresh
jorg-vr Mar 30, 2022
b5ded39
Allow sort to interact correctly with backend
jorg-vr Mar 30, 2022
8b6a456
Allow removing sort and make listerners more abstract
jorg-vr Mar 30, 2022
a72a059
Fix searchfilter options
jorg-vr Mar 31, 2022
cb08873
Create search field component
jorg-vr Mar 31, 2022
40557d2
Create searchable dropdown
jorg-vr Apr 1, 2022
887303f
Add tab completion
jorg-vr Apr 1, 2022
cdc0620
Add search tokens
jorg-vr Apr 1, 2022
fb644da
Clear filter after selecting token
jorg-vr Apr 1, 2022
19e049a
Create filter icon component
jorg-vr Apr 4, 2022
aee5174
Replace tokens by filter buttons
jorg-vr Apr 5, 2022
0750af6
Use clickable tokens on all relevant pages
jorg-vr Apr 5, 2022
29512bc
Fix course list page
jorg-vr Apr 5, 2022
78f212c
Let course membership tabs use query params
jorg-vr Apr 7, 2022
d140fac
Fix toggled dropdown
jorg-vr Apr 7, 2022
04356be
Allow setting of baseUrl
jorg-vr Apr 7, 2022
142b578
Remove refactored index file
jorg-vr Apr 7, 2022
4b807a3
Use imports instead of window object
jorg-vr Apr 7, 2022
136a80f
Update address bar on pagination
jorg-vr Apr 8, 2022
6ba2bb6
Merge branch 'develop' into sort_tables
jorg-vr Apr 8, 2022
3616991
Fix linting
jorg-vr Apr 8, 2022
f659813
Fix rubocop issues
jorg-vr Apr 8, 2022
2724403
Fix tests
jorg-vr Apr 8, 2022
6e43aee
Fix test
jorg-vr Apr 8, 2022
5390b9a
Limit search dropdown length with overflow auto
jorg-vr Apr 11, 2022
447e5d1
Remove sort by unindexed number column
jorg-vr Apr 11, 2022
ca3fefe
Add tests on ordering
jorg-vr Apr 12, 2022
0736103
Merge branch 'develop' into sort_tables
jorg-vr Apr 12, 2022
7b63371
Fix linting
jorg-vr Apr 12, 2022
b2e4d6b
Introduce shadowless lit ellement
jorg-vr Apr 20, 2022
8c7aaa2
Fix linting
jorg-vr Apr 20, 2022
923a29f
Update formating for search action
jorg-vr Apr 20, 2022
85afb91
Fix indentation
jorg-vr Apr 20, 2022
c571d6e
Merge branch 'develop' into sort_tables
jorg-vr Apr 21, 2022
7e03603
Switch from snake to camel case
jorg-vr Apr 21, 2022
c11e130
Make update adressbar check more explicit
jorg-vr Apr 21, 2022
4a84e7f
Push state when chaning page and only update pagination on param chan…
jorg-vr Apr 21, 2022
35016ab
Add padding left of tokens to improve allignement
jorg-vr Apr 26, 2022
d4e1aed
Move all search components to search pack
jorg-vr Apr 26, 2022
5c04c8a
Remove double dodona.searchQuery assignment
jorg-vr Apr 26, 2022
856be38
Replacecxase by string pattern
jorg-vr Apr 26, 2022
fd372ff
Remove nill safety inside if
jorg-vr Apr 26, 2022
04dc1af
Move tests to correct position in file
jorg-vr Apr 26, 2022
ed332df
Improve tokenfield padding
jorg-vr Apr 26, 2022
8e92f09
Move style to class
jorg-vr Apr 26, 2022
b8d5804
Remove unused code that utilises blodhound and tokenfield
jorg-vr Apr 26, 2022
0f60cec
Merge branch 'develop' into sort_tables
jorg-vr Apr 26, 2022
9e5b3c2
Remove unused imports
jorg-vr Apr 26, 2022
1d1ef0b
Fix bootstrap js not working on search pages
jorg-vr Apr 27, 2022
02fc7c2
Improve dropdown layout
jorg-vr Apr 27, 2022
be45091
No wrap inside dropdowns
jorg-vr Apr 27, 2022
869d752
Add commments to elements
jorg-vr Apr 27, 2022
cec4ef5
Add commments to elements
jorg-vr Apr 27, 2022
adff22d
Fix linting
jorg-vr Apr 27, 2022
daa3a3a
Fix onpopstate event for chrome
jorg-vr Apr 28, 2022
a2b8cee
Update sort button styling
jorg-vr Apr 28, 2022
71911e7
Fix empty search suggestions box
jorg-vr Apr 28, 2022
1e26071
Replace uglifier with terser
jorg-vr Apr 28, 2022
14f18d2
Update app/assets/javascripts/components/sort_button.ts
jorg-vr Apr 28, 2022
5b129c4
Update app/assets/javascripts/search.ts
jorg-vr Apr 28, 2022
134ed6a
Update app/assets/javascripts/search.ts
jorg-vr Apr 28, 2022
12a9e48
Update app/assets/javascripts/search.ts
jorg-vr Apr 28, 2022
70b9f2a
Declare custom type for filtercollections
jorg-vr Apr 28, 2022
23703bd
Merge branch 'sort_tables' of github.com:dodona-edu/dodona into sort_…
jorg-vr Apr 28, 2022
ebc9763
Merge branch 'develop' into sort_tables and adjust dropdownfilters to…
jorg-vr Apr 29, 2022
eb3fd01
Remove code duplication with abstract class
jorg-vr Apr 29, 2022
afaa893
clean up dropdown initiation code
jorg-vr Apr 29, 2022
2968d22
Get rid of weird indent after decorators
jorg-vr Apr 29, 2022
3c3fb71
Fix margin between chips
jorg-vr May 2, 2022
26919d0
Remove sort buttons
jorg-vr May 2, 2022
60267ec
Remove abstract class keyword
jorg-vr May 2, 2022
65dce87
Update app/assets/stylesheets/components/dropdown.css.scss
jorg-vr May 2, 2022
c63ab1f
Update app/assets/stylesheets/components/search_field.css.scss
jorg-vr May 2, 2022
7828674
Update app/assets/stylesheets/components/search_field.css.scss
jorg-vr May 2, 2022
c67f6a4
make search token seperate class to revert impact on other pages
jorg-vr May 2, 2022
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
4 changes: 2 additions & 2 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ gem 'jsbundling-rails', '~> 1.0.2'
# This is still used for all javascript in app/assets/javascripts
gem 'sprockets-rails', '~> 3.4.2'

# Use Uglifier as compressor for JavaScript assets
gem 'uglifier', '>= 4.1.20'
# Use Terser as compressor for JavaScript assets
gem 'terser', '>= 1.1.1'

# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
gem 'jbuilder', '~> 2.11.5'
Expand Down
6 changes: 3 additions & 3 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -467,14 +467,14 @@ GEM
activesupport (>= 3)
attr_required (>= 0.0.5)
httpclient (>= 2.4)
terser (1.1.8)
execjs (>= 0.3.0, < 3)
test-prof (1.0.8)
thor (1.2.1)
tilt (2.0.10)
timeout (0.2.0)
tzinfo (2.0.4)
concurrent-ruby (~> 1.0)
uglifier (4.2.0)
execjs (>= 0.3.0, < 3)
unicode-display_width (2.1.0)
validate_email (0.1.6)
activemodel (>= 3.0)
Expand Down Expand Up @@ -582,9 +582,9 @@ DEPENDENCIES
slack-notifier (~> 2.4.0)
sprockets-rails (~> 3.4.2)
stackprof (~> 0.2.19)
terser (>= 1.1.1)
test-prof (~> 1.0.8)
tzinfo-data
uglifier (>= 4.1.20)
web-console (~> 4.2.0)
webmock
will_paginate (~> 3.3.1)
Expand Down
69 changes: 32 additions & 37 deletions app/assets/javascripts/components/dropdown_filter.ts
Original file line number Diff line number Diff line change
@@ -1,46 +1,15 @@
import { html, LitElement, TemplateResult } from "lit";
import { html, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators.js";

type Label = { id: string | number, name: string };
import { FilterCollection, Label, FilterCollectionElement } from "components/filter_collection_element";
import { ShadowlessLitElement } from "components/shadowless_lit_element";

@customElement("dodona-dropdown-filter")
export class DropdownFilter extends LitElement {
@property({ type: Boolean })
multi: boolean;
@property({ type: Array })
labels: Array<Label> = [];
export class DropdownFilter extends FilterCollectionElement {
@property()
color: (s: Label) => string;
@property({ type: Array })
selected: string[];
@property()
type: string;

// don't use shadow dom
createRenderRoot(): Element {
return this;
}

toggleLabel(s: Label): void {
if (this.isSelected(s)) {
if (dodona.deleteTokenFromSearch) {
dodona.deleteTokenFromSearch(this.type, s.name);
}
} else {
if (dodona.addTokenToSearch) {
dodona.addTokenToSearch(this.type, s.name);
}
}
}

isSelected(s: Label): boolean {
return this.selected.includes(s.name);
}

getSelectedLabels(): Array<Label> {
return this.labels.filter(s => this.isSelected(s));
}

render(): TemplateResult {
if (this.labels.length === 0) {
return html``;
Expand All @@ -58,8 +27,8 @@ export class DropdownFilter extends LitElement {
${this.labels.map(s => html`
<li><span class="dropdown-item-text ">
<div class="form-check">
<input class="form-check-input" type="${this.multi?"checkbox":"radio"}" .checked=${this.isSelected(s)} @click="${() => this.toggleLabel(s)}" id="check-${this.type}-${s.id}">
<label class="form-check-label" for="check-${this.type}-${s.id}">
<input class="form-check-input" type="${this.multi?"checkbox":"radio"}" .checked=${this.isSelected(s)} @click="${() => this.toggle(s)}" id="check-${this.param}-${s.id}">
<label class="form-check-label" for="check-${this.param}-${s.id}">
${s.name}
</label>
</div>
Expand All @@ -70,3 +39,29 @@ export class DropdownFilter extends LitElement {
`;
}
}

@customElement("dodona-dropdown-filters")
export class DropdownFilters extends ShadowlessLitElement {
@property( { type: Array })
filterCollections: [string, FilterCollection][];

render(): TemplateResult {
if (!this.filterCollections) {
return html``;
}

return html`
${this.filterCollections.map(([type, c]) => html`
<dodona-dropdown-filter
.labels=${c.data}
.color=${c.color}
.paramVal=${c.paramVal}
.param=${c.param}
.multi=${c.multi}
.type=${type}
>
</dodona-dropdown-filter>
`)}
`;
}
}
84 changes: 84 additions & 0 deletions app/assets/javascripts/components/filter_button.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { customElement, property } from "lit/decorators.js";
import { css, html, LitElement, TemplateResult } from "lit";
import { ref } from "lit/directives/ref.js";
import { searchQuery } from "search";
import { ShadowlessLitElement } from "components/shadowless_lit_element";

/**
* This is a very simple clickable component
* It sets a given query param to a given value when clicked
* multi should be specified in case of array query params
*/
@customElement("dodona-filter-button")
export class FilterButton extends LitElement {
jorg-vr marked this conversation as resolved.
Show resolved Hide resolved
@property({ type: String })
param: string;
@property({ type: String })
value: string;
@property({ type: Boolean })
multi = false;

static styles = css`
:host {
cursor: pointer;
}
`;

addFilter(): void {
if (this.multi) {
const selected = new Set(searchQuery.arrayQueryParams.params.get(this.param));
selected.add(this.value);
searchQuery.arrayQueryParams.updateParam(this.param, Array.from(selected));
} else {
searchQuery.queryParams.updateParam(this.param, this.value);
}
}

render(): TemplateResult {
return html`<slot @click=${() => this.addFilter()}></slot>`;
}
}

/**
* This is a clickable filter icon
* When clicked it sets the query param 'filter' to the given value
*/
@customElement("dodona-filter-icon")
export class FilterIcon extends ShadowlessLitElement {
@property({ type: String })
value: string;
@property({ type: String })
title: string;

element: Element;

initialiseTooltip(e: Element): void {
if (e) {
this.element = e;
const tooltip = window.bootstrap.Tooltip.getInstance(this.element);
if (!tooltip) {
new window.bootstrap.Tooltip(this.element);
}
}
}

disconnectedCallback(): void {
const tooltip = window.bootstrap.Tooltip.getInstance(this.element);
tooltip.hide();
super.disconnectedCallback();
}

render(): TemplateResult {
return html`
<dodona-filter-button param="filter" .value=${this.value}>
<i class="mdi mdi-filter-outline mdi-18 filter-icon"
title="${this.title}"
data-bs-toggle="tooltip"
data-bs-placement="top"
${ref(r => this.initialiseTooltip(r))}
>
</i>
</dodona-filter-button>
`;
}
}
103 changes: 103 additions & 0 deletions app/assets/javascripts/components/filter_collection_element.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { property } from "lit/decorators.js";
import { searchQuery } from "search";
import { ShadowlessLitElement } from "components/shadowless_lit_element";

export type Label = {id: string, name: string};
export type FilterCollection = {
data: Label[],
multi: boolean,
color: (l: Label) => string,
paramVal: (l: Label) => string,
param: string
};

export class FilterCollectionElement extends ShadowlessLitElement {
@property()
param: string;
@property({ type: Boolean })
multi: boolean;
@property()
paramVal: (l: Label) => string;
@property({ type: Array })
labels: Array<Label> = [];
@property({ state: true })
private multiSelected: string[] = [];
@property({ state: true })
private singleSelected = "";

update(changedProperties: Map<string, unknown>): void {
if ((changedProperties.has("param") || changedProperties.has("multi")) &&
this.param !== undefined && this.multi !== undefined) {
if (this.multi) {
this.multiSubscribeToQueryParams();
this.select = this.multiSelect;
this.unSelect = this.multiUnSelect;
this.isSelected = this.multiIsSelected;
} else {
this.singleSubscribeToQueryParams();
this.select = this.singleSelect;
this.unSelect = this.singleUnSelect;
this.isSelected = this.singleIsSelected;
}
}
super.update(changedProperties);
}

private str(label: Label): string {
return this.paramVal(label).toString();
}

private multiUnSelect(label: Label): void {
searchQuery.arrayQueryParams.updateParam(this.param, this.multiSelected.filter(s => s !== this.str(label)));
}

private multiIsSelected(label: Label): boolean {
return this.multiSelected.includes(this.str(label));
}

private multiSelect(label: Label): void {
searchQuery.arrayQueryParams.updateParam(this.param, [...this.multiSelected, this.str(label)]);
searchQuery.queryParams.updateParam("filter", undefined);
}

private multiSubscribeToQueryParams(): void {
this.multiSelected = searchQuery.arrayQueryParams.params.get(this.param) || [];
searchQuery.arrayQueryParams.subscribeByKey(this.param, (k, o, n) => {
this.multiSelected = n || [];
});
}

private singleUnSelect(label: Label): void {
searchQuery.queryParams.updateParam(this.param, undefined);
}

private singleSelect(label: Label): void {
searchQuery.queryParams.updateParam(this.param, this.str(label));
searchQuery.queryParams.updateParam("filter", undefined);
}

private singleIsSelected(label: Label): boolean {
return this.singleSelected === this.str(label);
}

private singleSubscribeToQueryParams(): void {
this.singleSelected = searchQuery.queryParams.params.get(this.param);
searchQuery.queryParams.subscribeByKey(this.param, (k, o, n) => this.singleSelected = n || "");
}

isSelected = this.singleIsSelected;
unSelect = this.singleUnSelect;
select = this.singleSelect;

toggle(label: Label): void {
if (this.isSelected(label)) {
this.unSelect(label);
} else {
this.select(label);
}
}

getSelectedLabels(): Label[] {
return this.labels.filter( l => this.isSelected(l));
}
}
Loading