mirror of
https://github.com/ankitects/anki.git
synced 2025-11-11 07:07:13 -05:00
Feat: Simulator suspend after lapse count (#3837)
* Added: Leech suspend to simulator * Added: leech threshold spin box * Update git rev * Added: Save to preset options * ./check * Added: "Advanced settings" dropdown * Removed: Indent * Added: Easy days * Added: Sticky header * Removed: Easy Day updating without saving * un-nest disclosure * bump fsrs * Update a VSCode setting to match recent releases * Move Easy Days above the Advanced settings I think it's a bit more logical to have Advanced come last. * Ensure graph fits inside screen height * Bump fsrs version
This commit is contained in:
parent
122980e06b
commit
79b6f658c3
9 changed files with 240 additions and 128 deletions
|
|
@ -2,7 +2,7 @@
|
||||||
"editor.formatOnSave": true,
|
"editor.formatOnSave": true,
|
||||||
"[python]": {
|
"[python]": {
|
||||||
"editor.codeActionsOnSave": {
|
"editor.codeActionsOnSave": {
|
||||||
"source.organizeImports": true
|
"source.organizeImports": "explicit"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"files.watcherExclude": {
|
"files.watcherExclude": {
|
||||||
|
|
|
||||||
2
Cargo.lock
generated
2
Cargo.lock
generated
|
|
@ -2099,7 +2099,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fsrs"
|
name = "fsrs"
|
||||||
version = "3.0.0"
|
version = "3.0.0"
|
||||||
source = "git+https://github.com/open-spaced-repetition/fsrs-rs.git?rev=96520531415e032781adfe212f8a5eed216006be#96520531415e032781adfe212f8a5eed216006be"
|
source = "git+https://github.com/open-spaced-repetition/fsrs-rs.git?rev=22f8e453c120f5bc5996f86558a559c6b7abfc49#22f8e453c120f5bc5996f86558a559c6b7abfc49"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"burn",
|
"burn",
|
||||||
"itertools 0.12.1",
|
"itertools 0.12.1",
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ rev = "184b2ca50ed39ca43da13f0b830a463861adb9ca"
|
||||||
[workspace.dependencies.fsrs]
|
[workspace.dependencies.fsrs]
|
||||||
# version = "=2.0.3"
|
# version = "=2.0.3"
|
||||||
git = "https://github.com/open-spaced-repetition/fsrs-rs.git"
|
git = "https://github.com/open-spaced-repetition/fsrs-rs.git"
|
||||||
rev = "96520531415e032781adfe212f8a5eed216006be"
|
rev = "22f8e453c120f5bc5996f86558a559c6b7abfc49"
|
||||||
# path = "../open-spaced-repetition/fsrs-rs"
|
# path = "../open-spaced-repetition/fsrs-rs"
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
|
|
|
||||||
|
|
@ -393,6 +393,7 @@ message SimulateFsrsReviewRequest {
|
||||||
bool new_cards_ignore_review_limit = 9;
|
bool new_cards_ignore_review_limit = 9;
|
||||||
repeated float easy_days_percentages = 10;
|
repeated float easy_days_percentages = 10;
|
||||||
deck_config.DeckConfig.Config.ReviewCardOrder review_order = 11;
|
deck_config.DeckConfig.Config.ReviewCardOrder review_order = 11;
|
||||||
|
optional uint32 suspend_after_lapse_count = 12;
|
||||||
}
|
}
|
||||||
|
|
||||||
message SimulateFsrsReviewResponse {
|
message SimulateFsrsReviewResponse {
|
||||||
|
|
@ -409,6 +410,7 @@ message ComputeOptimalRetentionRequest {
|
||||||
string search = 4;
|
string search = 4;
|
||||||
double loss_aversion = 5;
|
double loss_aversion = 5;
|
||||||
repeated float easy_days_percentages = 6;
|
repeated float easy_days_percentages = 6;
|
||||||
|
optional uint32 suspend_after_lapse_count = 7;
|
||||||
}
|
}
|
||||||
|
|
||||||
message ComputeOptimalRetentionResponse {
|
message ComputeOptimalRetentionResponse {
|
||||||
|
|
|
||||||
|
|
@ -44,9 +44,9 @@ impl Collection {
|
||||||
let post_scheduling_fn: Option<PostSchedulingFn> =
|
let post_scheduling_fn: Option<PostSchedulingFn> =
|
||||||
if self.get_config_bool(BoolKey::LoadBalancerEnabled) {
|
if self.get_config_bool(BoolKey::LoadBalancerEnabled) {
|
||||||
Some(PostSchedulingFn(Arc::new(
|
Some(PostSchedulingFn(Arc::new(
|
||||||
move |interval, max_interval, today, due_cnt_per_day, rng| {
|
move |card, max_interval, today, due_cnt_per_day, rng| {
|
||||||
apply_load_balance_and_easy_days(
|
apply_load_balance_and_easy_days(
|
||||||
interval,
|
card.interval,
|
||||||
max_interval,
|
max_interval,
|
||||||
today,
|
today,
|
||||||
due_cnt_per_day,
|
due_cnt_per_day,
|
||||||
|
|
@ -78,6 +78,7 @@ impl Collection {
|
||||||
learn_limit,
|
learn_limit,
|
||||||
review_limit: usize::MAX,
|
review_limit: usize::MAX,
|
||||||
new_cards_ignore_review_limit: true,
|
new_cards_ignore_review_limit: true,
|
||||||
|
suspend_after_lapses: None,
|
||||||
post_scheduling_fn,
|
post_scheduling_fn,
|
||||||
review_priority_fn: None,
|
review_priority_fn: None,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -142,11 +142,13 @@ impl Collection {
|
||||||
.min(req.new_limit as usize);
|
.min(req.new_limit as usize);
|
||||||
if req.new_limit > 0 {
|
if req.new_limit > 0 {
|
||||||
let new_cards = (0..new_cards).map(|i| fsrs::Card {
|
let new_cards = (0..new_cards).map(|i| fsrs::Card {
|
||||||
|
id: -(i as i64),
|
||||||
difficulty: f32::NEG_INFINITY,
|
difficulty: f32::NEG_INFINITY,
|
||||||
stability: 1e-8, // Not filtered by fsrs-rs
|
stability: 1e-8, // Not filtered by fsrs-rs
|
||||||
last_date: f32::NEG_INFINITY, // Treated as a new card in simulation
|
last_date: f32::NEG_INFINITY, // Treated as a new card in simulation
|
||||||
due: ((introduced_today_count + i) / req.new_limit as usize) as f32,
|
due: ((introduced_today_count + i) / req.new_limit as usize) as f32,
|
||||||
interval: f32::NEG_INFINITY,
|
interval: f32::NEG_INFINITY,
|
||||||
|
lapses: 0,
|
||||||
});
|
});
|
||||||
converted_cards.extend(new_cards);
|
converted_cards.extend(new_cards);
|
||||||
}
|
}
|
||||||
|
|
@ -159,9 +161,9 @@ impl Collection {
|
||||||
let post_scheduling_fn: Option<PostSchedulingFn> =
|
let post_scheduling_fn: Option<PostSchedulingFn> =
|
||||||
if self.get_config_bool(BoolKey::LoadBalancerEnabled) {
|
if self.get_config_bool(BoolKey::LoadBalancerEnabled) {
|
||||||
Some(PostSchedulingFn(Arc::new(
|
Some(PostSchedulingFn(Arc::new(
|
||||||
move |interval, max_interval, today, due_cnt_per_day, rng| {
|
move |card, max_interval, today, due_cnt_per_day, rng| {
|
||||||
apply_load_balance_and_easy_days(
|
apply_load_balance_and_easy_days(
|
||||||
interval,
|
card.interval,
|
||||||
max_interval,
|
max_interval,
|
||||||
today,
|
today,
|
||||||
due_cnt_per_day,
|
due_cnt_per_day,
|
||||||
|
|
@ -198,6 +200,7 @@ impl Collection {
|
||||||
learn_limit: req.new_limit as usize,
|
learn_limit: req.new_limit as usize,
|
||||||
review_limit: req.review_limit as usize,
|
review_limit: req.review_limit as usize,
|
||||||
new_cards_ignore_review_limit: req.new_cards_ignore_review_limit,
|
new_cards_ignore_review_limit: req.new_cards_ignore_review_limit,
|
||||||
|
suspend_after_lapses: req.suspend_after_lapse_count,
|
||||||
post_scheduling_fn,
|
post_scheduling_fn,
|
||||||
review_priority_fn,
|
review_priority_fn,
|
||||||
};
|
};
|
||||||
|
|
@ -234,21 +237,25 @@ impl Card {
|
||||||
let relative_due = due - days_elapsed;
|
let relative_due = due - days_elapsed;
|
||||||
let last_date = (relative_due - card.interval as i32).min(0) as f32;
|
let last_date = (relative_due - card.interval as i32).min(0) as f32;
|
||||||
Some(fsrs::Card {
|
Some(fsrs::Card {
|
||||||
|
id: card.id.0,
|
||||||
difficulty: state.difficulty,
|
difficulty: state.difficulty,
|
||||||
stability: state.stability,
|
stability: state.stability,
|
||||||
last_date,
|
last_date,
|
||||||
due: relative_due as f32,
|
due: relative_due as f32,
|
||||||
interval: card.interval as f32,
|
interval: card.interval as f32,
|
||||||
|
lapses: card.lapses,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
CardQueue::New => None,
|
CardQueue::New => None,
|
||||||
CardQueue::Learn | CardQueue::SchedBuried | CardQueue::UserBuried => {
|
CardQueue::Learn | CardQueue::SchedBuried | CardQueue::UserBuried => {
|
||||||
Some(fsrs::Card {
|
Some(fsrs::Card {
|
||||||
|
id: card.id.0,
|
||||||
difficulty: state.difficulty,
|
difficulty: state.difficulty,
|
||||||
stability: state.stability,
|
stability: state.stability,
|
||||||
last_date: 0.0,
|
last_date: 0.0,
|
||||||
due: 0.0,
|
due: 0.0,
|
||||||
interval: card.interval as f32,
|
interval: card.interval as f32,
|
||||||
|
lapses: card.lapses,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
CardQueue::PreviewRepeat => None,
|
CardQueue::PreviewRepeat => None,
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
import TitledContainer from "$lib/components/TitledContainer.svelte";
|
import TitledContainer from "$lib/components/TitledContainer.svelte";
|
||||||
import type { DeckOptionsState } from "./lib";
|
import type { DeckOptionsState } from "./lib";
|
||||||
import Warning from "./Warning.svelte";
|
import Warning from "./Warning.svelte";
|
||||||
|
import EasyDaysInput from "./EasyDaysInput.svelte";
|
||||||
|
|
||||||
export let state: DeckOptionsState;
|
export let state: DeckOptionsState;
|
||||||
export let api: Record<string, never>;
|
export let api: Record<string, never>;
|
||||||
|
|
@ -35,16 +36,6 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
easyDaysChanged && !($fsrsEnabled && $reschedule)
|
easyDaysChanged && !($fsrsEnabled && $reschedule)
|
||||||
? tr.deckConfigEasyDaysChange()
|
? tr.deckConfigEasyDaysChange()
|
||||||
: "";
|
: "";
|
||||||
|
|
||||||
const easyDays = [
|
|
||||||
tr.deckConfigEasyDaysMonday(),
|
|
||||||
tr.deckConfigEasyDaysTuesday(),
|
|
||||||
tr.deckConfigEasyDaysWednesday(),
|
|
||||||
tr.deckConfigEasyDaysThursday(),
|
|
||||||
tr.deckConfigEasyDaysFriday(),
|
|
||||||
tr.deckConfigEasyDaysSaturday(),
|
|
||||||
tr.deckConfigEasyDaysSunday(),
|
|
||||||
];
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<datalist id="easy_day_steplist">
|
<datalist id="easy_day_steplist">
|
||||||
|
|
@ -53,43 +44,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
<TitledContainer title={tr.deckConfigEasyDaysTitle()}>
|
<TitledContainer title={tr.deckConfigEasyDaysTitle()}>
|
||||||
<DynamicallySlottable slotHost={Item} {api}>
|
<DynamicallySlottable slotHost={Item} {api}>
|
||||||
<Item>
|
<EasyDaysInput bind:values={$config.easyDaysPercentages} />
|
||||||
<div class="easy-days-settings">
|
|
||||||
<table>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th></th>
|
|
||||||
<th class="header min-col">
|
|
||||||
<span>{tr.deckConfigEasyDaysMinimum()}</span>
|
|
||||||
</th>
|
|
||||||
<th class="header text-center">
|
|
||||||
<span>{tr.deckConfigEasyDaysReduced()}</span>
|
|
||||||
</th>
|
|
||||||
<th class="header normal-col">
|
|
||||||
<span>{tr.deckConfigEasyDaysNormal()}</span>
|
|
||||||
</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{#each easyDays as day, index}
|
|
||||||
<tr>
|
|
||||||
<td class="day">{day}</td>
|
|
||||||
<td colspan="3">
|
|
||||||
<input
|
|
||||||
type="range"
|
|
||||||
bind:value={$config.easyDaysPercentages[index]}
|
|
||||||
step={0.5}
|
|
||||||
max={1.0}
|
|
||||||
min={0.0}
|
|
||||||
list="easy_day_steplist"
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{/each}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</Item>
|
|
||||||
<Item>
|
<Item>
|
||||||
<Warning warning={noNormalDay} />
|
<Warning warning={noNormalDay} />
|
||||||
</Item>
|
</Item>
|
||||||
|
|
@ -98,36 +53,3 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
</Item>
|
</Item>
|
||||||
</DynamicallySlottable>
|
</DynamicallySlottable>
|
||||||
</TitledContainer>
|
</TitledContainer>
|
||||||
|
|
||||||
<style>
|
|
||||||
.easy-days-settings table {
|
|
||||||
width: 100%;
|
|
||||||
border-collapse: collapse;
|
|
||||||
table-layout: fixed;
|
|
||||||
}
|
|
||||||
.easy-days-settings th,
|
|
||||||
.easy-days-settings td {
|
|
||||||
padding: 8px;
|
|
||||||
border-bottom: var(--border) solid 1px;
|
|
||||||
}
|
|
||||||
.header {
|
|
||||||
word-wrap: break-word;
|
|
||||||
font-size: smaller;
|
|
||||||
}
|
|
||||||
.easy-days-settings input[type="range"] {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.day {
|
|
||||||
word-wrap: break-word;
|
|
||||||
font-size: smaller;
|
|
||||||
}
|
|
||||||
|
|
||||||
.min-col {
|
|
||||||
text-align: start;
|
|
||||||
}
|
|
||||||
|
|
||||||
.normal-col {
|
|
||||||
text-align: end;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
||||||
91
ts/routes/deck-options/EasyDaysInput.svelte
Normal file
91
ts/routes/deck-options/EasyDaysInput.svelte
Normal file
|
|
@ -0,0 +1,91 @@
|
||||||
|
<!--
|
||||||
|
Copyright: Ankitects Pty Ltd and contributors
|
||||||
|
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
-->
|
||||||
|
<script>
|
||||||
|
import * as tr from "@generated/ftl";
|
||||||
|
import Item from "$lib/components/Item.svelte";
|
||||||
|
|
||||||
|
const easyDays = [
|
||||||
|
tr.deckConfigEasyDaysMonday(),
|
||||||
|
tr.deckConfigEasyDaysTuesday(),
|
||||||
|
tr.deckConfigEasyDaysWednesday(),
|
||||||
|
tr.deckConfigEasyDaysThursday(),
|
||||||
|
tr.deckConfigEasyDaysFriday(),
|
||||||
|
tr.deckConfigEasyDaysSaturday(),
|
||||||
|
tr.deckConfigEasyDaysSunday(),
|
||||||
|
];
|
||||||
|
|
||||||
|
export let values = [0, 0, 0, 0, 0, 0, 0];
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Item>
|
||||||
|
<div class="easy-days-settings">
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th></th>
|
||||||
|
<th class="header min-col">
|
||||||
|
<span>{tr.deckConfigEasyDaysMinimum()}</span>
|
||||||
|
</th>
|
||||||
|
<th class="header text-center">
|
||||||
|
<span>{tr.deckConfigEasyDaysReduced()}</span>
|
||||||
|
</th>
|
||||||
|
<th class="header normal-col">
|
||||||
|
<span>{tr.deckConfigEasyDaysNormal()}</span>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{#each easyDays as day, index}
|
||||||
|
<tr>
|
||||||
|
<td class="day">{day}</td>
|
||||||
|
<td colspan="3">
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
bind:value={values[index]}
|
||||||
|
step={0.5}
|
||||||
|
max={1.0}
|
||||||
|
min={0.0}
|
||||||
|
list="easy_day_steplist"
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{/each}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</Item>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.easy-days-settings table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
table-layout: fixed;
|
||||||
|
}
|
||||||
|
.easy-days-settings th,
|
||||||
|
.easy-days-settings td {
|
||||||
|
padding: 8px;
|
||||||
|
border-bottom: var(--border) solid 1px;
|
||||||
|
}
|
||||||
|
.header {
|
||||||
|
word-wrap: break-word;
|
||||||
|
font-size: smaller;
|
||||||
|
}
|
||||||
|
.easy-days-settings input[type="range"] {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.day {
|
||||||
|
word-wrap: break-word;
|
||||||
|
font-size: smaller;
|
||||||
|
}
|
||||||
|
|
||||||
|
.min-col {
|
||||||
|
text-align: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.normal-col {
|
||||||
|
text-align: end;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -28,6 +28,8 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
import SpinBoxFloatRow from "./SpinBoxFloatRow.svelte";
|
import SpinBoxFloatRow from "./SpinBoxFloatRow.svelte";
|
||||||
import { reviewOrderChoices } from "./choices";
|
import { reviewOrderChoices } from "./choices";
|
||||||
import EnumSelectorRow from "$lib/components/EnumSelectorRow.svelte";
|
import EnumSelectorRow from "$lib/components/EnumSelectorRow.svelte";
|
||||||
|
import { DeckConfig_Config_LeechAction } from "@generated/anki/deck_config_pb";
|
||||||
|
import EasyDaysInput from "./EasyDaysInput.svelte";
|
||||||
|
|
||||||
export let shown = false;
|
export let shown = false;
|
||||||
export let state: DeckOptionsState;
|
export let state: DeckOptionsState;
|
||||||
|
|
@ -48,6 +50,8 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
let points: Point[] = [];
|
let points: Point[] = [];
|
||||||
const newCardsIgnoreReviewLimit = state.newCardsIgnoreReviewLimit;
|
const newCardsIgnoreReviewLimit = state.newCardsIgnoreReviewLimit;
|
||||||
let smooth = true;
|
let smooth = true;
|
||||||
|
let suspendLeeches = $config.leechAction == DeckConfig_Config_LeechAction.SUSPEND;
|
||||||
|
let leechThreshold = $config.leechThreshold;
|
||||||
|
|
||||||
$: daysToSimulate = 365;
|
$: daysToSimulate = 365;
|
||||||
$: deckSize = 0;
|
$: deckSize = 0;
|
||||||
|
|
@ -75,6 +79,10 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
let resp: SimulateFsrsReviewResponse | undefined;
|
let resp: SimulateFsrsReviewResponse | undefined;
|
||||||
simulateFsrsRequest.daysToSimulate = daysToSimulate;
|
simulateFsrsRequest.daysToSimulate = daysToSimulate;
|
||||||
simulateFsrsRequest.deckSize = deckSize;
|
simulateFsrsRequest.deckSize = deckSize;
|
||||||
|
simulateFsrsRequest.suspendAfterLapseCount = suspendLeeches
|
||||||
|
? leechThreshold
|
||||||
|
: undefined;
|
||||||
|
simulateFsrsRequest.easyDaysPercentages = easyDayPercentages;
|
||||||
try {
|
try {
|
||||||
await runWithBackendProgress(
|
await runWithBackendProgress(
|
||||||
async () => {
|
async () => {
|
||||||
|
|
@ -169,6 +177,8 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
simulateSubgraph,
|
simulateSubgraph,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let easyDayPercentages = [...$config.easyDaysPercentages];
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="modal" class:show={shown} class:d-block={shown} tabindex="-1">
|
<div class="modal" class:show={shown} class:d-block={shown} tabindex="-1">
|
||||||
|
|
@ -235,13 +245,24 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
</SettingTitle>
|
</SettingTitle>
|
||||||
</SpinBoxRow>
|
</SpinBoxRow>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>{tr.deckConfigEasyDaysTitle()}</summary>
|
||||||
|
{#key easyDayPercentages}
|
||||||
|
<EasyDaysInput bind:values={easyDayPercentages} />
|
||||||
|
{/key}
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>{"Advanced settings"}</summary>
|
||||||
<SpinBoxRow
|
<SpinBoxRow
|
||||||
bind:value={simulateFsrsRequest.maxInterval}
|
bind:value={simulateFsrsRequest.maxInterval}
|
||||||
defaultValue={$config.maximumReviewInterval}
|
defaultValue={$config.maximumReviewInterval}
|
||||||
min={1}
|
min={1}
|
||||||
max={36500}
|
max={36500}
|
||||||
>
|
>
|
||||||
<SettingTitle on:click={() => openHelpModal("simulateFsrsReview")}>
|
<SettingTitle
|
||||||
|
on:click={() => openHelpModal("simulateFsrsReview")}
|
||||||
|
>
|
||||||
{tr.schedulingMaximumInterval()}
|
{tr.schedulingMaximumInterval()}
|
||||||
</SettingTitle>
|
</SettingTitle>
|
||||||
</SpinBoxRow>
|
</SpinBoxRow>
|
||||||
|
|
@ -251,7 +272,9 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
defaultValue={$config.reviewOrder}
|
defaultValue={$config.reviewOrder}
|
||||||
choices={reviewOrderChoices($fsrs)}
|
choices={reviewOrderChoices($fsrs)}
|
||||||
>
|
>
|
||||||
<SettingTitle on:click={() => openHelpModal("simulateFsrsReview")}>
|
<SettingTitle
|
||||||
|
on:click={() => openHelpModal("simulateFsrsReview")}
|
||||||
|
>
|
||||||
{tr.deckConfigReviewSortOrder()}
|
{tr.deckConfigReviewSortOrder()}
|
||||||
</SettingTitle>
|
</SettingTitle>
|
||||||
</EnumSelectorRow>
|
</EnumSelectorRow>
|
||||||
|
|
@ -260,17 +283,51 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
bind:value={simulateFsrsRequest.newCardsIgnoreReviewLimit}
|
bind:value={simulateFsrsRequest.newCardsIgnoreReviewLimit}
|
||||||
defaultValue={$newCardsIgnoreReviewLimit}
|
defaultValue={$newCardsIgnoreReviewLimit}
|
||||||
>
|
>
|
||||||
<SettingTitle on:click={() => openHelpModal("simulateFsrsReview")}>
|
<SettingTitle
|
||||||
<GlobalLabel title={tr.deckConfigNewCardsIgnoreReviewLimit()} />
|
on:click={() => openHelpModal("simulateFsrsReview")}
|
||||||
|
>
|
||||||
|
<GlobalLabel
|
||||||
|
title={tr.deckConfigNewCardsIgnoreReviewLimit()}
|
||||||
|
/>
|
||||||
</SettingTitle>
|
</SettingTitle>
|
||||||
</SwitchRow>
|
</SwitchRow>
|
||||||
|
|
||||||
<SwitchRow bind:value={smooth} defaultValue={true}>
|
<SwitchRow bind:value={smooth} defaultValue={true}>
|
||||||
<SettingTitle on:click={() => openHelpModal("simulateFsrsReview")}>
|
<SettingTitle
|
||||||
|
on:click={() => openHelpModal("simulateFsrsReview")}
|
||||||
|
>
|
||||||
{"Smooth Graph"}
|
{"Smooth Graph"}
|
||||||
</SettingTitle>
|
</SettingTitle>
|
||||||
</SwitchRow>
|
</SwitchRow>
|
||||||
|
|
||||||
|
<SwitchRow
|
||||||
|
bind:value={suspendLeeches}
|
||||||
|
defaultValue={$config.leechAction ==
|
||||||
|
DeckConfig_Config_LeechAction.SUSPEND}
|
||||||
|
>
|
||||||
|
<SettingTitle
|
||||||
|
on:click={() => openHelpModal("simulateFsrsReview")}
|
||||||
|
>
|
||||||
|
{"Suspend Leeches"}
|
||||||
|
</SettingTitle>
|
||||||
|
</SwitchRow>
|
||||||
|
|
||||||
|
{#if suspendLeeches}
|
||||||
|
<SpinBoxRow
|
||||||
|
bind:value={leechThreshold}
|
||||||
|
defaultValue={$config.leechThreshold}
|
||||||
|
min={1}
|
||||||
|
max={9999}
|
||||||
|
>
|
||||||
|
<SettingTitle
|
||||||
|
on:click={() => openHelpModal("simulateFsrsReview")}
|
||||||
|
>
|
||||||
|
{tr.schedulingLeechThreshold()}
|
||||||
|
</SettingTitle>
|
||||||
|
</SpinBoxRow>
|
||||||
|
{/if}
|
||||||
|
</details>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
class="btn {computing ? 'btn-warning' : 'btn-primary'}"
|
class="btn {computing ? 'btn-warning' : 'btn-primary'}"
|
||||||
disabled={computing}
|
disabled={computing}
|
||||||
|
|
@ -298,6 +355,11 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
$newCardsIgnoreReviewLimit =
|
$newCardsIgnoreReviewLimit =
|
||||||
simulateFsrsRequest.newCardsIgnoreReviewLimit;
|
simulateFsrsRequest.newCardsIgnoreReviewLimit;
|
||||||
$config.reviewOrder = simulateFsrsRequest.reviewOrder;
|
$config.reviewOrder = simulateFsrsRequest.reviewOrder;
|
||||||
|
$config.leechAction = suspendLeeches
|
||||||
|
? DeckConfig_Config_LeechAction.SUSPEND
|
||||||
|
: DeckConfig_Config_LeechAction.TAG_ONLY;
|
||||||
|
$config.leechThreshold = leechThreshold;
|
||||||
|
$config.easyDaysPercentages = [...easyDayPercentages];
|
||||||
onPresetChange();
|
onPresetChange();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
@ -339,6 +401,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
</InputBox>
|
</InputBox>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="svg-container">
|
||||||
<svg
|
<svg
|
||||||
bind:this={svg}
|
bind:this={svg}
|
||||||
viewBox={`0 0 ${bounds.width} ${bounds.height}`}
|
viewBox={`0 0 ${bounds.width} ${bounds.height}`}
|
||||||
|
|
@ -348,6 +411,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
<AxisTicks {bounds} />
|
<AxisTicks {bounds} />
|
||||||
<NoDataOverlay {bounds} />
|
<NoDataOverlay {bounds} />
|
||||||
</svg>
|
</svg>
|
||||||
|
</div>
|
||||||
|
|
||||||
<TableData {tableData} />
|
<TableData {tableData} />
|
||||||
</Graph>
|
</Graph>
|
||||||
|
|
@ -359,6 +423,27 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
<style>
|
<style>
|
||||||
.modal {
|
.modal {
|
||||||
background-color: rgba(0, 0, 0, 0.5);
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
|
--bs-modal-margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.svg-container {
|
||||||
|
width: 100%;
|
||||||
|
max-height: calc(100vh - 400px); /* Account for modal header, controls, etc */
|
||||||
|
aspect-ratio: 600 / 250;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
svg {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
background-color: var(--bs-body-bg);
|
||||||
|
z-index: 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
:global(.modal-xl) {
|
:global(.modal-xl) {
|
||||||
|
|
@ -372,4 +457,8 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
.btn {
|
.btn {
|
||||||
margin-bottom: 0.375rem;
|
margin-bottom: 0.375rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
summary {
|
||||||
|
margin-bottom: 0.5em;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue