mirror of
https://github.com/ankitects/anki.git
synced 2025-09-24 16:56:36 -04:00
Merge branch 'main' into editor-3830
This commit is contained in:
commit
3ae7484610
14 changed files with 226 additions and 37 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -2295,7 +2295,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fsrs"
|
name = "fsrs"
|
||||||
version = "4.0.0"
|
version = "4.0.0"
|
||||||
source = "git+https://github.com/open-spaced-repetition/fsrs-rs.git?rev=092c20bac7d9239a991ae5b561556ad34c706c16#092c20bac7d9239a991ae5b561556ad34c706c16"
|
source = "git+https://github.com/open-spaced-repetition/fsrs-rs.git?rev=33ec3ee4d5d73e704633469cf5bf1a42e620a524#33ec3ee4d5d73e704633469cf5bf1a42e620a524"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"burn",
|
"burn",
|
||||||
"itertools 0.14.0",
|
"itertools 0.14.0",
|
||||||
|
|
|
@ -37,7 +37,7 @@ rev = "184b2ca50ed39ca43da13f0b830a463861adb9ca"
|
||||||
[workspace.dependencies.fsrs]
|
[workspace.dependencies.fsrs]
|
||||||
# version = "3.0.0"
|
# version = "3.0.0"
|
||||||
git = "https://github.com/open-spaced-repetition/fsrs-rs.git"
|
git = "https://github.com/open-spaced-repetition/fsrs-rs.git"
|
||||||
rev = "092c20bac7d9239a991ae5b561556ad34c706c16"
|
rev = "33ec3ee4d5d73e704633469cf5bf1a42e620a524"
|
||||||
# path = "../open-spaced-repetition/fsrs-rs"
|
# path = "../open-spaced-repetition/fsrs-rs"
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
|
|
|
@ -470,11 +470,12 @@ deck-config-compute-optimal-retention-tooltip4 =
|
||||||
willing to invest more study time to achieve it. Setting your desired retention lower than the minimum
|
willing to invest more study time to achieve it. Setting your desired retention lower than the minimum
|
||||||
is not recommended, as it will lead to a higher workload, because of the high forgetting rate.
|
is not recommended, as it will lead to a higher workload, because of the high forgetting rate.
|
||||||
deck-config-please-save-your-changes-first = Please save your changes first.
|
deck-config-please-save-your-changes-first = Please save your changes first.
|
||||||
deck-config-a-100-day-interval =
|
deck-config-workload-factor-change = Approximate workload: {$factor}x
|
||||||
{ $days ->
|
(compared to {$previousDR}% desired retention)
|
||||||
[one] A 100 day interval will become { $days } day.
|
deck-config-workload-factor-unchanged = The higher this value, the more frequently cards will be shown to you.
|
||||||
*[other] A 100 day interval will become { $days } days.
|
deck-config-desired-retention-too-low = Your desired retention is very low, which can lead to very long intervals.
|
||||||
}
|
deck-config-desired-retention-too-high = Your desired retention is very high, which can lead to very short intervals.
|
||||||
|
|
||||||
deck-config-percent-of-reviews =
|
deck-config-percent-of-reviews =
|
||||||
{ $reviews ->
|
{ $reviews ->
|
||||||
[one] { $pct }% of { $reviews } review
|
[one] { $pct }% of { $reviews } review
|
||||||
|
@ -512,6 +513,12 @@ deck-config-fsrs-simulator-radio-memorized = Memorized
|
||||||
|
|
||||||
## NO NEED TO TRANSLATE. This text is no longer used by Anki, and will be removed in the future.
|
## NO NEED TO TRANSLATE. This text is no longer used by Anki, and will be removed in the future.
|
||||||
|
|
||||||
|
deck-config-a-100-day-interval =
|
||||||
|
{ $days ->
|
||||||
|
[one] A 100 day interval will become { $days } day.
|
||||||
|
*[other] A 100 day interval will become { $days } days.
|
||||||
|
}
|
||||||
|
|
||||||
deck-config-fsrs-simulator-y-axis-title-time = Review Time/Day
|
deck-config-fsrs-simulator-y-axis-title-time = Review Time/Day
|
||||||
deck-config-fsrs-simulator-y-axis-title-count = Review Count/Day
|
deck-config-fsrs-simulator-y-axis-title-count = Review Count/Day
|
||||||
deck-config-fsrs-simulator-y-axis-title-memorized = Memorized Total
|
deck-config-fsrs-simulator-y-axis-title-memorized = Memorized Total
|
||||||
|
|
|
@ -25,6 +25,8 @@ service DeckConfigService {
|
||||||
returns (collection.OpChanges);
|
returns (collection.OpChanges);
|
||||||
rpc GetIgnoredBeforeCount(GetIgnoredBeforeCountRequest)
|
rpc GetIgnoredBeforeCount(GetIgnoredBeforeCountRequest)
|
||||||
returns (GetIgnoredBeforeCountResponse);
|
returns (GetIgnoredBeforeCountResponse);
|
||||||
|
rpc GetRetentionWorkload(GetRetentionWorkloadRequest)
|
||||||
|
returns (GetRetentionWorkloadResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implicitly includes any of the above methods that are not listed in the
|
// Implicitly includes any of the above methods that are not listed in the
|
||||||
|
@ -35,6 +37,17 @@ message DeckConfigId {
|
||||||
int64 dcid = 1;
|
int64 dcid = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message GetRetentionWorkloadRequest {
|
||||||
|
repeated float w = 1;
|
||||||
|
string search = 2;
|
||||||
|
float before = 3;
|
||||||
|
float after = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetRetentionWorkloadResponse {
|
||||||
|
float factor = 1;
|
||||||
|
}
|
||||||
|
|
||||||
message GetIgnoredBeforeCountRequest {
|
message GetIgnoredBeforeCountRequest {
|
||||||
string ignore_revlogs_before_date = 1;
|
string ignore_revlogs_before_date = 1;
|
||||||
string search = 2;
|
string search = 2;
|
||||||
|
|
|
@ -682,6 +682,7 @@ exposed_backend_list = [
|
||||||
"simulate_fsrs_review",
|
"simulate_fsrs_review",
|
||||||
# DeckConfigService
|
# DeckConfigService
|
||||||
"get_ignored_before_count",
|
"get_ignored_before_count",
|
||||||
|
"get_retention_workload",
|
||||||
# CardRenderingService
|
# CardRenderingService
|
||||||
"encode_iri_paths",
|
"encode_iri_paths",
|
||||||
"decode_iri_paths",
|
"decode_iri_paths",
|
||||||
|
|
|
@ -96,6 +96,40 @@ impl crate::services::DeckConfigService for Collection {
|
||||||
total: guard.cards.try_into().unwrap_or(0),
|
total: guard.cards.try_into().unwrap_or(0),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_retention_workload(
|
||||||
|
&mut self,
|
||||||
|
input: anki_proto::deck_config::GetRetentionWorkloadRequest,
|
||||||
|
) -> Result<anki_proto::deck_config::GetRetentionWorkloadResponse> {
|
||||||
|
const LEARN_SPAN: usize = 1000;
|
||||||
|
|
||||||
|
let guard =
|
||||||
|
self.search_cards_into_table(&input.search, crate::search::SortMode::NoOrder)?;
|
||||||
|
let (pass_cost, fail_cost, learn_cost) = guard.col.storage.get_costs_for_retention()?;
|
||||||
|
|
||||||
|
let before = fsrs::expected_workload(
|
||||||
|
&input.w,
|
||||||
|
input.before,
|
||||||
|
LEARN_SPAN,
|
||||||
|
pass_cost,
|
||||||
|
fail_cost,
|
||||||
|
0.,
|
||||||
|
input.before,
|
||||||
|
)? + learn_cost;
|
||||||
|
let after = fsrs::expected_workload(
|
||||||
|
&input.w,
|
||||||
|
input.after,
|
||||||
|
LEARN_SPAN,
|
||||||
|
pass_cost,
|
||||||
|
fail_cost,
|
||||||
|
0.,
|
||||||
|
input.after,
|
||||||
|
)? + learn_cost;
|
||||||
|
|
||||||
|
Ok(anki_proto::deck_config::GetRetentionWorkloadResponse {
|
||||||
|
factor: after / before,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<DeckConfig> for anki_proto::deck_config::DeckConfig {
|
impl From<DeckConfig> for anki_proto::deck_config::DeckConfig {
|
||||||
|
|
|
@ -32,12 +32,14 @@ impl Card {
|
||||||
force_reset: bool,
|
force_reset: bool,
|
||||||
) {
|
) {
|
||||||
let new_due = (today + days_from_today) as i32;
|
let new_due = (today + days_from_today) as i32;
|
||||||
let new_interval =
|
let fsrs_enabled = self.memory_state.is_some();
|
||||||
if force_reset || !matches!(self.ctype, CardType::Review | CardType::Relearn) {
|
let new_interval = if fsrs_enabled {
|
||||||
days_from_today
|
self.interval.saturating_add_signed(new_due - self.due)
|
||||||
} else {
|
} else if force_reset || !matches!(self.ctype, CardType::Review | CardType::Relearn) {
|
||||||
self.interval
|
days_from_today
|
||||||
};
|
} else {
|
||||||
|
self.interval
|
||||||
|
};
|
||||||
let ease_factor = (ease_factor * 1000.0).round() as u16;
|
let ease_factor = (ease_factor * 1000.0).round() as u16;
|
||||||
|
|
||||||
self.schedule_as_review(new_interval, new_due, ease_factor);
|
self.schedule_as_review(new_interval, new_due, ease_factor);
|
||||||
|
|
|
@ -30,19 +30,21 @@ impl Collection {
|
||||||
|
|
||||||
let (average_secs, total_secs) = average_and_total_secs_strings(&revlog);
|
let (average_secs, total_secs) = average_and_total_secs_strings(&revlog);
|
||||||
let timing = self.timing_today()?;
|
let timing = self.timing_today()?;
|
||||||
let days_elapsed = self
|
let seconds_elapsed = self
|
||||||
.storage
|
.storage
|
||||||
.time_of_last_review(card.id)?
|
.time_of_last_review(card.id)?
|
||||||
.map(|ts| timing.next_day_at.elapsed_days_since(ts))
|
.map(|ts| timing.now.elapsed_secs_since(ts))
|
||||||
.unwrap_or_default() as u32;
|
.unwrap_or_default() as u32;
|
||||||
let fsrs_retrievability = card
|
let fsrs_retrievability = card
|
||||||
.memory_state
|
.memory_state
|
||||||
.zip(Some(days_elapsed))
|
.zip(Some(seconds_elapsed))
|
||||||
.zip(Some(card.decay.unwrap_or(FSRS5_DEFAULT_DECAY)))
|
.zip(Some(card.decay.unwrap_or(FSRS5_DEFAULT_DECAY)))
|
||||||
.map(|((state, days), decay)| {
|
.map(|((state, seconds), decay)| {
|
||||||
FSRS::new(None)
|
FSRS::new(None).unwrap().current_retrievability_seconds(
|
||||||
.unwrap()
|
state.into(),
|
||||||
.current_retrievability(state.into(), days, decay)
|
seconds,
|
||||||
|
decay,
|
||||||
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
let original_deck = if card.original_deck_id == DeckId(0) {
|
let original_deck = if card.original_deck_id == DeckId(0) {
|
||||||
|
|
49
rslib/src/storage/card/get_costs_for_retention.sql
Normal file
49
rslib/src/storage/card/get_costs_for_retention.sql
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
WITH searched_revlogs AS (
|
||||||
|
SELECT *
|
||||||
|
FROM revlog
|
||||||
|
WHERE ease > 0
|
||||||
|
AND cid IN search_cids
|
||||||
|
ORDER BY id DESC -- Use the last 10_000 reviews
|
||||||
|
LIMIT 10000
|
||||||
|
), average_pass AS (
|
||||||
|
SELECT AVG(time)
|
||||||
|
FROM searched_revlogs
|
||||||
|
WHERE ease > 1
|
||||||
|
),
|
||||||
|
lapse_count AS (
|
||||||
|
SELECT COUNT(time) AS lapse_count
|
||||||
|
FROM searched_revlogs
|
||||||
|
WHERE ease = 1
|
||||||
|
AND type = 1
|
||||||
|
),
|
||||||
|
fail_sum AS (
|
||||||
|
SELECT SUM(time) AS total_fail_time
|
||||||
|
FROM searched_revlogs
|
||||||
|
WHERE (
|
||||||
|
ease = 1
|
||||||
|
AND type = 1
|
||||||
|
)
|
||||||
|
OR type = 2
|
||||||
|
),
|
||||||
|
-- (sum(Relearning) + sum(Lapses)) / count(Lapses)
|
||||||
|
average_fail AS (
|
||||||
|
SELECT total_fail_time * 1.0 / NULLIF(lapse_count, 0) AS avg_fail_time
|
||||||
|
FROM fail_sum,
|
||||||
|
lapse_count
|
||||||
|
),
|
||||||
|
-- Can lead to cards with partial learn histories skewing the time
|
||||||
|
summed_learns AS (
|
||||||
|
SELECT cid,
|
||||||
|
SUM(time) AS total_time
|
||||||
|
FROM searched_revlogs
|
||||||
|
WHERE searched_revlogs.type = 0
|
||||||
|
GROUP BY cid
|
||||||
|
),
|
||||||
|
average_learn AS (
|
||||||
|
SELECT AVG(total_time) AS avg_learn_time
|
||||||
|
FROM summed_learns
|
||||||
|
)
|
||||||
|
SELECT *
|
||||||
|
FROM average_pass,
|
||||||
|
average_fail,
|
||||||
|
average_learn;
|
|
@ -747,6 +747,20 @@ impl super::SqliteStorage {
|
||||||
.get(0)?)
|
.get(0)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn get_costs_for_retention(&self) -> Result<(f32, f32, f32)> {
|
||||||
|
let mut statement = self
|
||||||
|
.db
|
||||||
|
.prepare(include_str!("get_costs_for_retention.sql"))?;
|
||||||
|
let mut query = statement.query(params![])?;
|
||||||
|
let row = query.next()?.unwrap();
|
||||||
|
|
||||||
|
Ok((
|
||||||
|
row.get(0).unwrap_or(7000.),
|
||||||
|
row.get(1).unwrap_or(23_000.),
|
||||||
|
row.get(2).unwrap_or(30_000.),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub(crate) fn get_all_cards(&self) -> Vec<Card> {
|
pub(crate) fn get_all_cards(&self) -> Vec<Card> {
|
||||||
self.db
|
self.db
|
||||||
|
|
|
@ -23,7 +23,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
export let percentage = false;
|
export let percentage = false;
|
||||||
|
|
||||||
let input: HTMLInputElement;
|
let input: HTMLInputElement;
|
||||||
let focused = false;
|
export let focused = false;
|
||||||
let multiplier: number;
|
let multiplier: number;
|
||||||
$: multiplier = percentage ? 100 : 1;
|
$: multiplier = percentage ? 100 : 1;
|
||||||
|
|
||||||
|
@ -129,6 +129,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
value={stringValue}
|
value={stringValue}
|
||||||
bind:this={input}
|
bind:this={input}
|
||||||
on:blur={update}
|
on:blur={update}
|
||||||
|
on:change={update}
|
||||||
on:input={onInput}
|
on:input={onInput}
|
||||||
on:focusin={() => (focused = true)}
|
on:focusin={() => (focused = true)}
|
||||||
on:focusout={() => (focused = false)}
|
on:focusout={() => (focused = false)}
|
||||||
|
|
|
@ -11,6 +11,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
import {
|
import {
|
||||||
computeFsrsParams,
|
computeFsrsParams,
|
||||||
evaluateParams,
|
evaluateParams,
|
||||||
|
getRetentionWorkload,
|
||||||
setWantsAbort,
|
setWantsAbort,
|
||||||
} from "@generated/backend";
|
} from "@generated/backend";
|
||||||
import * as tr from "@generated/ftl";
|
import * as tr from "@generated/ftl";
|
||||||
|
@ -26,11 +27,15 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
import ParamsInputRow from "./ParamsInputRow.svelte";
|
import ParamsInputRow from "./ParamsInputRow.svelte";
|
||||||
import ParamsSearchRow from "./ParamsSearchRow.svelte";
|
import ParamsSearchRow from "./ParamsSearchRow.svelte";
|
||||||
import SimulatorModal from "./SimulatorModal.svelte";
|
import SimulatorModal from "./SimulatorModal.svelte";
|
||||||
import { UpdateDeckConfigsMode } from "@generated/anki/deck_config_pb";
|
import {
|
||||||
|
GetRetentionWorkloadRequest,
|
||||||
|
UpdateDeckConfigsMode,
|
||||||
|
} from "@generated/anki/deck_config_pb";
|
||||||
|
|
||||||
export let state: DeckOptionsState;
|
export let state: DeckOptionsState;
|
||||||
export let openHelpModal: (String) => void;
|
export let openHelpModal: (String) => void;
|
||||||
export let onPresetChange: () => void;
|
export let onPresetChange: () => void;
|
||||||
|
export let newlyEnabled = false;
|
||||||
|
|
||||||
const config = state.currentConfig;
|
const config = state.currentConfig;
|
||||||
const defaults = state.defaults;
|
const defaults = state.defaults;
|
||||||
|
@ -39,6 +44,15 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
$: lastOptimizationWarning =
|
$: lastOptimizationWarning =
|
||||||
$daysSinceLastOptimization > 30 ? tr.deckConfigTimeToOptimize() : "";
|
$daysSinceLastOptimization > 30 ? tr.deckConfigTimeToOptimize() : "";
|
||||||
|
let desiredRetentionFocused = false;
|
||||||
|
let desiredRetentionEverFocused = false;
|
||||||
|
let optimized = false;
|
||||||
|
const startingDesiredRetention = $config.desiredRetention.toFixed(2);
|
||||||
|
$: if (desiredRetentionFocused) {
|
||||||
|
desiredRetentionEverFocused = true;
|
||||||
|
}
|
||||||
|
$: showDesiredRetentionTooltip =
|
||||||
|
newlyEnabled || desiredRetentionEverFocused || optimized;
|
||||||
|
|
||||||
let computeParamsProgress: ComputeParamsProgress | undefined;
|
let computeParamsProgress: ComputeParamsProgress | undefined;
|
||||||
let computingParams = false;
|
let computingParams = false;
|
||||||
|
@ -47,10 +61,23 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
$: computing = computingParams || checkingParams;
|
$: computing = computingParams || checkingParams;
|
||||||
$: defaultparamSearch = `preset:"${state.getCurrentNameForSearch()}" -is:suspended`;
|
$: defaultparamSearch = `preset:"${state.getCurrentNameForSearch()}" -is:suspended`;
|
||||||
$: roundedRetention = Number($config.desiredRetention.toFixed(2));
|
$: roundedRetention = Number($config.desiredRetention.toFixed(2));
|
||||||
$: desiredRetentionWarning = getRetentionWarning(
|
$: desiredRetentionWarning = getRetentionLongShortWarning(roundedRetention);
|
||||||
roundedRetention,
|
|
||||||
fsrsParams($config),
|
let timeoutId: ReturnType<typeof setTimeout> | undefined = undefined;
|
||||||
);
|
const WORKLOAD_UPDATE_DELAY_MS = 100;
|
||||||
|
|
||||||
|
let desiredRetentionChangeInfo = "";
|
||||||
|
$: {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
if (showDesiredRetentionTooltip) {
|
||||||
|
timeoutId = setTimeout(() => {
|
||||||
|
getRetentionChangeInfo(roundedRetention, fsrsParams($config));
|
||||||
|
}, WORKLOAD_UPDATE_DELAY_MS);
|
||||||
|
} else {
|
||||||
|
desiredRetentionChangeInfo = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$: retentionWarningClass = getRetentionWarningClass(roundedRetention);
|
$: retentionWarningClass = getRetentionWarningClass(roundedRetention);
|
||||||
|
|
||||||
$: newCardsIgnoreReviewLimit = state.newCardsIgnoreReviewLimit;
|
$: newCardsIgnoreReviewLimit = state.newCardsIgnoreReviewLimit;
|
||||||
|
@ -67,23 +94,44 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
reviewOrder: $config.reviewOrder,
|
reviewOrder: $config.reviewOrder,
|
||||||
});
|
});
|
||||||
|
|
||||||
function getRetentionWarning(retention: number, params: number[]): string {
|
const DESIRED_RETENTION_LOW_THRESHOLD = 0.8;
|
||||||
const decay = params.length > 20 ? -params[20] : -0.5; // default decay for FSRS-4.5 and FSRS-5
|
const DESIRED_RETENTION_HIGH_THRESHOLD = 0.95;
|
||||||
const factor = 0.9 ** (1 / decay) - 1;
|
|
||||||
const stability = 100;
|
function getRetentionLongShortWarning(retention: number) {
|
||||||
const days = Math.round(
|
if (retention < DESIRED_RETENTION_LOW_THRESHOLD) {
|
||||||
(stability / factor) * (Math.pow(retention, 1 / decay) - 1),
|
return tr.deckConfigDesiredRetentionTooLow();
|
||||||
);
|
} else if (retention > DESIRED_RETENTION_HIGH_THRESHOLD) {
|
||||||
if (days === 100) {
|
return tr.deckConfigDesiredRetentionTooHigh();
|
||||||
|
} else {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
return tr.deckConfigA100DayInterval({ days });
|
}
|
||||||
|
|
||||||
|
async function getRetentionChangeInfo(retention: number, params: number[]) {
|
||||||
|
if (+startingDesiredRetention == roundedRetention) {
|
||||||
|
desiredRetentionChangeInfo = tr.deckConfigWorkloadFactorUnchanged();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const request = new GetRetentionWorkloadRequest({
|
||||||
|
w: params,
|
||||||
|
search: defaultparamSearch,
|
||||||
|
before: +startingDesiredRetention,
|
||||||
|
after: retention,
|
||||||
|
});
|
||||||
|
const resp = await getRetentionWorkload(request);
|
||||||
|
desiredRetentionChangeInfo = tr.deckConfigWorkloadFactorChange({
|
||||||
|
factor: resp.factor.toFixed(2),
|
||||||
|
previousDr: (+startingDesiredRetention * 100).toString(),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function getRetentionWarningClass(retention: number): string {
|
function getRetentionWarningClass(retention: number): string {
|
||||||
if (retention < 0.7 || retention > 0.97) {
|
if (retention < 0.7 || retention > 0.97) {
|
||||||
return "alert-danger";
|
return "alert-danger";
|
||||||
} else if (retention < 0.8 || retention > 0.95) {
|
} else if (
|
||||||
|
retention < DESIRED_RETENTION_LOW_THRESHOLD ||
|
||||||
|
retention > DESIRED_RETENTION_HIGH_THRESHOLD
|
||||||
|
) {
|
||||||
return "alert-warning";
|
return "alert-warning";
|
||||||
} else {
|
} else {
|
||||||
return "alert-info";
|
return "alert-info";
|
||||||
|
@ -146,6 +194,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
setTimeout(() => alert(msg), 200);
|
setTimeout(() => alert(msg), 200);
|
||||||
} else {
|
} else {
|
||||||
$config.fsrsParams6 = resp.params;
|
$config.fsrsParams6 = resp.params;
|
||||||
|
optimized = true;
|
||||||
}
|
}
|
||||||
if (computeParamsProgress) {
|
if (computeParamsProgress) {
|
||||||
computeParamsProgress.current = computeParamsProgress.total;
|
computeParamsProgress.current = computeParamsProgress.total;
|
||||||
|
@ -237,12 +286,14 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
min={0.7}
|
min={0.7}
|
||||||
max={0.99}
|
max={0.99}
|
||||||
percentage={true}
|
percentage={true}
|
||||||
|
bind:focused={desiredRetentionFocused}
|
||||||
>
|
>
|
||||||
<SettingTitle on:click={() => openHelpModal("desiredRetention")}>
|
<SettingTitle on:click={() => openHelpModal("desiredRetention")}>
|
||||||
{tr.deckConfigDesiredRetention()}
|
{tr.deckConfigDesiredRetention()}
|
||||||
</SettingTitle>
|
</SettingTitle>
|
||||||
</SpinBoxFloatRow>
|
</SpinBoxFloatRow>
|
||||||
|
|
||||||
|
<Warning warning={desiredRetentionChangeInfo} className={"alert-info two-line"} />
|
||||||
<Warning warning={desiredRetentionWarning} className={retentionWarningClass} />
|
<Warning warning={desiredRetentionWarning} className={retentionWarningClass} />
|
||||||
|
|
||||||
<div class="ms-1 me-1">
|
<div class="ms-1 me-1">
|
||||||
|
@ -331,4 +382,13 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
.btn {
|
.btn {
|
||||||
margin-bottom: 0.375rem;
|
margin-bottom: 0.375rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:global(.two-line) {
|
||||||
|
white-space: pre-wrap;
|
||||||
|
min-height: calc(2ch + 30px);
|
||||||
|
box-sizing: content-box;
|
||||||
|
display: flex;
|
||||||
|
align-content: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -25,6 +25,10 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
export let onPresetChange: () => void;
|
export let onPresetChange: () => void;
|
||||||
|
|
||||||
const fsrs = state.fsrs;
|
const fsrs = state.fsrs;
|
||||||
|
let newlyEnabled = false;
|
||||||
|
$: if (!$fsrs) {
|
||||||
|
newlyEnabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
const settings = {
|
const settings = {
|
||||||
fsrs: {
|
fsrs: {
|
||||||
|
@ -94,6 +98,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
{#if $fsrs}
|
{#if $fsrs}
|
||||||
<FsrsOptions
|
<FsrsOptions
|
||||||
{state}
|
{state}
|
||||||
|
{newlyEnabled}
|
||||||
openHelpModal={(key) =>
|
openHelpModal={(key) =>
|
||||||
openHelpModal(Object.keys(settings).indexOf(key))}
|
openHelpModal(Object.keys(settings).indexOf(key))}
|
||||||
{onPresetChange}
|
{onPresetChange}
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
export let max = 9999;
|
export let max = 9999;
|
||||||
export let step = 0.01;
|
export let step = 0.01;
|
||||||
export let percentage = false;
|
export let percentage = false;
|
||||||
|
export let focused = false;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Row --cols={13}>
|
<Row --cols={13}>
|
||||||
|
@ -23,7 +24,7 @@
|
||||||
</Col>
|
</Col>
|
||||||
<Col --col-size={6} breakpoint="xs">
|
<Col --col-size={6} breakpoint="xs">
|
||||||
<ConfigInput>
|
<ConfigInput>
|
||||||
<SpinBox bind:value {min} {max} {step} {percentage} />
|
<SpinBox bind:value {min} {max} {step} {percentage} bind:focused />
|
||||||
<RevertButton slot="revert" bind:value {defaultValue} />
|
<RevertButton slot="revert" bind:value {defaultValue} />
|
||||||
</ConfigInput>
|
</ConfigInput>
|
||||||
</Col>
|
</Col>
|
||||||
|
|
Loading…
Reference in a new issue