Skip to content

Commit

Permalink
Merge pull request #4299 from dodona-edu/feature/teacher-course-cards
Browse files Browse the repository at this point in the history
Show more relevant info for teachers on course cards
  • Loading branch information
jorg-vr authored Jan 23, 2023
2 parents f99cd5a + 6002477 commit fed95df
Show file tree
Hide file tree
Showing 35 changed files with 744 additions and 169 deletions.
39 changes: 39 additions & 0 deletions app/assets/javascripts/components/loading_bar.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { html, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators.js";
import { ShadowlessLitElement } from "components/shadowless_lit_element";
import { searchQuery } from "search";

/**
* This component represents a loading bar.
* It will be triggered by search
*
* @element d-loading-bar
*/
@customElement("d-loading-bar")
export class LoadingBar extends ShadowlessLitElement {
@property({ type: Boolean, state: true })
loading = false;

constructor() {
super();
searchQuery.loadingBars.push(this);
}

show(): void {
this.loading = true;
}

hide(): void {
this.loading = false;
}

render(): TemplateResult {
return html`
<div class="dodona-progress dodona-progress-indeterminate" style="visibility: ${this.loading ? "visible" : "hidden"};">
<div class="progressbar bar bar1" style="width: 0%;"></div>
<div class="bufferbar bar bar2" style="width: 100%;"></div>
<div class="auxbar bar bar3" style="width: 0%;"></div>
</div>
`;
}
}
63 changes: 63 additions & 0 deletions app/assets/javascripts/components/progress_bar.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { html, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators.js";
import { ShadowlessLitElement } from "components/shadowless_lit_element";
import { initTooltips, ready } from "util.js";

/**
* This component displays a progress bar consisting of consecutive divs
* The divs are scaled according to the given values
* The divs have an opacity according to their index
*
* @element d-progress-bar
*
* @prop {number[]} values - Pass the data as an array.
* @prop {string} titleKey - The key of the title to be displayed in the tooltip.
*/
@customElement("d-progress-bar")
export class ProgressBar extends ShadowlessLitElement {
@property({ type: Array })
values: Array<number>;

@property({ type: String, attribute: "title-key" })
titleKey: string;

get valuesSum(): number {
return Object.values(this.values).reduce((a, b) => a + b, 0);
}

private getWidth(value: number): number {
return 100 * value / this.valuesSum;
}

private getOpacity(key: number): number {
return (key / (this.values.length - 1)) * 0.8 + 0.2;
}

private getTitle(value: number, index: number): string {
return I18n.t(this.titleKey + I18n.t(this.titleKey + ".key", index), { index: index, smart_count: value });
}

updated(changedProperties: Map<string, any>): void {
initTooltips(this);
super.updated(changedProperties);
}

constructor() {
super();
// Reload when I18n is available
ready.then(() => this.requestUpdate());
}

private renderBar(value: number, index: number): TemplateResult {
return html`<div
class="bar"
style="width: ${this.getWidth(value)}%; opacity: ${this.getOpacity(index)}; "
title=${this.getTitle(value, index)}
data-bs-toggle='tooltip'>
</div>`;
}

render(): TemplateResult[] {
return this.values.map( (v, i) => this.renderBar(v, i)).reverse();
}
}
6 changes: 6 additions & 0 deletions app/assets/javascripts/i18n/i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ export class I18n extends Polyglot {
}
}

constructor() {
super();
// set default locale, avoids a lot of errors when the locale is not yet set
this.locale = "en";
}

get locale(): string {
return super.locale();
}
Expand Down
14 changes: 14 additions & 0 deletions app/assets/javascripts/i18n/translations.json
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,13 @@
"no_selection": "You must select at least one item before being able to download solutions.",
"options": "Options",
"programming_languages": "Programming Languages",
"progress_bar": {
"series-admin-progress": {
"key": ".singular |||| .plural",
"plural": "One user has completed %{index} activities |||| %{smart_count} users have completed %{index} activities",
"singular": "One user has completed one activity |||| %{smart_count} users have completed one activity"
}
},
"question": {
"state": {
"answered": "Answered",
Expand Down Expand Up @@ -709,6 +716,13 @@
"no_selection": "Er moet ten minste één iets gekozen worden om oplossingen te kunnen downloaden.",
"options": "Opties",
"programming_languages": "Programmeertalen",
"progress_bar": {
"series-admin-progress": {
"key": ".singular |||| .plural",
"plural": "Eén gebruiker heeft %{index} oefeningen correct opgelost |||| %{smart_count} gebruikers hebben %{index} oefeningen correct opgelost",
"singular": "Eén gebruiker heeft één oefening correct opgelost |||| %{smart_count} gebruikers hebben één oefening correct opgelost"
}
},
"question": {
"state": {
"answered": "Beantwoord",
Expand Down
12 changes: 4 additions & 8 deletions app/assets/javascripts/search.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { createDelayer, fetch, getURLParameter, updateArrayURLParameter, updateURLParameter } from "util.js";
import { InactiveTimeout } from "auto_reload";
import { LoadingBar } from "components/loading_bar";
const RELOAD_SECONDS = 2;


Expand Down Expand Up @@ -56,6 +57,7 @@ export class SearchQuery {
arrayQueryParams: QueryParameters<string[]> = new QueryParameters<string[]>();
queryParams: QueryParameters<string> = new QueryParameters<string>();
localStorageKey?: string;
loadingBars: LoadingBar[] = [];

setRefreshElement(refreshElement: string): void {
this.refreshElement = refreshElement;
Expand Down Expand Up @@ -180,7 +182,7 @@ export class SearchQuery {
const url = this.addParametersToUrl();
const localIndex = ++this.searchIndex;

this.updateProgressFilterVisibility("visible");
this.loadingBars.forEach(bar => bar.show());
fetch(updateURLParameter(url, "format", "js"), {
headers: {
"accept": "text/javascript"
Expand All @@ -193,7 +195,7 @@ export class SearchQuery {
this.appliedIndex = localIndex;
eval(data);
}
this.updateProgressFilterVisibility("hidden");
this.loadingBars.forEach(bar => bar.hide());

// if there is local storage key => update the value to reuse later
if (this.localStorageKey) {
Expand All @@ -203,12 +205,6 @@ export class SearchQuery {
});
}

updateProgressFilterVisibility(state: string): void {
if (document.getElementById("progress-filter")) {
document.getElementById("progress-filter").style.visibility = state;
}
}

/**
* fetch params from localStorage using the localStorageKey if present and apply them to the current url
*/
Expand Down
13 changes: 10 additions & 3 deletions app/assets/javascripts/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,15 +95,22 @@ async function initCSRF() {
});
}

function initTooltips() {
function initTooltips(root = document) {
// First remove dead tooltips
const tooltips = document.querySelectorAll(".tooltip");
const tooltips = root.querySelectorAll(".tooltip");
for (const tooltip of tooltips) {
tooltip.remove();
}

// Then reinitialize tooltips
$("[data-bs-toggle=\"tooltip\"]").tooltip({ container: "body", trigger: "hover" });
const elements = root.querySelectorAll("[data-bs-toggle=\"tooltip\"]");
for (const element of elements) {
const tooltip = window.bootstrap.Tooltip.getOrCreateInstance(element);
if (element.title) {
tooltip.setContent({ ".tooltip-inner": element.title });
element.removeAttribute("title");
}
}
}

function tooltip(target, message, disappearAfter=1000) {
Expand Down
10 changes: 10 additions & 0 deletions app/assets/stylesheets/base.css.scss
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,10 @@
color: $info;
}

.colored-secondary {
color: $secondary;
}

figcaption {
margin: 0 10px;
}
Expand All @@ -110,3 +114,9 @@ svg {
--svg-color-incorrect: #{$danger};
--svg-color-warning: #{$warning};
}

.spread-line {
display: flex;
justify-content: space-between;
flex-direction: row;
}
1 change: 1 addition & 0 deletions app/assets/stylesheets/components.css.scss
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
@import "components/dropdown-filter.css.scss";
@import "components/courselabel-edit.css.scss";
@import "components/action-card.css.scss";
@import "components/progress_bar.css.scss";

.flex-spacer {
flex-grow: 1;
Expand Down
12 changes: 6 additions & 6 deletions app/assets/stylesheets/components/action-card.css.scss
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,6 @@
small {
font-size: 65%;
}

.spread-line {
display: flex;
justify-content: space-between;
flex-direction: row;
}
}

.card-title-icon {
Expand Down Expand Up @@ -57,6 +51,12 @@
--progress-chart-color: var(--on-clickable-card-color);
}

d-progress-bar {
.bar {
background-color: var(--on-clickable-card-color);
}
}

&:hover,
&:focus,
&:active {
Expand Down
17 changes: 17 additions & 0 deletions app/assets/stylesheets/components/card.css.scss
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,14 @@ $card-supporting-text-padding: 16px;
flex-direction: row;
align-items: center;
}

.course-subtitle {
margin-bottom: 0.75rem;
margin-top: 0.25rem;
justify-content: space-between;
display: flex;
width: 100%;
}
}

.card-title.card-title-colored {
Expand All @@ -81,6 +89,15 @@ $card-supporting-text-padding: 16px;
.progress-chart.colored-muted {
--progress-chart-color: #{$on-primary-container};
}

.series-icon {
--icon-color: #{$on-primary-container};
--icon-background-color: #{$primary-container};
}
}

.home-summary-card.course .card-title {
height: 111px;
}

a.card-title-link,
Expand Down
9 changes: 9 additions & 0 deletions app/assets/stylesheets/components/progress_bar.css.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
d-progress-bar {
display: block;

.bar {
height: 8px;
display: inline-block;
background-color: $success;
}
}
35 changes: 3 additions & 32 deletions app/assets/stylesheets/components/table-toolbar.css.scss
Original file line number Diff line number Diff line change
Expand Up @@ -102,19 +102,13 @@
}

.dodona-progress > .progressbar {
background-color: $progress-bar-bg;
background-color: $on-secondary-container;
z-index: 1;
left: 0;
}

.dodona-progress > .bufferbar {
background-image:
linear-gradient(
to right,
rgba(255, 255, 255, 0.7),
rgba(255, 255, 255, 0.7)
),
linear-gradient(to right, $secondary, $secondary);
background-color: $secondary-container;
z-index: 0;
left: 0;
}
Expand All @@ -125,17 +119,12 @@

.dodona-progress.dodona-progress-indeterminate > .bar1,
.dodona-progress.dodona-progress-indeterminate > .bar3 {
background-color: $progress-bar-bg;
background-color: $on-secondary-container;
animation-duration: 2s;
animation-iteration-count: infinite;
animation-timing-function: linear;
}

.mdl-progress.mdl-progress-indeterminate > .bar3 {
background-image: none;
animation-name: indeterminate2;
}

.dodona-progress.dodona-progress-indeterminate > .bar1 {
animation-name: indeterminate1;
}
Expand All @@ -156,21 +145,3 @@
width: 0%;
}
}

@keyframes indeterminate2 {
0%,
50% {
left: 0%;
width: 0%;
}

75% {
left: 0%;
width: 25%;
}

100% {
left: 100%;
width: 0%;
}
}
4 changes: 4 additions & 0 deletions app/assets/stylesheets/material_icons.css.scss
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@
line-height: 74px;
}

.mdi.mdi-vam::before {
vertical-align: middle;
}

.visible-xs.mdi::before {
margin-top: 12px;
}
Expand Down
2 changes: 1 addition & 1 deletion app/controllers/pages_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ def home
@year = params[:year] || @years.max
@courses = courses.select { |c| c.year == @year }
@favorite_courses = course_memberships.select(&:favorite).map(&:course)
@homepage_series = courses.map { |c| c.homepage_series(0) }.flatten.sort_by(&:deadline)
@homepage_series = courses.map { |c| c.homepage_series(current_user, 0) }.flatten.sort_by(&:deadline)

@jump_back_in = current_user.jump_back_in
else
Expand Down
Loading

0 comments on commit fed95df

Please sign in to comment.