Skip to content

Commit

Permalink
[Overhaul] Implementation of a more consistent & modern UI (#899)
Browse files Browse the repository at this point in the history
* UPDATE: Refactored each gui "view" into a separate file, so it is less cluttered and not a monolithic file

* FIX: Rewrote the keydown handler to make it a bit nicer

* UPDATE: formatted code

* UPDATE: Documented changes

* FIX: Fixed a bug where the keydown handler didn't ignore a key if the back wasn't shown

* UPDATE: Reimplemented the title in the decks list and implemented a way of showing/hiding without the need of rerendering everything

* UPDATE: Updated class naming scheme

* UPDATE: Changed the deck list header style a bit

* UPDATE: cleaned some old code

* UPDATE: Another bit of code cleanup

* FIX: Fixed that the close button wasn't clickable

* UPDATE: Redesigned DecksListView

* UPDATE: Updated the DecksListView design

* UPDATE: Major reimplantation of the FlashcardsReviewView & again small changes to the DecksListView

* UPDATE: Redesigned the edit modal

* UPDATED: Renamed files, such that the naming convention is the same everywhere.

* UPDATE: Code cleanup & added comment

* UPDATE: Added modal styles to the edit modal & commented the styles file

* UPDATE: Documented code changes

* FIX: Fixed that the overflow behavior cause the buttons to move

* UPDATE: formatted with prettier

* FIX: Fixed mobile view

* FIX: Fixed that mobile view wasn't spanning the whole screen, like it used to do

* UPDATE: Formatted with prettier

* UPDATE: Shortened changelog as requested in the review

* FIX: Fixed unused variables warning

* FIX: Title was hidden for some reason

* UPDATE: Used is-mobile class, instead of is-phone

* UPDATE: Improved backbutton position

* UPDATE: Improved context positioning

* FIX: Implemented a better close condition

* FIX: removed debug code

* FIX: Fixed that the keydown handler did respond, even if the window was closed

* UPDATE: Updated changelog & formatted code

* FIX: Fixed parts, as requested in review

* Update changelog.md

* UPDATE: Added issue links to the changelog & formatted files

---------

Co-authored-by: Stephen Mwangi <mail@stephenmwangi.com>
  • Loading branch information
KyleKlus and st3v3nmw authored Mar 20, 2024
1 parent 7aeae67 commit d4b8d01
Show file tree
Hide file tree
Showing 12 changed files with 949 additions and 619 deletions.
6 changes: 3 additions & 3 deletions docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).

#### [Unreleased]

