mirror of
https://github.com/ankitects/anki.git
synced 2025-09-18 22:12:21 -04:00
move answer_button_time to the backend, split sched into separate module
This commit is contained in:
parent
7b26814922
commit
99c07cfdcb
9 changed files with 171 additions and 27 deletions
|
@ -44,6 +44,7 @@ message BackendInput {
|
|||
Empty check_media = 28;
|
||||
TrashMediaFilesIn trash_media_files = 29;
|
||||
TranslateStringIn translate_string = 30;
|
||||
FormatTimeSpanIn format_time_span = 31;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -64,6 +65,7 @@ message BackendOutput {
|
|||
MediaCheckOut check_media = 28;
|
||||
Empty trash_media_files = 29;
|
||||
string translate_string = 30;
|
||||
string format_time_span = 31;
|
||||
|
||||
BackendError error = 2047;
|
||||
}
|
||||
|
@ -303,3 +305,12 @@ message TranslateArgValue {
|
|||
double number = 2;
|
||||
}
|
||||
}
|
||||
|
||||
message FormatTimeSpanIn {
|
||||
enum Context {
|
||||
ANSWER_BUTTONS = 0;
|
||||
}
|
||||
|
||||
float seconds = 1;
|
||||
Context context = 2;
|
||||
}
|
||||
|
|
|
@ -134,6 +134,8 @@ MediaCheckOutput = pb.MediaCheckOut
|
|||
|
||||
StringsGroup = pb.StringsGroup
|
||||
|
||||
FormatTimeSpanContext = pb.FormatTimeSpanIn.Context
|
||||
|
||||
|
||||
@dataclass
|
||||
class ExtractedLatex:
|
||||
|
@ -342,3 +344,10 @@ class RustBackend:
|
|||
translate_string=pb.TranslateStringIn(group=group, key=key, args=args)
|
||||
)
|
||||
).translate_string
|
||||
|
||||
def format_time_span(self, seconds: float, context: FormatTimeSpanContext) -> str:
|
||||
return self._run_command(
|
||||
pb.BackendInput(
|
||||
format_time_span=pb.FormatTimeSpanIn(seconds=seconds, context=context)
|
||||
)
|
||||
).format_time_span
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
# Copyright: Ankitects Pty Ltd and contributors
|
||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import itertools
|
||||
import random
|
||||
import time
|
||||
|
@ -8,11 +10,13 @@ from heapq import *
|
|||
from operator import itemgetter
|
||||
from typing import List, Optional, Set
|
||||
|
||||
import anki
|
||||
from anki import hooks
|
||||
from anki.cards import Card
|
||||
from anki.consts import *
|
||||
from anki.lang import _
|
||||
from anki.utils import answer_button_time, ids2str, intTime
|
||||
from anki.rsbackend import FormatTimeSpanContext
|
||||
from anki.utils import ids2str, intTime
|
||||
|
||||
# queue types: 0=new/cram, 1=lrn, 2=rev, 3=day lrn, -1=suspended, -2=buried
|
||||
# revlog types: 0=lrn, 1=rev, 2=relrn, 3=cram
|
||||
|
@ -25,7 +29,7 @@ class Scheduler:
|
|||
_spreadRev = True
|
||||
_burySiblingsOnAnswer = True
|
||||
|
||||
def __init__(self, col):
|
||||
def __init__(self, col: anki.storage._Collection) -> None:
|
||||
self.col = col
|
||||
self.queueLimit = 50
|
||||
self.reportLimit = 1000
|
||||
|
@ -33,7 +37,7 @@ class Scheduler:
|
|||
self.lrnCount = 0
|
||||
self.revCount = 0
|
||||
self.newCount = 0
|
||||
self.today = None
|
||||
self.today: Optional[int] = None
|
||||
self._haveQueues = False
|
||||
self._updateCutoff()
|
||||
|
||||
|
@ -1354,7 +1358,9 @@ To study outside of the normal schedule, click the Custom Study button below."""
|
|||
ivl_secs = self.nextIvl(card, ease)
|
||||
if not ivl_secs:
|
||||
return _("(end)")
|
||||
s = answer_button_time(self.col, ivl_secs)
|
||||
s = self.col.backend.format_time_span(
|
||||
ivl_secs, FormatTimeSpanContext.ANSWER_BUTTONS
|
||||
)
|
||||
if ivl_secs < self.col.conf["collapseTime"]:
|
||||
s = "<" + s
|
||||
return s
|
||||
|
|
|
@ -18,8 +18,8 @@ from anki import hooks
|
|||
from anki.cards import Card
|
||||
from anki.consts import *
|
||||
from anki.lang import _
|
||||
from anki.rsbackend import SchedTimingToday
|
||||
from anki.utils import answer_button_time, ids2str, intTime
|
||||
from anki.rsbackend import FormatTimeSpanContext, SchedTimingToday
|
||||
from anki.utils import ids2str, intTime
|
||||
|
||||
# card types: 0=new, 1=lrn, 2=rev, 3=relrn
|
||||
# queue types: 0=new, 1=(re)lrn, 2=rev, 3=day (re)lrn,
|
||||
|
@ -1548,7 +1548,9 @@ To study outside of the normal schedule, click the Custom Study button below."""
|
|||
ivl_secs = self.nextIvl(card, ease)
|
||||
if not ivl_secs:
|
||||
return _("(end)")
|
||||
s = answer_button_time(self.col, ivl_secs)
|
||||
s = self.col.backend.format_time_span(
|
||||
ivl_secs, FormatTimeSpanContext.ANSWER_BUTTONS
|
||||
)
|
||||
if ivl_secs < self.col.conf["collapseTime"]:
|
||||
s = "<" + s
|
||||
return s
|
||||
|
|
|
@ -23,8 +23,6 @@ from hashlib import sha1
|
|||
from html.entities import name2codepoint
|
||||
from typing import Iterable, Iterator, List, Optional, Tuple, Union
|
||||
|
||||
import anki
|
||||
from anki.backend_pb2 import StringsGroup
|
||||
from anki.db import DB
|
||||
from anki.lang import _, ngettext
|
||||
|
||||
|
@ -39,22 +37,6 @@ def intTime(scale: int = 1) -> int:
|
|||
return int(time.time() * scale)
|
||||
|
||||
|
||||
# eg 70 seconds -> (1.16, "minutes")
|
||||
def seconds_to_appropriate_unit(seconds: int) -> Tuple[float, str]:
|
||||
unit, _ = optimalPeriod(seconds, 0, 99)
|
||||
amount = convertSecondsTo(seconds, unit)
|
||||
return (amount, unit)
|
||||
|
||||
|
||||
def answer_button_time(col: anki.storage._Collection, seconds: int) -> str:
|
||||
(amount, unit) = seconds_to_appropriate_unit(seconds)
|
||||
if unit not in ("months", "years"):
|
||||
amount = int(amount)
|
||||
return col.backend.translate(
|
||||
StringsGroup.SCHEDULING, f"answer-button-time-{unit}", amount=amount
|
||||
)
|
||||
|
||||
|
||||
timeTable = {
|
||||
"years": lambda n: ngettext("%s year", "%s years", n),
|
||||
"months": lambda n: ngettext("%s month", "%s months", n),
|
||||
|
|
|
@ -10,7 +10,8 @@ use crate::latex::{extract_latex, ExtractedLatex};
|
|||
use crate::media::check::MediaChecker;
|
||||
use crate::media::sync::MediaSyncProgress;
|
||||
use crate::media::MediaManager;
|
||||
use crate::sched::{local_minutes_west_for_stamp, sched_timing_today};
|
||||
use crate::sched::cutoff::{local_minutes_west_for_stamp, sched_timing_today};
|
||||
use crate::sched::timespan::answer_button_time;
|
||||
use crate::template::{
|
||||
render_card, without_legacy_template_directives, FieldMap, FieldRequirements, ParsedTemplate,
|
||||
RenderedNode,
|
||||
|
@ -200,6 +201,7 @@ impl Backend {
|
|||
OValue::TrashMediaFiles(Empty {})
|
||||
}
|
||||
Value::TranslateString(input) => OValue::TranslateString(self.translate_string(input)),
|
||||
Value::FormatTimeSpan(input) => OValue::FormatTimeSpan(self.format_time_span(input)),
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -397,6 +399,18 @@ impl Backend {
|
|||
|
||||
self.i18n.get(group).trn(&input.key, map)
|
||||
}
|
||||
|
||||
fn format_time_span(&self, input: pb::FormatTimeSpanIn) -> String {
|
||||
let context = match pb::format_time_span_in::Context::from_i32(input.context) {
|
||||
Some(context) => context,
|
||||
None => return "".to_string(),
|
||||
};
|
||||
match context {
|
||||
pb::format_time_span_in::Context::AnswerButtons => {
|
||||
answer_button_time(input.seconds, &self.i18n)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn translate_arg_to_fluent_val(arg: &pb::TranslateArgValue) -> FluentValue {
|
||||
|
|
|
@ -92,7 +92,7 @@ pub fn local_minutes_west_for_stamp(stamp: i64) -> i32 {
|
|||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::sched::{
|
||||
use crate::sched::cutoff::{
|
||||
fixed_offset_from_minutes, local_minutes_west_for_stamp, normalized_rollover_hour,
|
||||
sched_timing_today,
|
||||
};
|
2
rslib/src/sched/mod.rs
Normal file
2
rslib/src/sched/mod.rs
Normal file
|
@ -0,0 +1,2 @@
|
|||
pub mod cutoff;
|
||||
pub mod timespan;
|
118
rslib/src/sched/timespan.rs
Normal file
118
rslib/src/sched/timespan.rs
Normal file
|
@ -0,0 +1,118 @@
|
|||
// Copyright: Ankitects Pty Ltd and contributors
|
||||
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
|
||||
use crate::i18n::{tr_args, I18n, StringsGroup};
|
||||
|
||||
pub fn answer_button_time(seconds: f32, i18n: &I18n) -> String {
|
||||
let span = Timespan::from_secs(seconds).natural_span();
|
||||
let amount = match span.unit() {
|
||||
TimespanUnit::Months | TimespanUnit::Years => span.as_unit(),
|
||||
// we don't show fractional value except for months/years
|
||||
_ => span.as_unit().round(),
|
||||
};
|
||||
let unit = span.unit().as_str();
|
||||
let args = tr_args!["amount" => amount];
|
||||
i18n.get(StringsGroup::Scheduling)
|
||||
.trn(&format!("answer-button-time-{}", unit), args)
|
||||
}
|
||||
|
||||
const SECOND: f32 = 1.0;
|
||||
const MINUTE: f32 = 60.0 * SECOND;
|
||||
const HOUR: f32 = 60.0 * MINUTE;
|
||||
const DAY: f32 = 24.0 * HOUR;
|
||||
const MONTH: f32 = 30.0 * DAY;
|
||||
const YEAR: f32 = 365.0 * MONTH;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
enum TimespanUnit {
|
||||
Seconds,
|
||||
Minutes,
|
||||
Hours,
|
||||
Days,
|
||||
Months,
|
||||
Years,
|
||||
}
|
||||
|
||||
impl TimespanUnit {
|
||||
fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
TimespanUnit::Seconds => "seconds",
|
||||
TimespanUnit::Minutes => "minutes",
|
||||
TimespanUnit::Hours => "hours",
|
||||
TimespanUnit::Days => "days",
|
||||
TimespanUnit::Months => "months",
|
||||
TimespanUnit::Years => "years",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct Timespan {
|
||||
seconds: f32,
|
||||
unit: TimespanUnit,
|
||||
}
|
||||
|
||||
impl Timespan {
|
||||
fn from_secs(seconds: f32) -> Self {
|
||||
Timespan {
|
||||
seconds,
|
||||
unit: TimespanUnit::Seconds,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the value as the configured unit, eg seconds=70/unit=Minutes
|
||||
/// returns 1.17
|
||||
fn as_unit(self) -> f32 {
|
||||
let s = self.seconds;
|
||||
match self.unit {
|
||||
TimespanUnit::Seconds => s,
|
||||
TimespanUnit::Minutes => s / MINUTE,
|
||||
TimespanUnit::Hours => s / HOUR,
|
||||
TimespanUnit::Days => s / DAY,
|
||||
TimespanUnit::Months => s / MONTH,
|
||||
TimespanUnit::Years => s / YEAR,
|
||||
}
|
||||
}
|
||||
|
||||
fn unit(self) -> TimespanUnit {
|
||||
self.unit
|
||||
}
|
||||
|
||||
/// Return a new timespan in the most appropriate unit, eg
|
||||
/// 70 secs -> timespan in minutes
|
||||
fn natural_span(self) -> Timespan {
|
||||
let secs = self.seconds.abs();
|
||||
let unit = if secs < MINUTE {
|
||||
TimespanUnit::Seconds
|
||||
} else if secs < HOUR {
|
||||
TimespanUnit::Minutes
|
||||
} else if secs < DAY {
|
||||
TimespanUnit::Hours
|
||||
} else if secs < MONTH {
|
||||
TimespanUnit::Days
|
||||
} else if secs < YEAR {
|
||||
TimespanUnit::Months
|
||||
} else {
|
||||
TimespanUnit::Years
|
||||
};
|
||||
|
||||
Timespan {
|
||||
seconds: self.seconds,
|
||||
unit,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::i18n::I18n;
|
||||
use crate::sched::timespan::{answer_button_time, MONTH};
|
||||
|
||||
#[test]
|
||||
fn answer_buttons() {
|
||||
let i18n = I18n::new(&["zz"], "");
|
||||
assert_eq!(answer_button_time(30.0, &i18n), "30s");
|
||||
assert_eq!(answer_button_time(70.0, &i18n), "1m");
|
||||
assert_eq!(answer_button_time(1.1 * MONTH, &i18n), "1.10mo");
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue