-
-
Notifications
You must be signed in to change notification settings - Fork 28
Expand file tree
/
Copy path__init__.py
More file actions
348 lines (247 loc) · 10.5 KB
/
__init__.py
File metadata and controls
348 lines (247 loc) · 10.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
from pathlib import Path
from aqt.gui_hooks import deck_browser_will_show_options_menu, state_did_change
from aqt import mw
from aqt.qt import QAction, QDesktopServices, QUrl
from aqt.utils import openLink, askUser
from typing import Callable
from .utils import get_dr
from .dsr_state import init_dsr_status_hook
from .sync_hook import init_sync_hook
from .schedule.reschedule import reschedule
from .schedule.postpone import postpone
from .schedule.advance import advance
from .schedule.flatten import flatten
from .schedule.reset import clear_custom_data, clear_manual_rescheduling
from .schedule.disperse_siblings import disperse_siblings
from .schedule.schedule_break import schedule_break
from .schedule.easy_days import (
easy_days,
easy_day_for_sepcific_date,
)
from .schedule.remedy import remedy_hard_misuse, undo_remedy
from .schedule import init_review_hook
from .stats import init_stats
from .browser.browser import init_browser
from .configuration import Config, run_on_configuration_change
from .i18n import t
"""
Acknowledgement to Arthur Milchior, Carlos Duarte and oakkitten.
I learnt a lot from their add-ons.
https://github.com/Arthur-Milchior/Anki-postpone-reviews
https://github.com/cjdduarte/Free_Weekend_Load_Balancer
https://github.com/oakkitten/anki-delay-siblings
https://github.com/hgiesel/anki_straight_reward
"""
config = Config()
config.load()
# A tiny helper for menu items, since type checking is broken there
def checkable(title: str, on_click: Callable[[bool, QAction], None]) -> QAction:
action = QAction(title, mw, checkable=True)
def on_triggered(checked):
on_click(checked, action)
action.triggered.connect(on_triggered)
return action
def build_action(fun, text, shortcut=None):
"""fun -- without argument
text -- the text in the menu
"""
action = QAction(text)
action.triggered.connect(lambda b, did=None: fun(did))
if shortcut:
action.setShortcut(shortcut)
return action
def add_action_to_gear(fun, text):
"""fun -- takes an argument, the did
text -- what's written in the gear."""
def aux(m, did):
if not hasattr(m, "fsrs_helper_submenu"):
m.fsrs_helper_submenu = m.addMenu(t("fsrs-helper"))
a = m.fsrs_helper_submenu.addAction(text)
a.triggered.connect(lambda b, did=did: fun(did))
deck_browser_will_show_options_menu.append(aux)
def set_auto_reschedule_after_sync(checked, _):
config.auto_reschedule_after_sync = checked
menu_auto_reschedule_after_sync = checkable(
title=t("sync-auto-reschedule"),
on_click=set_auto_reschedule_after_sync,
)
def set_auto_disperse_after_sync(checked, _):
config.auto_disperse_after_sync = checked
menu_auto_disperse_after_sync = checkable(
title=t("auto-disperse-after-sync"),
on_click=set_auto_disperse_after_sync,
)
def set_auto_disperse_when_review(checked, _):
config.auto_disperse_when_review = checked
menu_auto_disperse = checkable(
title=t("auto-disperse-when-review"), on_click=set_auto_disperse_when_review
)
def set_auto_disperse_after_reschedule(checked, _):
config.auto_disperse_after_reschedule = checked
menu_auto_disperse_after_reschedule = checkable(
title=t("disperse-after-reschedule"),
on_click=set_auto_disperse_after_reschedule,
)
def set_reschedule_set_due_date(checked, _):
config.reschedule_set_due_date = checked
menu_reschedule_set_due_date = checkable(
title=t("reschedule-set-due-date"),
on_click=set_reschedule_set_due_date,
)
def set_display_memory_state(checked, _):
config.display_memory_state = checked
menu_display_memory_state = checkable(
title=t("display-memory-state"), on_click=set_display_memory_state
)
def set_show_steps_stats(checked, _):
if not config.show_steps_stats and not askUser(t("steps-stats-warning")):
return
config.show_steps_stats = checked
menu_show_steps_stats = checkable(
title=t("show-steps-stats"), on_click=set_show_steps_stats
)
def set_show_true_retention(checked, _):
config.show_true_retention = checked
menu_show_true_retention = checkable(
title=t("show-true-retention"), on_click=set_show_true_retention
)
def reschedule_recent(did):
reschedule(did, recent=True)
menu_reschedule = build_action(reschedule, t("reschedule-all-cards"))
add_action_to_gear(reschedule, t("reschedule-all-cards"))
menu_reschedule_recent = build_action(
reschedule_recent,
t("reschedule-recent-cards", count=config.days_to_reschedule),
)
add_action_to_gear(
reschedule_recent,
t("reschedule-recent-cards", count=config.days_to_reschedule),
)
menu_schedule_break = build_action(schedule_break, t("schedule-break-menu"))
add_action_to_gear(schedule_break, t("schedule-break-menu"))
menu_postpone = build_action(postpone, t("postpone-all-decks"))
add_action_to_gear(postpone, t("postpone-cards"))
menu_advance = build_action(advance, t("advance-all-decks"))
add_action_to_gear(advance, t("advance-cards"))
menu_flatten = build_action(flatten, t("flatten-all-decks"))
add_action_to_gear(flatten, t("flatten-cards"))
menu_reset = build_action(clear_custom_data, t("clear-custom-data-title"))
menu_clear_manual_rescheduling = build_action(
clear_manual_rescheduling, t("delete-redundant-revlog")
)
menu_disperse_siblings = build_action(disperse_siblings, t("disperse-all-siblings"))
menu_remedy_hard_misuse = build_action(remedy_hard_misuse, t("remedy"))
menu_undo_remedy = build_action(undo_remedy, t("undo"))
def contact_author(did=None):
openLink("https://github.com/open-spaced-repetition/fsrs4anki-helper")
menu_contact = build_action(contact_author, t("contact-author"))
def rate_on_ankiweb(did=None):
openLink("https://ankiweb.net/shared/review/759844606")
config.has_rated = True
menu_rate = build_action(rate_on_ankiweb, t("rate-addon"))
def visualize_schedule(did=None):
url = "https://open-spaced-repetition.github.io/anki_fsrs_visualizer"
deck = mw.col.decks.current()
if "conf" in deck:
config = mw.col.decks.get_config(deck["conf"])
retention = get_dr(mw.col.decks, deck["id"])
param_options = ["fsrsParams6", "fsrsParams5", "fsrsWeights"]
fsrs_params = next(
(
config[param]
for param in param_options
if param in config and len(config[param]) > 0
),
[],
)
fsrs_params_string = ",".join(f"{x:.4f}" for x in fsrs_params)
url += f"/?w={fsrs_params_string}&m={retention}"
openLink(url)
menu_visualize = build_action(visualize_schedule, t("visualize-schedule"))
def export_dataset(did=None):
addon = mw.addonManager.addonFromModule(__name__)
user_files = Path(mw.addonManager.addonsFolder(addon)) / "user_files"
user_files.mkdir(parents=True, exist_ok=True)
mw.col.export_dataset_for_research(f"{user_files}/{mw.pm.name}.revlog")
QDesktopServices.openUrl(QUrl.fromLocalFile(str(user_files.absolute())))
menu_export_dataset = build_action(export_dataset, t("export-dataset"))
def pass_fail(did=None):
openLink("https://ankiweb.net/shared/info/876946123")
menu_pass_fail = build_action(pass_fail, t("pass-fail"))
def ajt_card_management(did=None):
openLink("https://ankiweb.net/shared/info/1021636467")
menu_ajt_card_management = build_action(ajt_card_management, t("ajt-card-management"))
def search_stats_extended(did=None):
openLink("https://ankiweb.net/shared/info/1613056169")
menu_search_stats_extended = build_action(
search_stats_extended, t("search-stats-extended")
)
menu_for_helper = mw.form.menuTools.addMenu(t("fsrs-helper"))
menu_for_helper.addAction(menu_auto_reschedule_after_sync)
menu_for_helper.addAction(menu_auto_disperse_after_sync)
menu_for_helper.addAction(menu_reschedule_set_due_date)
menu_for_helper.addAction(menu_auto_disperse)
menu_for_helper.addAction(menu_display_memory_state)
menu_for_helper.addAction(menu_show_steps_stats)
menu_for_helper.addAction(menu_show_true_retention)
menu_for_helper.addAction(menu_auto_disperse_after_reschedule)
menu_for_easy_days = menu_for_helper.addMenu(t("less-anki-easy-days"))
menu_for_helper.addSeparator()
menu_for_helper.addAction(menu_reschedule)
menu_for_helper.addAction(menu_reschedule_recent)
menu_for_helper.addAction(menu_schedule_break)
menu_for_helper.addAction(menu_postpone)
menu_for_helper.addAction(menu_advance)
menu_for_helper.addAction(menu_flatten)
menu_for_helper.addAction(menu_disperse_siblings)
menu_for_helper.addSeparator()
menu_for_helper.addAction(menu_reset)
menu_for_helper.addAction(menu_clear_manual_rescheduling)
menu_for_remedy = menu_for_helper.addMenu(t("remedy-hard-misuse"))
menu_for_remedy.addAction(menu_remedy_hard_misuse)
menu_for_remedy.addAction(menu_undo_remedy)
menu_for_helper.addSeparator()
menu_for_helper.addAction(menu_contact)
menu_for_helper.addAction(menu_visualize)
if not config.has_rated:
menu_for_helper.addAction(menu_rate)
menu_for_helper.addAction(menu_export_dataset)
menu_for_helper.addSeparator()
menu_for_recommended_addons = menu_for_helper.addMenu(t("recommended-addons"))
menu_for_recommended_addons.addAction(menu_pass_fail)
menu_for_recommended_addons.addAction(menu_ajt_card_management)
menu_for_recommended_addons.addAction(menu_search_stats_extended)
menu_apply_easy_days = build_action(easy_days, t("apply-easy-days-now"))
menu_apply_easy_days_for_specific_date = build_action(
lambda did: easy_day_for_sepcific_date(did, config),
t("apply-easy-days-specific"),
)
menu_for_easy_days.addAction(menu_apply_easy_days_for_specific_date)
menu_for_easy_days.addAction(menu_apply_easy_days)
def adjust_menu():
if mw.col is not None:
menu_reschedule_recent.setText(
t("reschedule-recent-cards", count=config.days_to_reschedule)
)
menu_auto_reschedule_after_sync.setChecked(config.auto_reschedule_after_sync)
menu_auto_disperse_after_sync.setChecked(config.auto_disperse_after_sync)
menu_auto_disperse.setChecked(config.auto_disperse_when_review)
menu_display_memory_state.setChecked(config.display_memory_state)
menu_show_steps_stats.setChecked(config.show_steps_stats)
menu_show_true_retention.setChecked(config.show_true_retention)
menu_auto_disperse_after_reschedule.setChecked(
config.auto_disperse_after_reschedule
)
menu_reschedule_set_due_date.setChecked(config.reschedule_set_due_date)
@state_did_change.append
def state_did_change(_next_state, _previous_state):
adjust_menu()
@run_on_configuration_change
def configuration_changed():
config.load()
adjust_menu()
init_sync_hook()
init_stats()
init_browser()
init_review_hook()
init_dsr_status_hook()