Skip to content

Commit

Permalink
Fixed progress bar, added new practice mode, removed kanji preview
Browse files Browse the repository at this point in the history
  • Loading branch information
The-Library committed Feb 18, 2025
1 parent 376227e commit 3655d12
Show file tree
Hide file tree
Showing 14 changed files with 1,198 additions and 496 deletions.
11 changes: 6 additions & 5 deletions rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,18 @@ import replace from "@rollup/plugin-replace";

const header = `// ==UserScript==
// @name Extra Practice
// @namespace https://github.com/mrpassiontea/WaniKani-Extra-Practice
// @version 2025-02-08
// @description Practice your current level's Radicals and Kanji
// @namespace https://github.com/mrpassiontea/Extra-Practice
// @version 2.0.0
// @description Practice your current level's Radicals and Kanji with standard, english -> Kanji, and combination mode!
// @author @mrpassiontea
// @match *://*.wanikani.com/
// @match https://www.wanikani.com/
// @match *://*.wanikani.com/dashboard
// @match *://*.wanikani.com/dashboard?*
// @copyright 2025, mrpassiontea
// @grant none
// @grant window.onurlchange
// @require https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js
// @require file://${process.cwd()}/dist/extra-practice.user.js
// @require https://unpkg.com/wanakana@5.3.1/wanakana.min.js
// @license MIT; http://opensource.org/licenses/MIT
// @run-at document-end
Expand All @@ -24,7 +25,7 @@ const header = `// ==UserScript==
export default {
input: "src/index.js",
output: {
file: "dist/wanikani-extra-practice.user.js",
file: "dist/extra-practice.user.js",
format: "iife",
sourcemap: true,
intro: header
Expand Down
59 changes: 0 additions & 59 deletions src/components/modals/kanjiSelection/kanjiGrid.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,56 +57,6 @@ class KanjiGrid {
);
}

createKanjiPreview(kanji) {
return $("<div>")
.addClass("kanji-preview")
.css({
display: "none",
position: "absolute",
top: "100%",
left: "50%",
transform: "translateX(-50%)",
backgroundColor: theme.colors.gray[800],
padding: theme.spacing.md,
borderRadius: theme.borderRadius.md,
zIndex: theme.zIndex.modal + 1,
minWidth: "200px",
boxShadow: "0 4px 6px rgba(0, 0, 0, 0.1)",
color: theme.colors.white
})
.append(
$("<div>")
.addClass("kanji-meanings")
.css({
marginBottom: theme.spacing.sm,
fontWeight: theme.typography.fontWeight.bold
})
.text(kanji.meanings.map(m => m.meaning).join(", ")),
$("<div>")
.addClass("kanji-readings")
.css({
fontSize: theme.typography.fontSize.sm,
color: theme.colors.gray[300]
})
.text(kanji.readings.map(r => r.reading).join(", ")),
$("<div>")
.addClass("kanji-radicals")
.css({
marginTop: theme.spacing.sm,
borderTop: `1px solid ${theme.colors.gray[600]}`,
paddingTop: theme.spacing.sm,
fontSize: theme.typography.fontSize.xs
})
.append(
$("<span>")
.text("Radicals: ")
.css({ color: theme.colors.gray[400] }),
$("<span>")
.text(kanji.radicals.map(r => r.meaning).join(", "))
)
);
}

createKanjiElement(kanji) {
const $element = $("<div>")
.addClass("kanji-selection-item")
Expand All @@ -125,19 +75,10 @@ class KanjiGrid {
.text(kanji.character)
);

const $preview = this.createKanjiPreview(kanji);
$element.append($preview);

$element
.on("click", () => {
const isCurrentlySelected = this.selectedKanji.has(kanji.id);
this.updateKanjiSelection($element, kanji, !isCurrentlySelected);
})
.on("mouseenter", () => {
$preview.show();
})
.on("mouseleave", () => {
$preview.hide();
});

return $element;
Expand Down
153 changes: 100 additions & 53 deletions src/components/modals/kanjiSelection/kanjiSelectionModal.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { modalTemplate, styles, theme } from "../../../constants/index";
import { modalTemplate, styles, theme, PRACTICE_MODES } from "../../../constants/index";
import { MODAL_STATES, EVENTS } from "./types";
import KanjiGrid from "./kanjiGrid";

class KanjiSelectionModal {
constructor(kanji) {
constructor(kanji, allUnlockedKanji) {
this.kanji = kanji;
this.allUnlockedKanji = allUnlockedKanji;
this.selectedMode = PRACTICE_MODES.STANDARD;
this.state = MODAL_STATES.READY;
this.totalKanji = kanji.length;
this.$modal = null;
Expand All @@ -22,72 +24,109 @@ class KanjiSelectionModal {
if (callback) callback(data);
}

updateSelectAllButton(selectedCount) {
const selectAllButton = $("#ep-practice-modal-select-all");
const isAllSelected = selectedCount === this.totalKanji;

selectAllButton
.text(isAllSelected ? "Deselect All" : "Select All")
.css({
color: isAllSelected ? theme.colors.error : theme.colors.white,
borderColor: isAllSelected ? theme.colors.error : theme.colors.white,
'&:hover': {
borderColor: isAllSelected ? theme.colors.error : theme.colors.kanji
}
});
}
validateSelection(selectedCount) {
const minRequired = {
[PRACTICE_MODES.STANDARD]: 1,
[PRACTICE_MODES.ENGLISH_TO_KANJI]: 4,
[PRACTICE_MODES.COMBINED]: 4
};

updateStartButton(selectedCount) {
const required = minRequired[this.selectedMode];
const isValid = selectedCount >= required;
const startButton = $("#ep-practice-modal-start");
const baseStyles = {
...styles.practiceModal.buttons.start.base,
backgroundColor: theme.colors.kanji,
'&:hover': {
backgroundColor: theme.colors.kanji,
opacity: 0.9
}
};

if (selectedCount > 0) {

if (isValid) {
startButton
.prop("disabled", false)
.text(`Start Review (${selectedCount} Selected)`)
.css({
...baseStyles,
opacity: "1",
pointerEvents: "inherit"
...styles.practiceModal.buttons.start.base,
...styles.practiceModal.buttons.start.kanji,
opacity: 1,
cursor: "pointer"
});
} else {
startButton
.prop("disabled", true)
.text("Start Review (0 Selected)")
.text(`Select at least ${required} kanji`)
.css({
...baseStyles,
...styles.practiceModal.buttons.start.disabled
...styles.practiceModal.buttons.start.base,
...styles.practiceModal.buttons.start.kanji,
opacity: 0.5,
cursor: "not-allowed"
});
}
}

updateSelectAllButton(selectedCount) {
const selectAllButton = $("#ep-practice-modal-select-all");
const isAllSelected = selectedCount === this.totalKanji;

selectAllButton
.text(isAllSelected ? "Deselect All" : "Select All")
.css({
color: isAllSelected ? theme.colors.error : theme.colors.white,
borderColor: isAllSelected ? theme.colors.error : theme.colors.white,
'&:hover': {
borderColor: isAllSelected ? theme.colors.error : theme.colors.kanji
}
});
}

handleSelectionChange(selectedKanji) {
const selectedCount = selectedKanji.size;
this.updateSelectAllButton(selectedCount);
this.updateStartButton(selectedCount);
this.validateSelection(selectedCount);
}

createModeSelector() {
const $container = $("<div>")
.css(styles.practiceModal.modeSelector.container);

const $label = $("<div>")
.text("Select Practice Mode")
.css(styles.practiceModal.modeSelector.label);

const $options = $("<div>")
.css(styles.practiceModal.modeSelector.options);

const createOption = (mode, label) => {
const $option = $("<button>")
.text(label)
.css({
...styles.practiceModal.modeSelector.option.base,
...(this.selectedMode === mode ? styles.practiceModal.modeSelector.option.selected : {})
})
.on("click", () => {
$options.find("button").css(styles.practiceModal.modeSelector.option.base);
$option.css({
...styles.practiceModal.modeSelector.option.base,
...styles.practiceModal.modeSelector.option.selected
});

this.selectedMode = mode;
const currentSelection = this.kanjiGrid.getSelectedKanji();
this.validateSelection(currentSelection.length);
});
return $option;
};

$options.append(
createOption(PRACTICE_MODES.STANDARD, "Standard Practice"),
createOption(PRACTICE_MODES.ENGLISH_TO_KANJI, "English → Kanji"),
createOption(PRACTICE_MODES.COMBINED, "Combined Practice")
);

return $container.append($label, $options);
}

async render() {
this.$modal = $(modalTemplate).appendTo("body");

$("#username").text($("p.user-summary__username:first").text());

// Apply themed styles
this.$modal.css(styles.practiceModal.backdrop);
$("#ep-practice-modal-welcome").css({
...styles.practiceModal.welcomeText.container,
'& h2': {
color: theme.colors.kanji
}
});

$("#ep-practice-modal-welcome").css(styles.practiceModal.welcomeText.container);
$("#ep-practice-modal-welcome h1").css(styles.practiceModal.welcomeText.username);
$("#ep-practice-modal-welcome h2")
.text("Please select the Kanji characters you would like to practice")
Expand All @@ -96,17 +135,18 @@ class KanjiSelectionModal {
opacity: 0.9
});

const $modeSelector = this.createModeSelector();
$modeSelector.insertAfter("#ep-practice-modal-welcome");

$("#ep-practice-modal-footer").css(styles.practiceModal.footer);
$("#ep-practice-modal-content").css(styles.practiceModal.contentWrapper);

// Apply kanji-specific button styles
// Initial disabled state with kanji color scheme
$("#ep-practice-modal-start").css({
...styles.practiceModal.buttons.start.base,
backgroundColor: theme.colors.kanji,
'&:hover': {
backgroundColor: theme.colors.kanji,
opacity: 0.9
}
...styles.practiceModal.buttons.start.kanji,
opacity: 0.5,
cursor: "not-allowed"
});

$("#ep-practice-modal-select-all").css({
Expand All @@ -132,9 +172,6 @@ class KanjiSelectionModal {
const $grid = await this.kanjiGrid.render();
$("#ep-practice-modal-grid").replaceWith($grid);

this.updateStartButton(0);

// Event handlers
$("#ep-practice-modal-select-all").on("click", () => {
const isSelectingAll = $("#ep-practice-modal-select-all").text() === "Select All";
this.kanjiGrid.toggleAllKanji(isSelectingAll);
Expand All @@ -146,8 +183,18 @@ class KanjiSelectionModal {

$("#ep-practice-modal-start").on("click", () => {
const selectedKanji = this.kanjiGrid.getSelectedKanji();
if (selectedKanji.length > 0) {
this.emit(EVENTS.START_REVIEW, selectedKanji);
const minRequired = {
[PRACTICE_MODES.STANDARD]: 1,
[PRACTICE_MODES.ENGLISH_TO_KANJI]: 4,
[PRACTICE_MODES.COMBINED]: 4
};

if (selectedKanji.length >= minRequired[this.selectedMode]) {
this.emit(EVENTS.START_REVIEW, {
kanji: selectedKanji,
mode: this.selectedMode,
allUnlockedKanji: this.allUnlockedKanji
});
}
});

Expand Down
16 changes: 7 additions & 9 deletions src/components/modals/radicalSelection/radicalSelectionModal.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,15 @@ class RadicalSelectionModal {
.text(`Start Review (${selectedCount} Selected)`)
.css({
...styles.practiceModal.buttons.start.base,
opacity: "1",
pointerEvents: "inherit"
...styles.practiceModal.buttons.start.radical
});
} else {
startButton
.prop("disabled", true)
.text("Start Review (0 Selected)")
.css({
...styles.practiceModal.buttons.start.base,
...styles.practiceModal.buttons.start.radical,
...styles.practiceModal.buttons.start.disabled
});
}
Expand All @@ -64,23 +64,23 @@ class RadicalSelectionModal {
}

async render() {
// Create and append modal
this.$modal = $(modalTemplate).appendTo("body");

// Set username
$("#username").text($("p.user-summary__username:first").text());

// Apply styles
this.$modal.css(styles.practiceModal.backdrop);
$("#ep-practice-modal-welcome").css(styles.practiceModal.welcomeText.container);
$("#ep-practice-modal-welcome h1").css(styles.practiceModal.welcomeText.username);
$("#ep-practice-modal-footer").css(styles.practiceModal.footer);
$("#ep-practice-modal-start").css(styles.practiceModal.buttons.start.base);
$("#ep-practice-modal-start").css({
...styles.practiceModal.buttons.start.base,
...styles.practiceModal.buttons.start.radical,
...styles.practiceModal.buttons.start.disabled
});
$("#ep-practice-modal-select-all").css(styles.practiceModal.buttons.selectAll);
$("#ep-practice-modal-content").css(styles.practiceModal.contentWrapper);
$("#ep-practice-modal-close").css(styles.practiceModal.buttons.exit);

// Initialize RadicalGrid
this.radicalGrid = new RadicalGrid(
this.radicals,
this.handleSelectionChange.bind(this)
Expand All @@ -89,10 +89,8 @@ class RadicalSelectionModal {
const $grid = await this.radicalGrid.render();
$("#ep-practice-modal-grid").replaceWith($grid);

// Initialize buttons
this.updateStartButton(0);

// Event handlers
$("#ep-practice-modal-select-all").on("click", () => {
const isSelectingAll = $("#ep-practice-modal-select-all").text() === "Select All";
this.radicalGrid.toggleAllRadicals(isSelectingAll);
Expand Down
Loading

0 comments on commit 3655d12

Please sign in to comment.