Skip to content

Commit

Permalink
Merge pull request #5214 from dodona-edu/fix/series-icon
Browse files Browse the repository at this point in the history
Always use svg series icon
  • Loading branch information
jorg-vr authored Jan 9, 2024
2 parents d4bfbf2 + 2f1b38c commit 0b6619a
Show file tree
Hide file tree
Showing 6 changed files with 131 additions and 70 deletions.
107 changes: 107 additions & 0 deletions app/assets/javascripts/components/series_icon.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { ShadowlessLitElement } from "components/meta/shadowless_lit_element";
import { html, svg, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators.js";

export type SeriesProgressStatus = "not-yet-begun" | "started" | "completed" | "wrong";
export type SeriesDeadlineStatus = "met" | "missed" | null;
export type SeasonTheme = "christmas" | "december" | "valentine" | "mario-day" | "pi-day" | null;

/**
* @element d-series-icon
*
* this icon visualizes the status of a series for a give user
*
* @cssprop --icon-color - the color of the icon
* @cssprop --icon-background-color - the background color of the icon
* @cssprop --deadline-icon-color - the color of the deadline icon
* @cssprop --deadline-icon-background-color - the background color of the deadline icon
*
* @attr {string} deadline - The status of the deadline, if any
* @attr {string} season - The season theme to apply, if any
* @attr {string} progress - the progress of the user in the series
* @attr {number} size - the size of the icon in pixels
* @attr {string} status - the status of the series, displayed as a tooltip
*/
@customElement("d-series-icon")
export class SeriesIcon extends ShadowlessLitElement {
@property({ type: String })
progress: SeriesProgressStatus = "not-yet-begun";
@property({ type: String })
deadline: SeriesDeadlineStatus = null;
@property({ type: String })
season: SeasonTheme = null;
@property({ type: Number })
size = 40;
@property({ type: String })
status = "";

static PROGRESS_ICONS: Record<SeriesProgressStatus, string> = {
"not-yet-begun": "mdi-school",
"started": "mdi-thumb-up",
"completed": "mdi-check-bold",
"wrong": "mdi-close",
};

static DEADLINE_ICONS: Record<SeriesDeadlineStatus, string> = {
"met": "mdi-alarm-check",
"missed": "mdi-alarm-off",
};

get progress_icon(): string {
return SeriesIcon.PROGRESS_ICONS[this.progress];
}

get deadline_icon(): string {
return SeriesIcon.DEADLINE_ICONS[this.deadline];
}

get deadline_class(): string {
return this.deadline ? `deadline-${this.deadline}` : "";
}

protected render(): TemplateResult {
return html`
<svg viewBox="0 0 40 40"
style="width: ${this.size}px; height: ${this.size}px; "
class="series-icon ${this.season} ${this.progress} ${this.deadline_class}"
title="${this.status}"
data-bs-toggle="tooltip"
data-bs-placement="top"
>
${ this.progress === "completed" && this.deadline !== "missed" ? svg`
<defs>
<linearGradient id="rainbow" gradientTransform="rotate(135, 0.5, 0.5)" >
<stop stop-color="var(--red)" offset="1%" />
<stop stop-color="var(--orange)" offset="25%" />
<stop stop-color="var(--yellow)" offset="40%" />
<stop stop-color="var(--green)" offset="60%" />
<stop stop-color="var(--blue)" offset="75%" />
<stop stop-color="var(--purple)" offset="99%" />
</linearGradient>
</defs>
` : ""}
<g class="icon-base" >
<circle cx="50%" cy="50%" r= "50%" fill="var(--icon-color)" class="outer-circle"></circle>
<circle cx="50%" cy="50%" r= "42%" fill="var(--icon-background-color)"></circle>
<foreignObject x="8" y="8" height="24" width="24" style="color: var(--icon-color)">
<i class="mdi ${this.progress_icon}"></i>
</foreignObject>
${this.deadline_icon ? svg`
<circle cx="33" cy="34.5" r= "11" fill="var(--deadline-icon-background-color)"></circle>
<foreignObject x="24" y="26" height="18" width="18" style="color: var(--deadline-icon-color)">
<i class="mdi mdi-18 ${this.deadline_icon}"></i>
</foreignObject>
` : ""}
</g>
${this.season ? svg`
<foreignObject width="100%" height="100%" class="overlay">
<div></div>
</foreignObject>
` : ""}
</svg>
`;
}
}
11 changes: 4 additions & 7 deletions app/assets/javascripts/course.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,11 +109,10 @@ function initCourseMembers(): void {
init();
}

const TABLE_WRAPPER_SELECTOR = ".series-activities-table-wrapper";
const SKELETON_TABLE_SELECTOR = ".skeleton-table";
const ICON_SELECTOR = ".series-icon";

class Series {
private readonly id: number;
public readonly id: number;
private url: string;
private loaded: boolean;
private loading: boolean;
Expand Down Expand Up @@ -141,10 +140,8 @@ class Series {

reselect(card: HTMLElement): void {
this.url = card.dataset.seriesUrl;
const tableWrapper: HTMLElement | null = card.querySelector(TABLE_WRAPPER_SELECTOR);
const skeleton = tableWrapper?.querySelector(SKELETON_TABLE_SELECTOR);
// if tableWrapper is null the series is empty (no activities) => series is always loaded
this.loaded = skeleton === null || tableWrapper === null;
// if the icon is not found, the series is not loaded
this.loaded = card.dataset.loaded === "true";
this.loading = false;
this._top = card.getBoundingClientRect().top + window.scrollY;
this._bottom = this.top + card.getBoundingClientRect().height;
Expand Down
16 changes: 8 additions & 8 deletions app/helpers/series_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,20 @@ def breadcrumb_series_path(series, user)

# returns [class, icon]
def series_status_progress(series, user)
return %w[not-yet-begun mdi-school] unless series.started?(user: user)
return %w[completed mdi-check-bold] if series.completed?(user: user)
return %w[wrong mdi-close] if series.wrong?(user: user)
return 'not-yet-begun' unless series.started?(user: user)
return 'completed' if series.completed?(user: user)
return 'wrong' if series.wrong?(user: user)

%w[started mdi-thumb-up]
'started'
end

# returns [class, icon]
def series_status_deadline(series, user)
return [nil, nil] unless series.deadline?
return %w[deadline-missed mdi-alarm-off] if series.missed_deadline?(user)
return %w[deadline-met mdi-alarm-check] if series.completed_before_deadline?(user) && !series.completed?(user: user)
return nil unless series.deadline?
return 'missed' if series.missed_deadline?(user)
return 'met' if series.completed_before_deadline?(user) && !series.completed?(user: user)

[nil, nil]
nil
end

def series_status(series, user)
Expand Down
1 change: 1 addition & 0 deletions app/javascript/packs/application_pack.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import "components/saved_annotations/saved_annotation_list";
import "components/progress_bar";
import "components/theme_picker";
import { userState } from "state/Users";
import "components/series_icon.ts";

// Initialize clipboard.js
initClipboard();
Expand Down
3 changes: 2 additions & 1 deletion app/views/series/_series.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@

<div class="card card-supporting-text series"
id="series-card-<%= series.id %>"
data-series-url="<%= user&.id ? series_path(series, format: :js, user_id: user.id) : series_path(series, format: :js) %>">
data-series-url="<%= user&.id ? series_path(series, format: :js, user_id: user.id) : series_path(series, format: :js) %>"
data-loaded="<%= loaded %>">
<div class="card-subtitle">
<a class="anchor" id="<%= series.anchor %>" name="series-<%= series.id %>"></a>
<div class="hidden-print">
Expand Down
63 changes: 9 additions & 54 deletions app/views/series/_series_status.html.erb
Original file line number Diff line number Diff line change
@@ -1,58 +1,13 @@
<% # arguments: loaded (bool), series (Series), user (User) %>
<% size ||= 40 %>
<% if defined?(series).nil? %>
<svg viewBox="0 0 40 40" style="width: <%= size %>px; height: <%= size %>px; " class="series-icon">
<circle cx="50%" cy="50%" r= "50%" fill="var(--icon-color)" class="outer-circle"></circle>
<circle cx="50%" cy="50%" r= "42%" fill="var(--icon-background-color)"></circle>
<foreignObject x="8" y="8" height="24" width="24" style="color: var(--icon-color)">
<i class="mdi mdi-school"></i>
</foreignObject>
</svg>
<% elsif loaded && user.present? %>
<% deadline_class, deadline_icon = series_status_deadline(series, user) %>
<% progress_class, progress_icon = series_status_progress(series, user) %>
<% overlay_class = series_status_overlay %>
<svg viewBox="0 0 40 40"
style="width: <%= size %>px; height: <%= size %>px; "
class="series-icon <%= overlay_class %> <%= progress_class %> <%= deadline_class %>"
title="<%= series_status(series, user) %>"
data-bs-toggle="tooltip"
data-bs-placement="top"
>
<defs>
<linearGradient id="rainbow" gradientTransform="rotate(135, 0.5, 0.5)" >
<stop stop-color="var(--red)" offset="1%" />
<stop stop-color="var(--orange)" offset="25%" />
<stop stop-color="var(--yellow)" offset="40%" />
<stop stop-color="var(--green)" offset="60%" />
<stop stop-color="var(--blue)" offset="75%" />
<stop stop-color="var(--purple)" offset="99%" />
</linearGradient>
</defs>
<g class="icon-base" >
<circle cx="50%" cy="50%" r= "50%" fill="var(--icon-color)" class="outer-circle"></circle>
<circle cx="50%" cy="50%" r= "42%" fill="var(--icon-background-color)"></circle>
<foreignObject x="8" y="8" height="24" width="24" style="color: var(--icon-color)">
<i class="mdi <%= progress_icon %>"></i>
</foreignObject>
<% if deadline_icon %>
<circle cx="33" cy="34.5" r= "11" fill="var(--deadline-icon-background-color)"></circle>
<foreignObject x="24" y="26" height="18" width="18" style="color: var(--deadline-icon-color)">
<i class="mdi mdi-18 <%= deadline_icon %>"></i>
</foreignObject>
<% end %>
</g>
<% if overlay_class %>
<foreignObject width="100%" height="100%" class="overlay">
<div></div>
</foreignObject>
<% end %>
</svg>
<% if defined?(loaded) && loaded && user.present? %>
<d-series-icon
size="<%= size %>"
season="<%= series_status_overlay %>"
progress="<%= series_status_progress(series, user) %>"
deadline="<%= series_status_deadline(series, user) %>"
status="<%= series_status(series, user) %>"
></d-series-icon>
<% else %>
<div class="card-title-icon skeleton">
<i class="mdi mdi-school"></i>
</div>
<d-series-icon size="<%= size %>"></d-series-icon>
<% end %>

0 comments on commit 0b6619a

Please sign in to comment.