Skip to content

Commit

Permalink
feat: add first settings view for changing anki parameters
Browse files Browse the repository at this point in the history
  • Loading branch information
FlorianWoelki committed Jul 14, 2024
1 parent a3ccc56 commit a33644f
Show file tree
Hide file tree
Showing 4 changed files with 182 additions and 64 deletions.
3 changes: 3 additions & 0 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { FILE_VIEW_TYPE, RecallView } from './ui/views';
import { DecksManager } from './data/manager/decks-manager';
import { EventEmitter } from './data/event';
import { AnkiAlgorithm } from './spaced-repetition/anki';
import { SettingsTab } from './ui/settings/SettingsTab';

export default class BetterRecallPlugin extends Plugin {
public readonly algorithm = new AnkiAlgorithm();
Expand All @@ -30,6 +31,8 @@ export default class BetterRecallPlugin extends Plugin {
this.addRibbonIcon('wallet-cards', 'Open Decks', () => {
this.openRecallView();
});

this.addSettingTab(new SettingsTab(this));
}

onunload() {
Expand Down
81 changes: 79 additions & 2 deletions src/settings/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,83 @@ export interface BetterRecallData {
decks: DeckJsonStructure[];
}

export interface BetterRecallSettings {}
export interface AnkiParameters {
/**
* The multiplier applied to the current interval when a card lapses
* (is forgotten).
* @default 0.5
*/
lapseInterval: number;
/**
* The interval (in days) assigned to a card when rated as `easy` during
* learning/relearning.
* @default 4
*/
easyInterval: number;
/**
* The multiplier applied to the interval when a review card is rated as
* `easy`.
* @default 1.3
*/
easyBonus: number;
/**
* The interval (in days) assigned to a card when it graduates from
* learning to review.
* @default 1
*/
graduatingInterval: number;
/**
* The minimum allowed ease factor for a card.
* @default 1.3
*/
minEaseFactor: number;
/**
* The amount by which the ease factor is decreased when a card is
* rated as `again`.
* @default 0.2
*/
easeFactorDecrement: number;
/**
* The amount by which the ease factor is increased when a card is
* rated as `easy`.
* @default 0.15
*/
easeFactorIncrement: number;
/**
* The multiplier applied to the current interval when a review card
* is rated as `hard`.
* @default 1.2
*/
hardIntervalMultiplier: number;
/**
* An array of step intervals (in minutes) for new cards in the learning
* phase.
* @default [1, 10]
*/
learningSteps: number[];
/**
* An array of step intervals (in minutes) for cards in the relearning
* phase.
* @default [10]
*/
relearningSteps: number[];
}

export interface BetterRecallSettings {
ankiParameters: AnkiParameters;
}

export const DEFAULT_SETTINGS: BetterRecallSettings = {};
export const DEFAULT_SETTINGS: BetterRecallSettings = {
ankiParameters: {
lapseInterval: 0.5,
easyInterval: 4,
easyBonus: 1.3,
graduatingInterval: 1,
minEaseFactor: 0.2,
easeFactorDecrement: 0.2,
easeFactorIncrement: 0.15,
hardIntervalMultiplier: 1.2,
learningSteps: [1, 10],
relearningSteps: [10],
},
};
63 changes: 1 addition & 62 deletions src/spaced-repetition/anki.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { AnkiParameters } from '../settings/data';
import { CardState, SpacedRepetitionAlgorithm, SpacedRepetitionItem } from '.';

export enum PerformanceResponse {
Expand All @@ -9,68 +10,6 @@ export enum PerformanceResponse {

const MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000;

interface AnkiParameters {
/**
* The multiplier applied to the current interval when a card lapses
* (is forgotten).
* @default 0.5
*/
lapseInterval: number;
/**
* The interval (in days) assigned to a card when rated as `easy` during
* learning/relearning.
* @default 4
*/
easyInterval: number;
/**
* The multiplier applied to the interval when a review card is rated as
* `easy`.
* @default 1.3
*/
easyBonus: number;
/**
* The interval (in days) assigned to a card when it graduates from
* learning to review.
* @default 1
*/
graduatingInterval: number;
/**
* The minimum allowed ease factor for a card.
* @default 1.3
*/
minEaseFactor: number;
/**
* The amount by which the ease factor is decreased when a card is
* rated as `again`.
* @default 0.2
*/
easeFactorDecrement: number;
/**
* The amount by which the ease factor is increased when a card is
* rated as `easy`.
* @default 0.15
*/
easeFactorIncrement: number;
/**
* The multiplier applied to the current interval when a review card
* is rated as `hard`.
* @default 1.2
*/
hardIntervalMultiplier: number;
/**
* An array of step intervals (in minutes) for new cards in the learning
* phase.
* @default [1, 10]
*/
learningSteps: number[];
/**
* An array of step intervals (in minutes) for cards in the relearning
* phase.
* @default [10]
*/
relearningSteps: number[];
}

export class AnkiAlgorithm extends SpacedRepetitionAlgorithm<AnkiParameters> {
public getDefaultValues(): AnkiParameters {
return {
Expand Down
99 changes: 99 additions & 0 deletions src/ui/settings/SettingsTab.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { Setting, PluginSettingTab, TextComponent } from 'obsidian';
import BetterRecallPlugin from 'src/main';
import { ResetButtonComponent } from '../components/ResetButtonComponent';
import { AnkiParameters, DEFAULT_SETTINGS } from 'src/settings/data';

export class SettingsTab extends PluginSettingTab {
private titleParameterMapping: Record<string, keyof AnkiParameters> = {
'Lapse Interval': 'lapseInterval',
'Easy Interval': 'easyInterval',
'Easy Bonus': 'easyBonus',
'Graduating Interval': 'graduatingInterval',
'Min Ease Factor': 'minEaseFactor',
'Ease Factor Decrement': 'easeFactorDecrement',
'Ease Factor Increment': 'easeFactorIncrement',
'Hard Interval Multiplier': 'hardIntervalMultiplier',
'Learning Steps': 'learningSteps',
'Relearning Steps': 'relearningSteps',
};

constructor(private plugin: BetterRecallPlugin) {
super(plugin.app, plugin);
}

display() {
this.containerEl.empty();

new Setting(this.containerEl).setName('Anki Settings').setHeading();

Object.entries(this.titleParameterMapping).forEach(([key, value]) => {
let textComponent: TextComponent | null = null;
const pluginValue = this.plugin.getSettings().ankiParameters[value];

const setting = new Setting(this.containerEl).setName(key);

new ResetButtonComponent(setting.controlEl).onClick(async () => {
if (!textComponent) {
return;
}

const defaultValue = DEFAULT_SETTINGS.ankiParameters[value];
this.setValue(textComponent, defaultValue);
this.plugin.getSettings().ankiParameters[value] =
defaultValue as number & number[]; // TODO: Fix this type issue here.
await this.plugin.savePluginData();
});

setting.addText((text) => {
textComponent = text;
this.setValue(text, pluginValue);

text.onChange(async (input) => {
input = input.trim();
if (Array.isArray(DEFAULT_SETTINGS.ankiParameters[value])) {
if (!this.isStringValidArray(input)) {
console.log('not a valid array', input);
return;
}

const newValue = this.parseStringToArray(input);
this.plugin.getSettings().ankiParameters[value] =
newValue as number & number[];
} else {
if (isNaN(+input)) {
return;
}

this.plugin.getSettings().ankiParameters[value] = Number(
input,
) as number & number[];
}

await this.plugin.savePluginData();
});
});
});
}

private setValue(text: TextComponent, value: number | number[]): void {
if (Array.isArray(value)) {
text.setValue(value.join(','));
} else {
text.setValue(value.toString());
}
}

private parseStringToArray(input: string): number[] {
return input
.trim()
.split(',')
.map((text) => Number(text));
}

private isStringValidArray(input: string): boolean {
return input
.trim()
.split(',')
.every((text) => !isNaN(+text));
}
}

0 comments on commit a33644f

Please sign in to comment.