mirror of
https://github.com/ankitects/anki.git
synced 2025-09-18 22:12:21 -04:00
Feat/Ignored before card count (#3910)
* GetIgnoredBeforeCount * get_card_count_with_ignore_before * Included / total * Respect search * Get frontend hooked up * Fix: Malformed sql and search * Variable names * Added: Alert colours * i18n * ./check * Remove console.log * Fix: Tooltip showing for default value * Update ftl/core/deck-config.ftl Co-authored-by: user1823 <92206575+user1823@users.noreply.github.com> * Fix: Multiple backend calls * Message: (Approximately) * Fix: Bouncing info message * Added: Change delay * Added: ignore_before_updated * ./check * Fix typing, camelCase and improve wording * Temporarily enable the check on startup --------- Co-authored-by: user1823 <92206575+user1823@users.noreply.github.com>
This commit is contained in:
parent
369dec9319
commit
781a23c6c4
8 changed files with 126 additions and 1 deletions
|
@ -372,6 +372,7 @@ deck-config-good-above-easy = The easy interval should be at least as long as th
|
||||||
deck-config-relearning-steps-above-minimum-interval = The minimum lapse interval should be at least as long as your final relearning step.
|
deck-config-relearning-steps-above-minimum-interval = The minimum lapse interval should be at least as long as your final relearning step.
|
||||||
deck-config-maximum-answer-secs-above-recommended = Anki can schedule your reviews more efficiently when you keep each question short.
|
deck-config-maximum-answer-secs-above-recommended = Anki can schedule your reviews more efficiently when you keep each question short.
|
||||||
deck-config-too-short-maximum-interval = A maximum interval less than 6 months is not recommended.
|
deck-config-too-short-maximum-interval = A maximum interval less than 6 months is not recommended.
|
||||||
|
deck-config-ignore-before-info = (Approximately) { $included }/{ $totalCards } cards will be used to optimize the FSRS parameters.
|
||||||
|
|
||||||
## Selecting a deck
|
## Selecting a deck
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,8 @@ service DeckConfigService {
|
||||||
rpc GetDeckConfigsForUpdate(decks.DeckId) returns (DeckConfigsForUpdate);
|
rpc GetDeckConfigsForUpdate(decks.DeckId) returns (DeckConfigsForUpdate);
|
||||||
rpc UpdateDeckConfigs(UpdateDeckConfigsRequest)
|
rpc UpdateDeckConfigs(UpdateDeckConfigsRequest)
|
||||||
returns (collection.OpChanges);
|
returns (collection.OpChanges);
|
||||||
|
rpc GetIgnoredBeforeCount(GetIgnoredBeforeCountRequest)
|
||||||
|
returns (GetIgnoredBeforeCountResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
||||||
|
@ -33,6 +35,16 @@ message DeckConfigId {
|
||||||
int64 dcid = 1;
|
int64 dcid = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message GetIgnoredBeforeCountRequest {
|
||||||
|
string ignore_revlogs_before_date = 1;
|
||||||
|
string search = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetIgnoredBeforeCountResponse {
|
||||||
|
uint64 included = 1;
|
||||||
|
uint64 total = 2;
|
||||||
|
}
|
||||||
|
|
||||||
message DeckConfig {
|
message DeckConfig {
|
||||||
message Config {
|
message Config {
|
||||||
enum NewCardInsertOrder {
|
enum NewCardInsertOrder {
|
||||||
|
|
|
@ -652,6 +652,8 @@ exposed_backend_list = [
|
||||||
"evaluate_params",
|
"evaluate_params",
|
||||||
"get_optimal_retention_parameters",
|
"get_optimal_retention_parameters",
|
||||||
"simulate_fsrs_review",
|
"simulate_fsrs_review",
|
||||||
|
# DeckConfigService
|
||||||
|
"get_ignored_before_count",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ use crate::deckconfig::DeckConfig;
|
||||||
use crate::deckconfig::DeckConfigId;
|
use crate::deckconfig::DeckConfigId;
|
||||||
use crate::deckconfig::UpdateDeckConfigsRequest;
|
use crate::deckconfig::UpdateDeckConfigsRequest;
|
||||||
use crate::error::Result;
|
use crate::error::Result;
|
||||||
|
use crate::scheduler::fsrs::params::ignore_revlogs_before_date_to_ms;
|
||||||
|
|
||||||
impl crate::services::DeckConfigService for Collection {
|
impl crate::services::DeckConfigService for Collection {
|
||||||
fn add_or_update_deck_config_legacy(
|
fn add_or_update_deck_config_legacy(
|
||||||
|
@ -76,6 +77,25 @@ impl crate::services::DeckConfigService for Collection {
|
||||||
) -> Result<anki_proto::collection::OpChanges> {
|
) -> Result<anki_proto::collection::OpChanges> {
|
||||||
self.update_deck_configs(input.into()).map(Into::into)
|
self.update_deck_configs(input.into()).map(Into::into)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_ignored_before_count(
|
||||||
|
&mut self,
|
||||||
|
input: anki_proto::deck_config::GetIgnoredBeforeCountRequest,
|
||||||
|
) -> Result<anki_proto::deck_config::GetIgnoredBeforeCountResponse> {
|
||||||
|
let timestamp = ignore_revlogs_before_date_to_ms(&input.ignore_revlogs_before_date)?;
|
||||||
|
let guard = self.search_cards_into_table(
|
||||||
|
&format!("{} -is:new", input.search),
|
||||||
|
crate::search::SortMode::NoOrder,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(anki_proto::deck_config::GetIgnoredBeforeCountResponse {
|
||||||
|
included: guard
|
||||||
|
.col
|
||||||
|
.storage
|
||||||
|
.get_card_count_with_ignore_before(timestamp)?,
|
||||||
|
total: guard.cards.try_into().unwrap_or(0),
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<DeckConfig> for anki_proto::deck_config::DeckConfig {
|
impl From<DeckConfig> for anki_proto::deck_config::DeckConfig {
|
||||||
|
|
|
@ -33,7 +33,7 @@ use crate::search::SortMode;
|
||||||
|
|
||||||
pub(crate) type Params = Vec<f32>;
|
pub(crate) type Params = Vec<f32>;
|
||||||
|
|
||||||
fn ignore_revlogs_before_date_to_ms(
|
pub(crate) fn ignore_revlogs_before_date_to_ms(
|
||||||
ignore_revlogs_before_date: &String,
|
ignore_revlogs_before_date: &String,
|
||||||
) -> Result<TimestampMillis> {
|
) -> Result<TimestampMillis> {
|
||||||
Ok(match ignore_revlogs_before_date {
|
Ok(match ignore_revlogs_before_date {
|
||||||
|
|
5
rslib/src/storage/card/get_ignored_before_count.sql
Normal file
5
rslib/src/storage/card/get_ignored_before_count.sql
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
SELECT COUNT(DISTINCT cid)
|
||||||
|
FROM revlog
|
||||||
|
WHERE id > ?
|
||||||
|
AND type == 0
|
||||||
|
AND cid IN search_cids
|
|
@ -732,6 +732,20 @@ impl super::SqliteStorage {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn get_card_count_with_ignore_before(
|
||||||
|
&self,
|
||||||
|
ignore_before: TimestampMillis,
|
||||||
|
) -> Result<u64> {
|
||||||
|
Ok(self
|
||||||
|
.db
|
||||||
|
.prepare(include_str!("get_ignored_before_count.sql"))?
|
||||||
|
.query(params![ignore_before.0])?
|
||||||
|
.next()
|
||||||
|
.unwrap()
|
||||||
|
.unwrap()
|
||||||
|
.get(0)?)
|
||||||
|
}
|
||||||
|
|
||||||
#[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
|
||||||
|
|
|
@ -21,6 +21,8 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
import SpinBoxRow from "./SpinBoxRow.svelte";
|
import SpinBoxRow from "./SpinBoxRow.svelte";
|
||||||
import DateInput from "./DateInput.svelte";
|
import DateInput from "./DateInput.svelte";
|
||||||
import Warning from "./Warning.svelte";
|
import Warning from "./Warning.svelte";
|
||||||
|
import { getIgnoredBeforeCount } from "@generated/backend";
|
||||||
|
import type { GetIgnoredBeforeCountResponse } from "@generated/anki/deck_config_pb";
|
||||||
|
|
||||||
export let state: DeckOptionsState;
|
export let state: DeckOptionsState;
|
||||||
export let api: Record<string, never>;
|
export let api: Record<string, never>;
|
||||||
|
@ -91,6 +93,68 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
? tr.deckConfigTooShortMaximumInterval()
|
? tr.deckConfigTooShortMaximumInterval()
|
||||||
: "";
|
: "";
|
||||||
|
|
||||||
|
let ignoreRevlogsBeforeCount: GetIgnoredBeforeCountResponse | null = null;
|
||||||
|
let lastIgnoreRevlogsBeforeDate = "";
|
||||||
|
function updateIgnoreRevlogsBeforeCount(ignoreRevlogsBeforeDate: string) {
|
||||||
|
if (lastIgnoreRevlogsBeforeDate == ignoreRevlogsBeforeDate) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
cutoffUpdatedSinceLoad &&
|
||||||
|
ignoreRevlogsBeforeDate &&
|
||||||
|
ignoreRevlogsBeforeDate != "1970-01-01"
|
||||||
|
) {
|
||||||
|
lastIgnoreRevlogsBeforeDate = ignoreRevlogsBeforeDate;
|
||||||
|
getIgnoredBeforeCount({
|
||||||
|
search:
|
||||||
|
$config.paramSearch ||
|
||||||
|
`preset:"${state.getCurrentNameForSearch()}" -is:suspended`,
|
||||||
|
ignoreRevlogsBeforeDate,
|
||||||
|
}).then((resp) => {
|
||||||
|
ignoreRevlogsBeforeCount = resp;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
ignoreRevlogsBeforeCount = null;
|
||||||
|
}
|
||||||
|
cutoffUpdatedSinceLoad = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
let timeoutId: ReturnType<typeof setTimeout> | undefined = undefined;
|
||||||
|
// Running the card count check on startup is inefficient. After users have had a few months
|
||||||
|
// to notice + update (e.g. from ~Oct 2025), we should change this to false.
|
||||||
|
let cutoffUpdatedSinceLoad = true;
|
||||||
|
const IGNORE_REVLOG_COUNT_DELAY_MS = 1000;
|
||||||
|
|
||||||
|
$: {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
timeoutId = setTimeout(() => {
|
||||||
|
updateIgnoreRevlogsBeforeCount($config.ignoreRevlogsBeforeDate);
|
||||||
|
}, IGNORE_REVLOG_COUNT_DELAY_MS);
|
||||||
|
}
|
||||||
|
let ignoreRevlogsBeforeWarningClass = "alert-warning";
|
||||||
|
$: if (ignoreRevlogsBeforeCount) {
|
||||||
|
// If there is less than a tenth of reviews included
|
||||||
|
if (
|
||||||
|
Number(ignoreRevlogsBeforeCount.included) /
|
||||||
|
Number(ignoreRevlogsBeforeCount.total) <
|
||||||
|
0.1
|
||||||
|
) {
|
||||||
|
ignoreRevlogsBeforeWarningClass = "alert-danger";
|
||||||
|
} else if (
|
||||||
|
ignoreRevlogsBeforeCount.included != ignoreRevlogsBeforeCount.total
|
||||||
|
) {
|
||||||
|
ignoreRevlogsBeforeWarningClass = "alert-warning";
|
||||||
|
} else {
|
||||||
|
ignoreRevlogsBeforeWarningClass = "alert-info";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$: ignoreRevlogsBeforeWarning = ignoreRevlogsBeforeCount
|
||||||
|
? tr.deckConfigIgnoreBeforeInfo({
|
||||||
|
included: ignoreRevlogsBeforeCount.included.toString(),
|
||||||
|
totalCards: ignoreRevlogsBeforeCount.total.toString(),
|
||||||
|
})
|
||||||
|
: "";
|
||||||
|
|
||||||
let modal: Modal;
|
let modal: Modal;
|
||||||
let carousel: Carousel;
|
let carousel: Carousel;
|
||||||
|
|
||||||
|
@ -249,6 +313,13 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
</SettingTitle>
|
</SettingTitle>
|
||||||
</DateInput>
|
</DateInput>
|
||||||
</Item>
|
</Item>
|
||||||
|
|
||||||
|
<Item>
|
||||||
|
<Warning
|
||||||
|
warning={ignoreRevlogsBeforeWarning}
|
||||||
|
className={ignoreRevlogsBeforeWarningClass}
|
||||||
|
></Warning>
|
||||||
|
</Item>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<Item>
|
<Item>
|
||||||
|
|
Loading…
Reference in a new issue