- Overhauled ui as described in issue [`#872`](https://github.com/st3v3nmw/obsidian-spaced-repetition/issues/872)
- Fixes key listener bug from issue [`#907`](https://github.com/st3v3nmw/obsidian-spaced-repetition/issues/907)
- Fixes bug from issue [`#773`](https://github.com/st3v3nmw/obsidian-spaced-repetition/issues/773)
- Unrelated Tags in Card Selection Modal since version 1.12.0 [`#908`](https://github.com/st3v3nmw/obsidian-spaced-repetition/issues/908)

#### [1.12.0](https://github.com/st3v3nmw/obsidian-spaced-repetition/compare/1.11.2...1.12.0)
Expand Down Expand Up @@ -33,9 +36,6 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).

#### [1.11.1](https://github.com/st3v3nmw/obsidian-spaced-repetition/compare/1.11.0...1.11.1)

> 22 January 2024
- Bump version to v1.11.1 [`#854`](https://github.com/st3v3nmw/obsidian-spaced-repetition/pull/854)
- chore: fix README to point to new project board [`#848`](https://github.com/st3v3nmw/obsidian-spaced-repetition/pull/848)
- chore: update pt-br.ts for Brazilian Portuguese translation [`#765`](https://github.com/st3v3nmw/obsidian-spaced-repetition/pull/765)
- chore: update dependencies [`#845`](https://github.com/st3v3nmw/obsidian-spaced-repetition/pull/845)
Expand Down
236 changes: 236 additions & 0 deletions src/gui/DeckListView.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import h from "vhtml";

import type SRPlugin from "src/main";
import { SRSettings } from "src/settings";
import { COLLAPSE_ICON } from "src/constants";
import { t } from "src/lang/helpers";
import { Deck } from "../Deck";
import {
DeckStats,
IFlashcardReviewSequencer as IFlashcardReviewSequencer,
} from "src/FlashcardReviewSequencer";
import { TopicPath } from "src/TopicPath";
import { FlashcardModalMode } from "./FlashcardModal";

export class DeckListView {
public plugin: SRPlugin;
public mode: FlashcardModalMode;
public modalContentEl: HTMLElement;

public view: HTMLDivElement;
public header: HTMLDivElement;
public title: HTMLDivElement;
public stats: HTMLDivElement;
public content: HTMLDivElement;

private reviewSequencer: IFlashcardReviewSequencer;
private settings: SRSettings;
private startReviewOfDeck: (deck: Deck) => void;

constructor(
plugin: SRPlugin,
settings: SRSettings,
reviewSequencer: IFlashcardReviewSequencer,
contentEl: HTMLElement,
startReviewOfDeck: (deck: Deck) => void,
) {
// Init properties
this.plugin = plugin;
this.settings = settings;
this.reviewSequencer = reviewSequencer;
this.modalContentEl = contentEl;
this.startReviewOfDeck = startReviewOfDeck;

// Build ui
this.init();
}

/**
* Initializes all static elements in the DeckListView
*/
init(): void {
this.view = this.modalContentEl.createDiv();
this.view.addClasses(["sr-deck-list", "sr-is-hidden"]);

this.header = this.view.createDiv();
this.header.addClass("sr-header");

this.title = this.header.createDiv();
this.title.addClass("sr-title");
this.title.setText(t("DECKS"));

this.stats = this.header.createDiv();
this.stats.addClass("sr-header-stats-container");
this._createHeaderStats();

this.content = this.view.createDiv();
this.content.addClass("sr-content");
}

/**
* Shows the DeckListView & rerenders dynamic elements
*/
show(): void {
this.mode = FlashcardModalMode.DecksList;

// Redraw in case the stats have changed
this._createHeaderStats();

this.content.empty();
for (const deck of this.reviewSequencer.originalDeckTree.subdecks) {
this._createTree(deck, this.content);
}

if (this.view.hasClass("sr-is-hidden")) {
this.view.removeClass("sr-is-hidden");
}
}

/**
* Hides the DeckListView
*/
hide() {
if (!this.view.hasClass("sr-is-hidden")) {
this.view.addClass("sr-is-hidden");
}
}

/**
* Closes the DeckListView
*/
close() {
this.hide();
}

// -> Header

private _createHeaderStats() {
const statistics: DeckStats = this.reviewSequencer.getDeckStats(TopicPath.emptyPath);
this.stats.empty();

this._createHeaderStatsContainer(t("TOTAL_CARDS"), statistics.totalCount, "sr-bg-red");
this._createHeaderStatsContainer(t("NEW_CARDS"), statistics.newCount, "sr-bg-blue");
this._createHeaderStatsContainer(t("DUE_CARDS"), statistics.dueCount, "sr-bg-green");
}

private _createHeaderStatsContainer(
statsLable: string,
statsNumber: number,
statsClass: string,
): void {
const statsContainer = this.stats.createDiv();
statsContainer.ariaLabel = statsLable;
statsContainer.addClasses([
"tag-pane-tag-count",
"tree-item-flair",
"sr-header-stats-count",
statsClass,
]);

const lable = statsContainer.createDiv();
lable.setText(statsLable + ":");

const number = statsContainer.createDiv();
number.setText(statsNumber.toString());
}

// -> Tree content

private _createTree(deck: Deck, container: HTMLElement): void {
const deckTree: HTMLElement = container.createDiv("tree-item sr-tree-item-container");
const deckTreeSelf: HTMLElement = deckTree.createDiv(
"tree-item-self tag-pane-tag is-clickable sr-tree-item-row",
);

const shouldBeInitiallyExpanded: boolean = this.settings.initiallyExpandAllSubdecksInTree;
let collapsed = !shouldBeInitiallyExpanded;
let collapseIconEl: HTMLElement | null = null;
if (deck.subdecks.length > 0) {
collapseIconEl = deckTreeSelf.createDiv("tree-item-icon collapse-icon");
collapseIconEl.innerHTML = COLLAPSE_ICON;
(collapseIconEl.childNodes[0] as HTMLElement).style.transform = collapsed
? "rotate(-90deg)"
: "";
}

const deckTreeInner: HTMLElement = deckTreeSelf.createDiv("tree-item-inner");
const deckTreeInnerText: HTMLElement = deckTreeInner.createDiv("tag-pane-tag-text");
deckTreeInnerText.innerHTML += <span class="tag-pane-tag-self">{deck.deckName}</span>;

const deckTreeOuter: HTMLDivElement = deckTreeSelf.createDiv();
deckTreeOuter.addClasses(["tree-item-flair-outer", "sr-tree-stats-container"]);

const deckStats = this.reviewSequencer.getDeckStats(deck.getTopicPath());
this._createStats(deckStats, deckTreeOuter);

const deckTreeChildren: HTMLElement = deckTree.createDiv("tree-item-children");
deckTreeChildren.style.display = collapsed ? "none" : "block";
if (deck.subdecks.length > 0) {
collapseIconEl.addEventListener("click", (e) => {
if (collapsed) {
(collapseIconEl.childNodes[0] as HTMLElement).style.transform = "";
deckTreeChildren.style.display = "block";
} else {
(collapseIconEl.childNodes[0] as HTMLElement).style.transform =
"rotate(-90deg)";
deckTreeChildren.style.display = "none";
}

// We stop the propagation of the event so that the click event for deckTreeSelf doesn't get called
// if the user clicks on the collapse icon
e.stopPropagation();
collapsed = !collapsed;
});
}

// Add the click handler to deckTreeSelf instead of deckTreeInner so that it activates
// over the entire rectangle of the tree item, not just the text of the topic name
// https://github.com/st3v3nmw/obsidian-spaced-repetition/issues/709
deckTreeSelf.addEventListener("click", () => {
this.startReviewOfDeck(deck);
});

for (const subdeck of deck.subdecks) {
this._createTree(subdeck, deckTreeChildren);
}
}

private _createStats(statistics: DeckStats, statsWrapper: HTMLDivElement) {
statsWrapper.empty();

this._createStatsContainer(
t("TOTAL_CARDS"),
statistics.totalCount,
"sr-bg-red",
statsWrapper,
);
this._createStatsContainer(t("NEW_CARDS"), statistics.newCount, "sr-bg-blue", statsWrapper);
this._createStatsContainer(
t("DUE_CARDS"),
statistics.dueCount,
"sr-bg-green",
statsWrapper,
);
}

private _createStatsContainer(
statsLable: string,
statsNumber: number,
statsClass: string,
statsWrapper: HTMLDivElement,
): void {
const statsContainer = statsWrapper.createDiv();

statsContainer.ariaLabel = statsLable;

statsContainer.addClasses([
"tag-pane-tag-count",
"tree-item-flair",
"sr-tree-stats-count",
statsClass,
]);

statsContainer.setText(statsNumber.toString());
}
}
Loading

0 comments on commit d4b8d01

Please sign in to comment.