mirror of
https://github.com/ankitects/anki.git
synced 2025-09-18 14:02: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-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-ignore-before-info = (Approximately) { $included }/{ $totalCards } cards will be used to optimize the FSRS parameters.
|
||||
|
||||
## Selecting a deck
|
||||
|
||||
|
|
|
@ -23,6 +23,8 @@ service DeckConfigService {
|
|||
rpc GetDeckConfigsForUpdate(decks.DeckId) returns (DeckConfigsForUpdate);
|
||||
rpc UpdateDeckConfigs(UpdateDeckConfigsRequest)
|
||||
returns (collection.OpChanges);
|
||||
rpc GetIgnoredBeforeCount(GetIgnoredBeforeCountRequest)
|
||||
returns (GetIgnoredBeforeCountResponse);
|
||||
}
|
||||
|
||||
// Implicitly includes any of the above methods that are not listed in the
|
||||
|
@ -33,6 +35,16 @@ message DeckConfigId {
|
|||
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 Config {
|
||||
enum NewCardInsertOrder {
|
||||
|
|
|
@ -652,6 +652,8 @@ exposed_backend_list = [
|
|||
"evaluate_params",
|
||||
"get_optimal_retention_parameters",
|
||||
"simulate_fsrs_review",
|
||||
# DeckConfigService
|
||||
"get_ignored_before_count",
|
||||
]
|
||||
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ use crate::deckconfig::DeckConfig;
|
|||
use crate::deckconfig::DeckConfigId;
|
||||
use crate::deckconfig::UpdateDeckConfigsRequest;
|
||||
use crate::error::Result;
|
||||
use crate::scheduler::fsrs::params::ignore_revlogs_before_date_to_ms;
|
||||
|
||||
impl crate::services::DeckConfigService for Collection {
|
||||
fn add_or_update_deck_config_legacy(
|
||||
|
@ -76,6 +77,25 @@ impl crate::services::DeckConfigService for Collection {
|
|||
) -> Result<anki_proto::collection::OpChanges> {
|
||||
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 {
|
||||
|
|
|
@ -33,7 +33,7 @@ use crate::search::SortMode;
|
|||
|
||||
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,
|
||||
) -> Result<TimestampMillis> {
|
||||
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(())
|
||||
}
|
||||
|
||||
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)]
|
||||
pub(crate) fn get_all_cards(&self) -> Vec<Card> {
|
||||
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 DateInput from "./DateInput.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 api: Record<string, never>;
|
||||
|
@ -91,6 +93,68 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
? 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 carousel: Carousel;
|
||||
|
||||
|
@ -249,6 +313,13 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
</SettingTitle>
|
||||
</DateInput>
|
||||
</Item>
|
||||
|
||||
<Item>
|
||||
<Warning
|
||||
warning={ignoreRevlogsBeforeWarning}
|
||||
className={ignoreRevlogsBeforeWarningClass}
|
||||
></Warning>
|
||||
</Item>
|
||||
{/if}
|
||||
|
||||
<Item>
|
||||
|
|
Loading…
Reference in a new issue