generated from obsidianmd/obsidian-sample-plugin
-
Notifications
You must be signed in to change notification settings - Fork 183
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Overhaul] Implementation of a more consistent & modern UI (#899)
* 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
Showing
12 changed files
with
949 additions
and
619 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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()); | ||
} | ||
} |
Oops, something went wrong.