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

Always use svg series icon #5214

Merged
merged 7 commits into from
Jan 9, 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
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";

Check warning on line 3 in app/assets/javascripts/components/series_icon.ts

View check run for this annotation

Codecov / codecov/patch

app/assets/javascripts/components/series_icon.ts#L1-L3

Added lines #L1 - L3 were not covered by tests

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
Comment on lines +14 to +17
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these specified in a general css file? Why not add them here to make this component self contained?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right now it is a shadowless lit component, which forces all style definitions to be in the global scope.
Changing it to use the shadow dom would not allow any use of globally defined classes. In this this would mean we had to redefine the material design icons used within this class. I do not think that is desired.

Sadly there is no way to 'partially' use the shadow dom. Which is why we also aren't using the shadow dom in most other components for now.

*
* @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 {
jorg-vr marked this conversation as resolved.
Show resolved Hide resolved
@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 @@

# 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)

Check warning on line 14 in app/helpers/series_helper.rb

View check run for this annotation

Codecov / codecov/patch

app/helpers/series_helper.rb#L13-L14

Added lines #L13 - L14 were not covered by tests

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

Check warning on line 16 in app/helpers/series_helper.rb

View check run for this annotation

Codecov / codecov/patch

app/helpers/series_helper.rb#L16

Added line #L16 was not covered by tests
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 %>