mirror of
https://github.com/ankitects/anki.git
synced 2025-09-19 06:22:22 -04:00
Merge branch 'main' into editor-3830
This commit is contained in:
commit
c91f943f29
34 changed files with 260 additions and 134 deletions
2
.version
2
.version
|
@ -1 +1 @@
|
|||
25.08b1
|
||||
25.07.3
|
||||
|
|
|
@ -234,6 +234,8 @@ Emmanuel Ferdman <https://github.com/emmanuel-ferdman>
|
|||
Sunong2008 <https://github.com/Sunrongguo2008>
|
||||
Marvin Kopf <marvinkopf@outlook.com>
|
||||
Kevin Nakamura <grinkers@grinkers.net>
|
||||
Bradley Szoke <bradleyszoke@gmail.com>
|
||||
jcznk <https://github.com/jcznk>
|
||||
|
||||
********************
|
||||
|
||||
|
|
|
@ -141,7 +141,7 @@ walkdir = "2.5.0"
|
|||
which = "8.0.0"
|
||||
widestring = "1.1.0"
|
||||
winapi = { version = "0.3", features = ["wincon", "winreg"] }
|
||||
windows = { version = "0.61.3", features = ["Media_SpeechSynthesis", "Media_Core", "Foundation_Collections", "Storage_Streams", "Win32_System_Console", "Win32_System_Registry", "Win32_Foundation", "Win32_UI_Shell"] }
|
||||
windows = { version = "0.61.3", features = ["Media_SpeechSynthesis", "Media_Core", "Foundation_Collections", "Storage_Streams", "Win32_System_Console", "Win32_System_Registry", "Win32_System_SystemInformation", "Win32_Foundation", "Win32_UI_Shell", "Wdk_System_SystemServices"] }
|
||||
wiremock = "0.6.3"
|
||||
xz2 = "0.1.7"
|
||||
zip = { version = "4.1.0", default-features = false, features = ["deflate", "time"] }
|
||||
|
|
|
@ -32,10 +32,19 @@ pub fn setup_pyenv(args: PyenvArgs) {
|
|||
}
|
||||
}
|
||||
|
||||
let mut command = Command::new(args.uv_bin);
|
||||
|
||||
// remove UV_* environment variables to avoid interference
|
||||
for (key, _) in std::env::vars() {
|
||||
if key.starts_with("UV_") || key == "VIRTUAL_ENV" {
|
||||
command.env_remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
run_command(
|
||||
Command::new(args.uv_bin)
|
||||
command
|
||||
.env("UV_PROJECT_ENVIRONMENT", args.pyenv_folder.clone())
|
||||
.args(["sync", "--locked"])
|
||||
.args(["sync", "--locked", "--no-config"])
|
||||
.args(args.extra_args),
|
||||
);
|
||||
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit a9216499ba1fb1538cfd740c698adaaa3410fd4b
|
||||
Subproject commit b90ef6f03c251eb336029ac7c5f551200d41273f
|
|
@ -34,7 +34,7 @@ preferences-when-adding-default-to-current-deck = When adding, default to curren
|
|||
preferences-you-can-restore-backups-via-fileswitch = You can restore backups via File > Switch Profile.
|
||||
preferences-legacy-timezone-handling = Legacy timezone handling (buggy, but required for AnkiDroid <= 2.14)
|
||||
preferences-default-search-text = Default search text
|
||||
preferences-default-search-text-example = eg. 'deck:current '
|
||||
preferences-default-search-text-example = e.g. "deck:current"
|
||||
preferences-theme = Theme
|
||||
preferences-theme-follow-system = Follow System
|
||||
preferences-theme-light = Light
|
||||
|
|
|
@ -80,7 +80,7 @@ statistics-reviews =
|
|||
# This fragment of the tooltip in the FSRS simulation
|
||||
# diagram (Deck options -> FSRS) shows the total number of
|
||||
# cards that can be recalled or retrieved on a specific date.
|
||||
statistics-memorized = {$memorized} memorized
|
||||
statistics-memorized = {$memorized} cards memorized
|
||||
statistics-today-title = Today
|
||||
statistics-today-again-count = Again count:
|
||||
statistics-today-type-counts = Learn: { $learnCount }, Review: { $reviewCount }, Relearn: { $relearnCount }, Filtered: { $filteredCount }
|
||||
|
@ -99,9 +99,9 @@ statistics-counts-relearning-cards = Relearning
|
|||
statistics-counts-title = Card Counts
|
||||
statistics-counts-separate-suspended-buried-cards = Separate suspended/buried cards
|
||||
|
||||
## Retention rate represents your actual retention rate from past reviews, in
|
||||
## Retention represents your actual retention from past reviews, in
|
||||
## comparison to the "desired retention" setting of FSRS, which forecasts
|
||||
## future retention. Retention rate is the percentage of all reviewed cards
|
||||
## future retention. Retention is the percentage of all reviewed cards
|
||||
## that were marked as "Hard," "Good," or "Easy" within a specific time period.
|
||||
##
|
||||
## Most of these strings are used as column / row headings in a table.
|
||||
|
@ -112,9 +112,9 @@ statistics-counts-separate-suspended-buried-cards = Separate suspended/buried ca
|
|||
## N.B. Stats cards may be very small on mobile devices and when the Stats
|
||||
## window is certain sizes.
|
||||
|
||||
statistics-true-retention-title = Retention rate
|
||||
statistics-true-retention-title = Retention
|
||||
statistics-true-retention-subtitle = Pass rate of cards with an interval ≥ 1 day.
|
||||
statistics-true-retention-tooltip = If you are using FSRS, your retention rate is expected to be close to your desired retention. Please keep in mind that data for a single day is noisy, so it's better to look at monthly data.
|
||||
statistics-true-retention-tooltip = If you are using FSRS, your retention is expected to be close to your desired retention. Please keep in mind that data for a single day is noisy, so it's better to look at monthly data.
|
||||
statistics-true-retention-range = Range
|
||||
statistics-true-retention-pass = Pass
|
||||
statistics-true-retention-fail = Fail
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit a1134ab59d3d23468af2968741aa1f21d16ff308
|
||||
Subproject commit 9aa63c335c61b30421d39cf43fd8e3975179059c
|
|
@ -404,6 +404,7 @@ message SimulateFsrsReviewRequest {
|
|||
repeated float easy_days_percentages = 10;
|
||||
deck_config.DeckConfig.Config.ReviewCardOrder review_order = 11;
|
||||
optional uint32 suspend_after_lapse_count = 12;
|
||||
float historical_retention = 13;
|
||||
}
|
||||
|
||||
message SimulateFsrsReviewResponse {
|
||||
|
|
|
@ -7,7 +7,7 @@ dependencies = [
|
|||
"decorator",
|
||||
"markdown",
|
||||
"orjson",
|
||||
"protobuf>=4.21",
|
||||
"protobuf>=6.0,<8.0",
|
||||
"requests[socks]",
|
||||
# remove after we update to min python 3.11+
|
||||
"typing_extensions",
|
||||
|
|
|
@ -70,10 +70,10 @@ def show(mw: aqt.AnkiQt) -> QDialog:
|
|||
abouttext += f"<p>{lede}"
|
||||
abouttext += f"<p>{tr.about_anki_is_licensed_under_the_agpl3()}"
|
||||
abouttext += f"<p>{tr.about_version(val=version_with_build())}<br>"
|
||||
abouttext += ("Python %s Qt %s PyQt %s<br>") % (
|
||||
abouttext += ("Python %s Qt %s Chromium %s<br>") % (
|
||||
platform.python_version(),
|
||||
qVersion(),
|
||||
PYQT_VERSION_STR,
|
||||
(qWebEngineChromiumVersion() or "").split(".")[0],
|
||||
)
|
||||
abouttext += (
|
||||
without_unicode_isolation(tr.about_visit_website(val=aqt.appWebsite))
|
||||
|
@ -225,6 +225,7 @@ def show(mw: aqt.AnkiQt) -> QDialog:
|
|||
"Adnane Taghi",
|
||||
"Anon_0000",
|
||||
"Bilolbek Normuminov",
|
||||
"Sagiv Marzini",
|
||||
)
|
||||
)
|
||||
|
||||
|
|
|
@ -1292,9 +1292,10 @@
|
|||
<tabstop>daily_backups</tabstop>
|
||||
<tabstop>weekly_backups</tabstop>
|
||||
<tabstop>monthly_backups</tabstop>
|
||||
<tabstop>tabWidget</tabstop>
|
||||
<tabstop>syncAnkiHubLogout</tabstop>
|
||||
<tabstop>syncAnkiHubLogin</tabstop>
|
||||
<tabstop>buttonBox</tabstop>
|
||||
<tabstop>tabWidget</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections>
|
||||
|
|
|
@ -124,17 +124,14 @@ def launcher_executable() -> str | None:
|
|||
|
||||
|
||||
def trigger_launcher_run() -> None:
|
||||
"""Bump the mtime on pyproject.toml in the local data directory to trigger an update on next run."""
|
||||
"""Create a trigger file to request launcher UI on next run."""
|
||||
try:
|
||||
root = launcher_root()
|
||||
if not root:
|
||||
return
|
||||
|
||||
pyproject_path = Path(root) / "pyproject.toml"
|
||||
|
||||
if pyproject_path.exists():
|
||||
# Touch the file to update its mtime
|
||||
pyproject_path.touch()
|
||||
trigger_path = Path(root) / ".want-launcher"
|
||||
trigger_path.touch()
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
|
|
|
@ -82,11 +82,14 @@ class Preferences(QDialog):
|
|||
)
|
||||
group = self.form.preferences_answer_keys
|
||||
group.setLayout(layout := QFormLayout())
|
||||
tab_widget: QWidget = self.form.url_schemes
|
||||
for ease, label in ease_labels:
|
||||
layout.addRow(
|
||||
label,
|
||||
line_edit := QLineEdit(self.mw.pm.get_answer_key(ease) or ""),
|
||||
)
|
||||
QWidget.setTabOrder(tab_widget, line_edit)
|
||||
tab_widget = line_edit
|
||||
qconnect(
|
||||
line_edit.textChanged,
|
||||
functools.partial(self.mw.pm.set_answer_key, ease),
|
||||
|
|
|
@ -177,9 +177,13 @@ class CustomStyles:
|
|||
QPushButton:default {{
|
||||
border: 1px solid {tm.var(colors.BORDER_FOCUS)};
|
||||
}}
|
||||
QPushButton {{
|
||||
margin: 1px;
|
||||
}}
|
||||
QPushButton:focus {{
|
||||
border: 2px solid {tm.var(colors.BORDER_FOCUS)};
|
||||
outline: none;
|
||||
margin: 0px;
|
||||
}}
|
||||
QPushButton:hover,
|
||||
QTabBar::tab:hover,
|
||||
|
|
|
@ -69,17 +69,14 @@ def add_python_requirements(reqs: list[str]) -> tuple[bool, str]:
|
|||
|
||||
|
||||
def trigger_launcher_run() -> None:
|
||||
"""Bump the mtime on pyproject.toml in the local data directory to trigger an update on next run."""
|
||||
"""Create a trigger file to request launcher UI on next run."""
|
||||
try:
|
||||
root = launcher_root()
|
||||
if not root:
|
||||
return
|
||||
|
||||
pyproject_path = Path(root) / "pyproject.toml"
|
||||
|
||||
if pyproject_path.exists():
|
||||
# Touch the file to update its mtime
|
||||
pyproject_path.touch()
|
||||
trigger_path = Path(root) / ".want-launcher"
|
||||
trigger_path.touch()
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
|
|
|
@ -13,7 +13,8 @@ HOST_ARCH=$(uname -m)
|
|||
|
||||
# Define output paths
|
||||
OUTPUT_DIR="../../../out/launcher"
|
||||
LAUNCHER_DIR="$OUTPUT_DIR/anki-linux"
|
||||
ANKI_VERSION=$(cat ../../../.version | tr -d '\n')
|
||||
LAUNCHER_DIR="$OUTPUT_DIR/anki-launcher-$ANKI_VERSION-linux"
|
||||
|
||||
# Clean existing output directory
|
||||
rm -rf "$LAUNCHER_DIR"
|
||||
|
@ -77,8 +78,8 @@ chmod +x \
|
|||
chmod -R a+r "$LAUNCHER_DIR"
|
||||
|
||||
ZSTD="zstd -c --long -T0 -18"
|
||||
TRANSFORM="s%^.%anki-linux%S"
|
||||
TARBALL="$OUTPUT_DIR/anki-linux.tar.zst"
|
||||
TRANSFORM="s%^.%anki-launcher-$ANKI_VERSION-linux%S"
|
||||
TARBALL="$OUTPUT_DIR/anki-launcher-$ANKI_VERSION-linux.tar.zst"
|
||||
|
||||
tar -I "$ZSTD" --transform "$TRANSFORM" -cf "$TARBALL" -C "$LAUNCHER_DIR" .
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<key>CFBundleDisplayName</key>
|
||||
<string>Anki</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<string>ANKI_VERSION</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>12</string>
|
||||
<key>LSApplicationCategoryType</key>
|
||||
|
|
|
@ -31,7 +31,8 @@ lipo -create \
|
|||
cp "$OUTPUT_DIR/uv" "$APP_LAUNCHER/Contents/MacOS/"
|
||||
|
||||
# Copy support files
|
||||
cp Info.plist "$APP_LAUNCHER/Contents/"
|
||||
ANKI_VERSION=$(cat ../../../.version | tr -d '\n')
|
||||
sed "s/ANKI_VERSION/$ANKI_VERSION/g" Info.plist > "$APP_LAUNCHER/Contents/Info.plist"
|
||||
cp icon/Assets.car "$APP_LAUNCHER/Contents/Resources/"
|
||||
cp ../pyproject.toml "$APP_LAUNCHER/Contents/Resources/"
|
||||
cp ../../../.python-version "$APP_LAUNCHER/Contents/Resources/"
|
||||
|
|
|
@ -6,7 +6,8 @@ set -e
|
|||
# base folder with Anki.app in it
|
||||
output="$1"
|
||||
dist="$1/tmp"
|
||||
dmg_path="$output/Anki.dmg"
|
||||
ANKI_VERSION=$(cat ../../../.version | tr -d '\n')
|
||||
dmg_path="$output/anki-launcher-$ANKI_VERSION-mac.dmg"
|
||||
|
||||
if [ -d "/Volumes/Anki" ]
|
||||
then
|
||||
|
|
|
@ -22,6 +22,11 @@ const NSIS_PATH: &str = "C:\\Program Files (x86)\\NSIS\\makensis.exe";
|
|||
fn main() -> Result<()> {
|
||||
println!("Building Windows launcher...");
|
||||
|
||||
// Read version early so it can be used throughout the build process
|
||||
let version = std::fs::read_to_string("../../../.version")?
|
||||
.trim()
|
||||
.to_string();
|
||||
|
||||
let output_dir = PathBuf::from(OUTPUT_DIR);
|
||||
let launcher_exe_dir = PathBuf::from(LAUNCHER_EXE_DIR);
|
||||
let nsis_dir = PathBuf::from(NSIS_DIR);
|
||||
|
@ -31,16 +36,20 @@ fn main() -> Result<()> {
|
|||
extract_nsis_plugins()?;
|
||||
copy_files(&output_dir)?;
|
||||
sign_binaries(&output_dir)?;
|
||||
copy_nsis_files(&nsis_dir)?;
|
||||
copy_nsis_files(&nsis_dir, &version)?;
|
||||
build_uninstaller(&output_dir, &nsis_dir)?;
|
||||
sign_file(&output_dir.join("uninstall.exe"))?;
|
||||
generate_install_manifest(&output_dir)?;
|
||||
build_installer(&output_dir, &nsis_dir)?;
|
||||
sign_file(&PathBuf::from("../../../out/launcher_exe/anki-install.exe"))?;
|
||||
|
||||
let installer_filename = format!("anki-launcher-{version}-windows.exe");
|
||||
let installer_path = PathBuf::from("../../../out/launcher_exe").join(&installer_filename);
|
||||
|
||||
sign_file(&installer_path)?;
|
||||
|
||||
println!("Build completed successfully!");
|
||||
println!("Output directory: {}", output_dir.display());
|
||||
println!("Installer: ../../../out/launcher_exe/anki-install.exe");
|
||||
println!("Installer: ../../../out/launcher_exe/{installer_filename}");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -235,11 +244,13 @@ fn generate_install_manifest(output_dir: &Path) -> Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn copy_nsis_files(nsis_dir: &Path) -> Result<()> {
|
||||
fn copy_nsis_files(nsis_dir: &Path, version: &str) -> Result<()> {
|
||||
println!("Copying NSIS support files...");
|
||||
|
||||
// Copy anki.template.nsi as anki.nsi
|
||||
copy_file("anki.template.nsi", nsis_dir.join("anki.nsi"))?;
|
||||
// Copy anki.template.nsi as anki.nsi and substitute version placeholders
|
||||
let template_content = std::fs::read_to_string("anki.template.nsi")?;
|
||||
let substituted_content = template_content.replace("ANKI_VERSION", version);
|
||||
write_file(nsis_dir.join("anki.nsi"), substituted_content)?;
|
||||
|
||||
// Copy fileassoc.nsh
|
||||
copy_file("fileassoc.nsh", nsis_dir.join("fileassoc.nsh"))?;
|
||||
|
|
|
@ -46,6 +46,8 @@ struct State {
|
|||
dist_python_version_path: std::path::PathBuf,
|
||||
uv_lock_path: std::path::PathBuf,
|
||||
sync_complete_marker: std::path::PathBuf,
|
||||
launcher_trigger_file: std::path::PathBuf,
|
||||
pyproject_modified_by_user: bool,
|
||||
previous_version: Option<String>,
|
||||
resources_dir: std::path::PathBuf,
|
||||
}
|
||||
|
@ -70,7 +72,6 @@ pub enum MainMenuChoice {
|
|||
ToggleBetas,
|
||||
ToggleCache,
|
||||
Uninstall,
|
||||
Quit,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
|
@ -106,6 +107,8 @@ fn run() -> Result<()> {
|
|||
dist_python_version_path: resources_dir.join(".python-version"),
|
||||
uv_lock_path: uv_install_root.join("uv.lock"),
|
||||
sync_complete_marker: uv_install_root.join(".sync_complete"),
|
||||
launcher_trigger_file: uv_install_root.join(".want-launcher"),
|
||||
pyproject_modified_by_user: false, // calculated later
|
||||
previous_version: None,
|
||||
resources_dir,
|
||||
};
|
||||
|
@ -125,15 +128,15 @@ fn run() -> Result<()> {
|
|||
&state.user_python_version_path,
|
||||
)?;
|
||||
|
||||
let pyproject_has_changed = !state.sync_complete_marker.exists() || {
|
||||
let pyproject_toml_time = modified_time(&state.user_pyproject_path)?;
|
||||
let sync_complete_time = modified_time(&state.sync_complete_marker)?;
|
||||
Ok::<bool, anyhow::Error>(pyproject_toml_time > sync_complete_time)
|
||||
}
|
||||
.unwrap_or(true);
|
||||
let launcher_requested = state.launcher_trigger_file.exists();
|
||||
|
||||
if !pyproject_has_changed {
|
||||
// If venv is already up to date, launch Anki normally
|
||||
// Calculate whether user has custom edits that need syncing
|
||||
let pyproject_time = file_timestamp_secs(&state.user_pyproject_path);
|
||||
let sync_time = file_timestamp_secs(&state.sync_complete_marker);
|
||||
state.pyproject_modified_by_user = pyproject_time > sync_time;
|
||||
let pyproject_has_changed = state.pyproject_modified_by_user;
|
||||
if !launcher_requested && !pyproject_has_changed {
|
||||
// If no launcher request and venv is already up to date, launch Anki normally
|
||||
let args: Vec<String> = std::env::args().skip(1).collect();
|
||||
let cmd = build_python_command(&state, &args)?;
|
||||
launch_anki_normally(cmd)?;
|
||||
|
@ -143,6 +146,11 @@ fn run() -> Result<()> {
|
|||
// If we weren't in a terminal, respawn ourselves in one
|
||||
ensure_terminal_shown()?;
|
||||
|
||||
if launcher_requested {
|
||||
// Remove the trigger file to make request ephemeral
|
||||
let _ = remove_file(&state.launcher_trigger_file);
|
||||
}
|
||||
|
||||
print!("\x1B[2J\x1B[H"); // Clear screen and move cursor to top
|
||||
println!("\x1B[1mAnki Launcher\x1B[0m\n");
|
||||
|
||||
|
@ -190,6 +198,7 @@ fn extract_aqt_version(
|
|||
) -> Option<String> {
|
||||
let output = Command::new(uv_path)
|
||||
.current_dir(uv_install_root)
|
||||
.env("VIRTUAL_ENV", uv_install_root.join(".venv"))
|
||||
.args(["pip", "show", "aqt"])
|
||||
.output()
|
||||
.ok()?;
|
||||
|
@ -255,29 +264,21 @@ fn handle_version_install_or_update(state: &State, choice: MainMenuChoice) -> Re
|
|||
None
|
||||
};
|
||||
|
||||
// `uv sync` sometimes does not pull in Python automatically
|
||||
// This might be system/platform specific and/or a uv bug.
|
||||
// Prepare to sync the venv
|
||||
let mut command = Command::new(&state.uv_path);
|
||||
command
|
||||
.current_dir(&state.uv_install_root)
|
||||
.env("UV_CACHE_DIR", &state.uv_cache_dir)
|
||||
.env("UV_PYTHON_INSTALL_DIR", &state.uv_python_install_dir)
|
||||
.args(["python", "install", "--managed-python"]);
|
||||
command.current_dir(&state.uv_install_root);
|
||||
|
||||
// Add python version if .python-version file exists
|
||||
if let Some(version) = &python_version_trimmed {
|
||||
command.args([version]);
|
||||
// remove UV_* environment variables to avoid interference
|
||||
for (key, _) in std::env::vars() {
|
||||
if key.starts_with("UV_") || key == "VIRTUAL_ENV" {
|
||||
command.env_remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
command.ensure_success().context("Python install failed")?;
|
||||
|
||||
// Sync the venv
|
||||
let mut command = Command::new(&state.uv_path);
|
||||
command
|
||||
.current_dir(&state.uv_install_root)
|
||||
.env("UV_CACHE_DIR", &state.uv_cache_dir)
|
||||
.env("UV_PYTHON_INSTALL_DIR", &state.uv_python_install_dir)
|
||||
.args(["sync", "--upgrade", "--managed-python"]);
|
||||
.args(["sync", "--upgrade", "--managed-python", "--no-config"]);
|
||||
|
||||
// Add python version if .python-version file exists
|
||||
if let Some(version) = &python_version_trimmed {
|
||||
|
@ -321,9 +322,11 @@ fn main_menu_loop(state: &State) -> Result<()> {
|
|||
let menu_choice = get_main_menu_choice(state)?;
|
||||
|
||||
match menu_choice {
|
||||
MainMenuChoice::Quit => std::process::exit(0),
|
||||
MainMenuChoice::KeepExisting => {
|
||||
// Skip sync, just launch existing installation
|
||||
if state.pyproject_modified_by_user {
|
||||
// User has custom edits, sync them
|
||||
handle_version_install_or_update(state, MainMenuChoice::KeepExisting)?;
|
||||
}
|
||||
break;
|
||||
}
|
||||
MainMenuChoice::ToggleBetas => {
|
||||
|
@ -380,14 +383,28 @@ fn write_sync_marker(sync_complete_marker: &std::path::Path) -> Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Get mtime of provided file, or 0 if unavailable
|
||||
fn file_timestamp_secs(path: &std::path::Path) -> i64 {
|
||||
modified_time(path)
|
||||
.map(|t| t.duration_since(UNIX_EPOCH).unwrap_or_default().as_secs() as i64)
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn get_main_menu_choice(state: &State) -> Result<MainMenuChoice> {
|
||||
loop {
|
||||
println!("1) Latest Anki (press Enter)");
|
||||
println!("2) Choose a version");
|
||||
|
||||
if let Some(current_version) = &state.current_version {
|
||||
let normalized_current = normalize_version(current_version);
|
||||
println!("3) Keep existing version ({normalized_current})");
|
||||
|
||||
if state.pyproject_modified_by_user {
|
||||
println!("3) Sync project changes");
|
||||
} else {
|
||||
println!("3) Keep existing version ({normalized_current})");
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(prev_version) = &state.previous_version {
|
||||
if state.current_version.as_ref() != Some(prev_version) {
|
||||
let normalized_prev = normalize_version(prev_version);
|
||||
|
@ -408,7 +425,6 @@ fn get_main_menu_choice(state: &State) -> Result<MainMenuChoice> {
|
|||
);
|
||||
println!();
|
||||
println!("7) Uninstall");
|
||||
println!("8) Quit");
|
||||
print!("> ");
|
||||
let _ = stdout().flush();
|
||||
|
||||
|
@ -448,7 +464,6 @@ fn get_main_menu_choice(state: &State) -> Result<MainMenuChoice> {
|
|||
"5" => MainMenuChoice::ToggleBetas,
|
||||
"6" => MainMenuChoice::ToggleCache,
|
||||
"7" => MainMenuChoice::Uninstall,
|
||||
"8" => MainMenuChoice::Quit,
|
||||
_ => {
|
||||
println!("Invalid input. Please try again.");
|
||||
continue;
|
||||
|
@ -706,9 +721,6 @@ fn update_pyproject_for_version(menu_choice: MainMenuChoice, state: &State) -> R
|
|||
MainMenuChoice::Version(version_kind) => {
|
||||
apply_version_kind(&version_kind, state)?;
|
||||
}
|
||||
MainMenuChoice::Quit => {
|
||||
std::process::exit(0);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ use anyhow::Context;
|
|||
use anyhow::Result;
|
||||
use widestring::u16cstr;
|
||||
use windows::core::PCWSTR;
|
||||
use windows::Wdk::System::SystemServices::RtlGetVersion;
|
||||
use windows::Win32::System::Console::AttachConsole;
|
||||
use windows::Win32::System::Console::GetConsoleWindow;
|
||||
use windows::Win32::System::Console::ATTACH_PARENT_PROCESS;
|
||||
|
@ -18,8 +19,25 @@ use windows::Win32::System::Registry::HKEY;
|
|||
use windows::Win32::System::Registry::HKEY_CURRENT_USER;
|
||||
use windows::Win32::System::Registry::KEY_READ;
|
||||
use windows::Win32::System::Registry::REG_SZ;
|
||||
use windows::Win32::System::SystemInformation::OSVERSIONINFOW;
|
||||
use windows::Win32::UI::Shell::SetCurrentProcessExplicitAppUserModelID;
|
||||
|
||||
/// Returns true if running on Windows 10 (not Windows 11)
|
||||
fn is_windows_10() -> bool {
|
||||
unsafe {
|
||||
let mut info = OSVERSIONINFOW {
|
||||
dwOSVersionInfoSize: std::mem::size_of::<OSVERSIONINFOW>() as u32,
|
||||
..Default::default()
|
||||
};
|
||||
if RtlGetVersion(&mut info).is_ok() {
|
||||
// Windows 10 has build numbers < 22000, Windows 11 >= 22000
|
||||
info.dwBuildNumber < 22000 && info.dwMajorVersion == 10
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ensure_terminal_shown() -> Result<()> {
|
||||
unsafe {
|
||||
if !GetConsoleWindow().is_invalid() {
|
||||
|
@ -29,6 +47,14 @@ pub fn ensure_terminal_shown() -> Result<()> {
|
|||
}
|
||||
|
||||
if std::env::var("ANKI_IMPLICIT_CONSOLE").is_ok() && attach_to_parent_console() {
|
||||
// This black magic triggers Windows to switch to the new
|
||||
// ANSI-supporting console host, which is usually only available
|
||||
// when the app is built with the console subsystem.
|
||||
// Only needed on Windows 10, not Windows 11.
|
||||
if is_windows_10() {
|
||||
let _ = Command::new("cmd").args(["/C", ""]).status();
|
||||
}
|
||||
|
||||
// Successfully attached to parent console
|
||||
reconnect_stdio_to_console();
|
||||
return Ok(());
|
||||
|
|
|
@ -24,7 +24,7 @@ Name "Anki"
|
|||
Unicode true
|
||||
|
||||
; The file to write (relative to nsis directory)
|
||||
OutFile "..\launcher_exe\anki-install.exe"
|
||||
OutFile "..\launcher_exe\anki-launcher-ANKI_VERSION-windows.exe"
|
||||
|
||||
; Non elevated
|
||||
RequestExecutionLevel user
|
||||
|
@ -214,7 +214,7 @@ Section ""
|
|||
|
||||
; Write the uninstall keys for Windows
|
||||
WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\Anki" "DisplayName" "Anki Launcher"
|
||||
WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\Anki" "DisplayVersion" "1.0.0"
|
||||
WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\Anki" "DisplayVersion" "ANKI_VERSION"
|
||||
WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\Anki" "UninstallString" '"$INSTDIR\uninstall.exe"'
|
||||
WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\Anki" "QuietUninstallString" '"$INSTDIR\uninstall.exe" /S'
|
||||
WriteRegDWORD HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\Anki" "NoModify" 1
|
||||
|
|
|
@ -40,8 +40,8 @@ qt67 = [
|
|||
qt = [
|
||||
"pyqt6==6.9.1",
|
||||
"pyqt6-qt6==6.9.1",
|
||||
"pyqt6-webengine==6.9.0",
|
||||
"pyqt6-webengine-qt6==6.9.1",
|
||||
"pyqt6-webengine==6.8.0",
|
||||
"pyqt6-webengine-qt6==6.8.2",
|
||||
"pyqt6_sip==13.10.2",
|
||||
]
|
||||
qt68 = [
|
||||
|
|
|
@ -377,6 +377,7 @@ pub(crate) fn fsrs_item_for_memory_state(
|
|||
Ok(None)
|
||||
}
|
||||
} else {
|
||||
// no revlogs (new card or caused by ignore_revlogs_before or deleted revlogs)
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,11 +10,14 @@ use fsrs::simulate;
|
|||
use fsrs::PostSchedulingFn;
|
||||
use fsrs::ReviewPriorityFn;
|
||||
use fsrs::SimulatorConfig;
|
||||
use fsrs::FSRS;
|
||||
use itertools::Itertools;
|
||||
use rand::rngs::StdRng;
|
||||
use rand::Rng;
|
||||
|
||||
use crate::card::CardQueue;
|
||||
use crate::card::CardType;
|
||||
use crate::card::FsrsMemoryState;
|
||||
use crate::prelude::*;
|
||||
use crate::scheduler::states::fuzz::constrained_fuzz_bounds;
|
||||
use crate::scheduler::states::load_balancer::calculate_easy_days_modifiers;
|
||||
|
@ -129,7 +132,7 @@ impl Collection {
|
|||
fn is_included_card(c: &Card) -> bool {
|
||||
c.queue != CardQueue::Suspended
|
||||
&& c.queue != CardQueue::PreviewRepeat
|
||||
&& c.queue != CardQueue::New
|
||||
&& c.ctype != CardType::New
|
||||
}
|
||||
// calculate any missing memory state
|
||||
for c in &mut cards {
|
||||
|
@ -143,13 +146,29 @@ impl Collection {
|
|||
let days_elapsed = self.timing_today().unwrap().days_elapsed as i32;
|
||||
let new_cards = cards
|
||||
.iter()
|
||||
.filter(|c| c.memory_state.is_none() || c.queue == CardQueue::New)
|
||||
.filter(|c| c.ctype == CardType::New && c.queue != CardQueue::Suspended)
|
||||
.count()
|
||||
+ req.deck_size as usize;
|
||||
let fsrs = FSRS::new(Some(&req.params))?;
|
||||
let mut converted_cards = cards
|
||||
.into_iter()
|
||||
.filter(is_included_card)
|
||||
.filter_map(|c| Card::convert(c, days_elapsed))
|
||||
.filter_map(|c| {
|
||||
let memory_state = match c.memory_state {
|
||||
Some(state) => state,
|
||||
// cards that lack memory states after compute_memory_state have no FSRS items,
|
||||
// implying a truncated or ignored revlog
|
||||
None => fsrs
|
||||
.memory_state_from_sm2(
|
||||
c.ease_factor(),
|
||||
c.interval as f32,
|
||||
req.historical_retention,
|
||||
)
|
||||
.ok()?
|
||||
.into(),
|
||||
};
|
||||
Card::convert(c, days_elapsed, memory_state)
|
||||
})
|
||||
.collect_vec();
|
||||
let introduced_today_count = self
|
||||
.search_cards(&format!("{} introduced:1", &req.search), SortMode::NoOrder)?
|
||||
|
@ -251,39 +270,34 @@ impl Collection {
|
|||
}
|
||||
|
||||
impl Card {
|
||||
fn convert(card: Card, days_elapsed: i32) -> Option<fsrs::Card> {
|
||||
match card.memory_state {
|
||||
Some(state) => match card.queue {
|
||||
CardQueue::DayLearn | CardQueue::Review => {
|
||||
let due = card.original_or_current_due();
|
||||
let relative_due = due - days_elapsed;
|
||||
let last_date = (relative_due - card.interval as i32).min(0) as f32;
|
||||
Some(fsrs::Card {
|
||||
id: card.id.0,
|
||||
difficulty: state.difficulty,
|
||||
stability: state.stability,
|
||||
last_date,
|
||||
due: relative_due as f32,
|
||||
interval: card.interval as f32,
|
||||
lapses: card.lapses,
|
||||
})
|
||||
}
|
||||
CardQueue::New => None,
|
||||
CardQueue::Learn | CardQueue::SchedBuried | CardQueue::UserBuried => {
|
||||
Some(fsrs::Card {
|
||||
id: card.id.0,
|
||||
difficulty: state.difficulty,
|
||||
stability: state.stability,
|
||||
last_date: 0.0,
|
||||
due: 0.0,
|
||||
interval: card.interval as f32,
|
||||
lapses: card.lapses,
|
||||
})
|
||||
}
|
||||
CardQueue::PreviewRepeat => None,
|
||||
CardQueue::Suspended => None,
|
||||
},
|
||||
None => None,
|
||||
fn convert(card: Card, days_elapsed: i32, memory_state: FsrsMemoryState) -> Option<fsrs::Card> {
|
||||
match card.queue {
|
||||
CardQueue::DayLearn | CardQueue::Review => {
|
||||
let due = card.original_or_current_due();
|
||||
let relative_due = due - days_elapsed;
|
||||
let last_date = (relative_due - card.interval as i32).min(0) as f32;
|
||||
Some(fsrs::Card {
|
||||
id: card.id.0,
|
||||
difficulty: memory_state.difficulty,
|
||||
stability: memory_state.stability,
|
||||
last_date,
|
||||
due: relative_due as f32,
|
||||
interval: card.interval as f32,
|
||||
lapses: card.lapses,
|
||||
})
|
||||
}
|
||||
CardQueue::New => None,
|
||||
CardQueue::Learn | CardQueue::SchedBuried | CardQueue::UserBuried => Some(fsrs::Card {
|
||||
id: card.id.0,
|
||||
difficulty: memory_state.difficulty,
|
||||
stability: memory_state.stability,
|
||||
last_date: 0.0,
|
||||
due: 0.0,
|
||||
interval: card.interval as f32,
|
||||
lapses: card.lapses,
|
||||
}),
|
||||
CardQueue::PreviewRepeat => None,
|
||||
CardQueue::Suspended => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,3 +13,20 @@ export function isApplePlatform(): boolean {
|
|||
export function isDesktop(): boolean {
|
||||
return !(/iphone|ipad|ipod|android/i.test(window.navigator.userAgent));
|
||||
}
|
||||
|
||||
export function chromiumVersion(): number | null {
|
||||
const userAgent = window.navigator.userAgent;
|
||||
|
||||
// Check if it's a Chromium-based browser (Chrome, Edge, Opera, etc.)
|
||||
// but exclude Safari which also contains "Chrome" in its user agent
|
||||
if (userAgent.includes("Safari") && !userAgent.includes("Chrome")) {
|
||||
return null; // Safari
|
||||
}
|
||||
|
||||
const chromeMatch = userAgent.match(/Chrome\/(\d+)/);
|
||||
if (chromeMatch) {
|
||||
return parseInt(chromeMatch[1], 10);
|
||||
}
|
||||
|
||||
return null; // Not a Chromium-based browser
|
||||
}
|
||||
|
|
|
@ -85,6 +85,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
}
|
||||
.easy-days-settings input[type="range"] {
|
||||
width: 100%;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.day {
|
||||
|
|
|
@ -95,6 +95,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
newCardsIgnoreReviewLimit: $newCardsIgnoreReviewLimit,
|
||||
easyDaysPercentages: $config.easyDaysPercentages,
|
||||
reviewOrder: $config.reviewOrder,
|
||||
historicalRetention: $config.historicalRetention,
|
||||
});
|
||||
|
||||
const DESIRED_RETENTION_LOW_THRESHOLD = 0.8;
|
||||
|
|
|
@ -4,7 +4,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
-->
|
||||
<script lang="ts">
|
||||
import * as tr from "@generated/ftl";
|
||||
import { isApplePlatform } from "@tslib/platform";
|
||||
import { chromiumVersion, isApplePlatform } from "@tslib/platform";
|
||||
import { getPlatformString } from "@tslib/shortcuts";
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import { get } from "svelte/store";
|
||||
|
@ -22,9 +22,13 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
|
||||
const { focusedInput, fields } = noteEditorContext.get();
|
||||
|
||||
// Workaround for Cmd+Option+Shift+C not working on macOS. The keyup approach works
|
||||
// on Linux as well, but fails on Windows.
|
||||
const event = isApplePlatform() ? "keyup" : "keydown";
|
||||
// Workaround for Cmd+Option+Shift+C not working on macOS on older Chromium
|
||||
// versions.
|
||||
const chromiumVer = chromiumVersion();
|
||||
const event =
|
||||
isApplePlatform() && chromiumVer != null && chromiumVer <= 112
|
||||
? "keyup"
|
||||
: "keydown";
|
||||
|
||||
const clozePattern = /\{\{c(\d+)::/gu;
|
||||
function getCurrentHighestCloze(increment: boolean): number {
|
||||
|
|
|
@ -47,6 +47,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
white-space: nowrap;
|
||||
padding: 15px;
|
||||
border-radius: 5px;
|
||||
font-family: inherit;
|
||||
font-size: 15px;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
|
|
|
@ -7,7 +7,7 @@ import { ModuleName, setupI18n } from "@tslib/i18n";
|
|||
import { optimumPixelSizeForCanvas } from "./canvas-scale";
|
||||
import { Shape } from "./shapes";
|
||||
import { Ellipse, extractShapesFromRenderedClozes, Polygon, Rectangle, Text } from "./shapes";
|
||||
import { TEXT_BACKGROUND_COLOR, TEXT_FONT_FAMILY, TEXT_PADDING } from "./tools/lib";
|
||||
import { SHAPE_MASK_COLOR, TEXT_BACKGROUND_COLOR, TEXT_FONT_FAMILY, TEXT_PADDING } from "./tools/lib";
|
||||
import type { Size } from "./types";
|
||||
|
||||
export type DrawShapesData = {
|
||||
|
@ -217,7 +217,7 @@ function drawShapes(
|
|||
context,
|
||||
size,
|
||||
shape,
|
||||
fill: shape.fill ?? properties.inActiveShapeColor,
|
||||
fill: shape.fill !== SHAPE_MASK_COLOR ? shape.fill : properties.inActiveShapeColor,
|
||||
stroke: properties.inActiveBorder.color,
|
||||
strokeWidth: properties.inActiveBorder.width,
|
||||
});
|
||||
|
@ -437,7 +437,7 @@ function getShapeProperties(): ShapeProperties {
|
|||
activeShapeColor: activeShapeColor ? activeShapeColor : "#ff8e8e",
|
||||
inActiveShapeColor: inActiveShapeColor
|
||||
? inActiveShapeColor
|
||||
: "#ffeba2",
|
||||
: SHAPE_MASK_COLOR,
|
||||
highlightShapeColor: highlightShapeColor
|
||||
? highlightShapeColor
|
||||
: "#ff8e8e00",
|
||||
|
|
48
uv.lock
48
uv.lock
|
@ -66,7 +66,7 @@ requires-dist = [
|
|||
{ name = "distro", marker = "sys_platform != 'darwin' and sys_platform != 'win32'" },
|
||||
{ name = "markdown" },
|
||||
{ name = "orjson" },
|
||||
{ name = "protobuf", specifier = ">=4.21" },
|
||||
{ name = "protobuf", specifier = ">=6.0,<8.0" },
|
||||
{ name = "requests", extras = ["socks"] },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
|
@ -170,8 +170,8 @@ dependencies = [
|
|||
{ name = "pyqt6", version = "6.9.1", source = { registry = "https://pypi.org/simple" }, marker = "extra == 'extra-3-aqt-qt' or (extra == 'extra-3-aqt-qt66' and extra == 'extra-3-aqt-qt67') or (extra == 'extra-3-aqt-qt66' and extra == 'extra-3-aqt-qt68') or (extra == 'extra-3-aqt-qt67' and extra == 'extra-3-aqt-qt68') or (extra != 'extra-3-aqt-qt66' and extra != 'extra-3-aqt-qt67' and extra != 'extra-3-aqt-qt68')" },
|
||||
{ name = "pyqt6-webengine", version = "6.6.0", source = { registry = "https://pypi.org/simple" }, marker = "extra == 'extra-3-aqt-qt66' or (extra == 'extra-3-aqt-qt' and extra == 'extra-3-aqt-qt67') or (extra == 'extra-3-aqt-qt' and extra == 'extra-3-aqt-qt68') or (extra == 'extra-3-aqt-qt67' and extra == 'extra-3-aqt-qt68')" },
|
||||
{ name = "pyqt6-webengine", version = "6.7.0", source = { registry = "https://pypi.org/simple" }, marker = "extra == 'extra-3-aqt-qt67' or (extra == 'extra-3-aqt-qt' and extra == 'extra-3-aqt-qt66') or (extra == 'extra-3-aqt-qt66' and extra == 'extra-3-aqt-qt68') or (extra == 'extra-3-aqt-qt' and extra != 'extra-3-aqt-qt67' and extra == 'extra-3-aqt-qt68')" },
|
||||
{ name = "pyqt6-webengine", version = "6.8.0", source = { registry = "https://pypi.org/simple" }, marker = "(extra == 'extra-3-aqt-qt' and extra == 'extra-3-aqt-qt66') or (extra == 'extra-3-aqt-qt' and extra == 'extra-3-aqt-qt67') or (extra == 'extra-3-aqt-qt66' and extra == 'extra-3-aqt-qt67') or (extra != 'extra-3-aqt-qt67' and extra == 'extra-3-aqt-qt68') or (extra != 'extra-3-aqt-qt' and extra != 'extra-3-aqt-qt66' and extra == 'extra-3-aqt-qt68')" },
|
||||
{ name = "pyqt6-webengine", version = "6.9.0", source = { registry = "https://pypi.org/simple" }, marker = "extra == 'extra-3-aqt-qt' or (extra == 'extra-3-aqt-qt66' and extra == 'extra-3-aqt-qt67') or (extra == 'extra-3-aqt-qt66' and extra == 'extra-3-aqt-qt68') or (extra == 'extra-3-aqt-qt67' and extra == 'extra-3-aqt-qt68') or (extra != 'extra-3-aqt-qt66' and extra != 'extra-3-aqt-qt67' and extra != 'extra-3-aqt-qt68')" },
|
||||
{ name = "pyqt6-webengine", version = "6.8.0", source = { registry = "https://pypi.org/simple" }, marker = "extra == 'extra-3-aqt-qt' or extra == 'extra-3-aqt-qt68' or (extra == 'extra-3-aqt-qt66' and extra == 'extra-3-aqt-qt67')" },
|
||||
{ name = "pyqt6-webengine", version = "6.9.0", source = { registry = "https://pypi.org/simple" }, marker = "(extra == 'extra-3-aqt-qt' and extra == 'extra-3-aqt-qt66') or (extra == 'extra-3-aqt-qt' and extra == 'extra-3-aqt-qt67') or (extra == 'extra-3-aqt-qt' and extra == 'extra-3-aqt-qt68') or (extra == 'extra-3-aqt-qt66' and extra == 'extra-3-aqt-qt67') or (extra == 'extra-3-aqt-qt66' and extra == 'extra-3-aqt-qt68') or (extra == 'extra-3-aqt-qt67' and extra == 'extra-3-aqt-qt68') or (extra != 'extra-3-aqt-qt' and extra != 'extra-3-aqt-qt66' and extra != 'extra-3-aqt-qt67' and extra != 'extra-3-aqt-qt68')" },
|
||||
{ name = "pywin32", marker = "sys_platform == 'win32' or (extra == 'extra-3-aqt-qt' and extra == 'extra-3-aqt-qt66') or (extra == 'extra-3-aqt-qt' and extra == 'extra-3-aqt-qt67') or (extra == 'extra-3-aqt-qt' and extra == 'extra-3-aqt-qt68') or (extra == 'extra-3-aqt-qt66' and extra == 'extra-3-aqt-qt67') or (extra == 'extra-3-aqt-qt66' and extra == 'extra-3-aqt-qt68') or (extra == 'extra-3-aqt-qt67' and extra == 'extra-3-aqt-qt68')" },
|
||||
{ name = "requests" },
|
||||
{ name = "send2trash" },
|
||||
|
@ -186,8 +186,8 @@ qt = [
|
|||
{ name = "pyqt6", version = "6.9.1", source = { registry = "https://pypi.org/simple" } },
|
||||
{ name = "pyqt6-qt6", version = "6.9.1", source = { registry = "https://pypi.org/simple" } },
|
||||
{ name = "pyqt6-sip", version = "13.10.2", source = { registry = "https://pypi.org/simple" } },
|
||||
{ name = "pyqt6-webengine", version = "6.9.0", source = { registry = "https://pypi.org/simple" } },
|
||||
{ name = "pyqt6-webengine-qt6", version = "6.9.1", source = { registry = "https://pypi.org/simple" } },
|
||||
{ name = "pyqt6-webengine", version = "6.8.0", source = { registry = "https://pypi.org/simple" } },
|
||||
{ name = "pyqt6-webengine-qt6", version = "6.8.2", source = { registry = "https://pypi.org/simple" } },
|
||||
]
|
||||
qt66 = [
|
||||
{ name = "pyqt6", version = "6.6.1", source = { registry = "https://pypi.org/simple" } },
|
||||
|
@ -234,11 +234,11 @@ requires-dist = [
|
|||
{ name = "pyqt6-sip", marker = "extra == 'qt67'", specifier = "==13.10.2" },
|
||||
{ name = "pyqt6-sip", marker = "extra == 'qt68'", specifier = "==13.10.2" },
|
||||
{ name = "pyqt6-webengine", specifier = ">=6.2" },
|
||||
{ name = "pyqt6-webengine", marker = "extra == 'qt'", specifier = "==6.9.0" },
|
||||
{ name = "pyqt6-webengine", marker = "extra == 'qt'", specifier = "==6.8.0" },
|
||||
{ name = "pyqt6-webengine", marker = "extra == 'qt66'", specifier = "==6.6.0" },
|
||||
{ name = "pyqt6-webengine", marker = "extra == 'qt67'", specifier = "==6.7.0" },
|
||||
{ name = "pyqt6-webengine", marker = "extra == 'qt68'", specifier = "==6.8.0" },
|
||||
{ name = "pyqt6-webengine-qt6", marker = "extra == 'qt'", specifier = "==6.9.1" },
|
||||
{ name = "pyqt6-webengine-qt6", marker = "extra == 'qt'", specifier = "==6.8.2" },
|
||||
{ name = "pyqt6-webengine-qt6", marker = "extra == 'qt66'", specifier = "==6.6.2" },
|
||||
{ name = "pyqt6-webengine-qt6", marker = "extra == 'qt67'", specifier = "==6.7.3" },
|
||||
{ name = "pyqt6-webengine-qt6", marker = "extra == 'qt68'", specifier = "==6.8.1" },
|
||||
|
@ -552,7 +552,7 @@ name = "importlib-metadata"
|
|||
version = "8.7.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "zipp" },
|
||||
{ name = "zipp", marker = "python_full_version < '3.10' or (extra == 'extra-3-aqt-qt' and extra == 'extra-3-aqt-qt66') or (extra == 'extra-3-aqt-qt' and extra == 'extra-3-aqt-qt67') or (extra == 'extra-3-aqt-qt' and extra == 'extra-3-aqt-qt68') or (extra == 'extra-3-aqt-qt66' and extra == 'extra-3-aqt-qt67') or (extra == 'extra-3-aqt-qt66' and extra == 'extra-3-aqt-qt68') or (extra == 'extra-3-aqt-qt67' and extra == 'extra-3-aqt-qt68')" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/76/66/650a33bd90f786193e4de4b3ad86ea60b53c89b669a5c7be931fac31cdb0/importlib_metadata-8.7.0.tar.gz", hash = "sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000", size = 56641, upload-time = "2025-04-27T15:29:01.736Z" }
|
||||
wheels = [
|
||||
|
@ -1034,8 +1034,8 @@ resolution-markers = [
|
|||
"python_full_version < '3.10'",
|
||||
]
|
||||
dependencies = [
|
||||
{ name = "pyqt6-qt6", version = "6.9.1", source = { registry = "https://pypi.org/simple" } },
|
||||
{ name = "pyqt6-sip", version = "13.10.2", source = { registry = "https://pypi.org/simple" } },
|
||||
{ name = "pyqt6-qt6", version = "6.9.1", source = { registry = "https://pypi.org/simple" }, marker = "extra == 'extra-3-aqt-qt' or (extra == 'extra-3-aqt-qt66' and extra == 'extra-3-aqt-qt67') or (extra == 'extra-3-aqt-qt66' and extra == 'extra-3-aqt-qt68') or (extra == 'extra-3-aqt-qt67' and extra == 'extra-3-aqt-qt68') or (extra != 'extra-3-aqt-qt66' and extra != 'extra-3-aqt-qt67' and extra != 'extra-3-aqt-qt68')" },
|
||||
{ name = "pyqt6-sip", version = "13.10.2", source = { registry = "https://pypi.org/simple" }, marker = "extra == 'extra-3-aqt-qt' or (extra == 'extra-3-aqt-qt66' and extra == 'extra-3-aqt-qt67') or (extra == 'extra-3-aqt-qt66' and extra == 'extra-3-aqt-qt68') or (extra == 'extra-3-aqt-qt67' and extra == 'extra-3-aqt-qt68') or (extra != 'extra-3-aqt-qt66' and extra != 'extra-3-aqt-qt67' and extra != 'extra-3-aqt-qt68')" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/32/1b/567f46eb43ca961efd38d7a0b73efb70d7342854f075fd919179fdb2a571/pyqt6-6.9.1.tar.gz", hash = "sha256:50642be03fb40f1c2111a09a1f5a0f79813e039c15e78267e6faaf8a96c1c3a6", size = 1067230, upload-time = "2025-06-06T08:49:30.307Z" }
|
||||
wheels = [
|
||||
|
@ -1246,8 +1246,10 @@ resolution-markers = [
|
|||
]
|
||||
dependencies = [
|
||||
{ name = "pyqt6", version = "6.8.0", source = { registry = "https://pypi.org/simple" }, marker = "(extra == 'extra-3-aqt-qt' and extra == 'extra-3-aqt-qt66') or (extra == 'extra-3-aqt-qt' and extra == 'extra-3-aqt-qt67') or (extra == 'extra-3-aqt-qt66' and extra == 'extra-3-aqt-qt67') or (extra != 'extra-3-aqt-qt67' and extra == 'extra-3-aqt-qt68') or (extra != 'extra-3-aqt-qt' and extra != 'extra-3-aqt-qt66' and extra == 'extra-3-aqt-qt68')" },
|
||||
{ name = "pyqt6-sip", version = "13.10.2", source = { registry = "https://pypi.org/simple" }, marker = "(extra == 'extra-3-aqt-qt' and extra == 'extra-3-aqt-qt66') or (extra == 'extra-3-aqt-qt' and extra == 'extra-3-aqt-qt67') or (extra == 'extra-3-aqt-qt66' and extra == 'extra-3-aqt-qt67') or (extra != 'extra-3-aqt-qt67' and extra == 'extra-3-aqt-qt68') or (extra != 'extra-3-aqt-qt' and extra != 'extra-3-aqt-qt66' and extra == 'extra-3-aqt-qt68')" },
|
||||
{ name = "pyqt6", version = "6.9.1", source = { registry = "https://pypi.org/simple" }, marker = "extra == 'extra-3-aqt-qt' or (extra == 'extra-3-aqt-qt66' and extra == 'extra-3-aqt-qt67') or (extra == 'extra-3-aqt-qt66' and extra == 'extra-3-aqt-qt68') or (extra == 'extra-3-aqt-qt67' and extra == 'extra-3-aqt-qt68')" },
|
||||
{ name = "pyqt6-sip", version = "13.10.2", source = { registry = "https://pypi.org/simple" }, marker = "extra == 'extra-3-aqt-qt' or extra == 'extra-3-aqt-qt68' or (extra == 'extra-3-aqt-qt66' and extra == 'extra-3-aqt-qt67')" },
|
||||
{ name = "pyqt6-webengine-qt6", version = "6.8.1", source = { registry = "https://pypi.org/simple" }, marker = "(extra == 'extra-3-aqt-qt' and extra == 'extra-3-aqt-qt66') or (extra == 'extra-3-aqt-qt' and extra == 'extra-3-aqt-qt67') or (extra == 'extra-3-aqt-qt66' and extra == 'extra-3-aqt-qt67') or (extra != 'extra-3-aqt-qt67' and extra == 'extra-3-aqt-qt68') or (extra != 'extra-3-aqt-qt' and extra != 'extra-3-aqt-qt66' and extra == 'extra-3-aqt-qt68')" },
|
||||
{ name = "pyqt6-webengine-qt6", version = "6.8.2", source = { registry = "https://pypi.org/simple" }, marker = "extra == 'extra-3-aqt-qt' or (extra == 'extra-3-aqt-qt66' and extra == 'extra-3-aqt-qt67') or (extra == 'extra-3-aqt-qt66' and extra == 'extra-3-aqt-qt68') or (extra == 'extra-3-aqt-qt67' and extra == 'extra-3-aqt-qt68')" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/cd/c8/cadaa950eaf97f29e48c435e274ea5a81c051e745a3e2f5d9d994b7a6cda/PyQt6_WebEngine-6.8.0.tar.gz", hash = "sha256:64045ea622b6a41882c2b18f55ae9714b8660acff06a54e910eb72822c2f3ff2", size = 34203, upload-time = "2024-12-12T15:34:35.573Z" }
|
||||
wheels = [
|
||||
|
@ -1269,9 +1271,9 @@ resolution-markers = [
|
|||
"python_full_version < '3.10'",
|
||||
]
|
||||
dependencies = [
|
||||
{ name = "pyqt6", version = "6.9.1", source = { registry = "https://pypi.org/simple" } },
|
||||
{ name = "pyqt6-sip", version = "13.10.2", source = { registry = "https://pypi.org/simple" } },
|
||||
{ name = "pyqt6-webengine-qt6", version = "6.9.1", source = { registry = "https://pypi.org/simple" } },
|
||||
{ name = "pyqt6", version = "6.9.1", source = { registry = "https://pypi.org/simple" }, marker = "(extra == 'extra-3-aqt-qt' and extra == 'extra-3-aqt-qt66') or (extra == 'extra-3-aqt-qt' and extra == 'extra-3-aqt-qt67') or (extra == 'extra-3-aqt-qt' and extra == 'extra-3-aqt-qt68') or (extra == 'extra-3-aqt-qt66' and extra == 'extra-3-aqt-qt67') or (extra == 'extra-3-aqt-qt66' and extra == 'extra-3-aqt-qt68') or (extra == 'extra-3-aqt-qt67' and extra == 'extra-3-aqt-qt68') or (extra != 'extra-3-aqt-qt' and extra != 'extra-3-aqt-qt66' and extra != 'extra-3-aqt-qt67' and extra != 'extra-3-aqt-qt68')" },
|
||||
{ name = "pyqt6-sip", version = "13.10.2", source = { registry = "https://pypi.org/simple" }, marker = "(extra == 'extra-3-aqt-qt' and extra == 'extra-3-aqt-qt66') or (extra == 'extra-3-aqt-qt' and extra == 'extra-3-aqt-qt67') or (extra == 'extra-3-aqt-qt' and extra == 'extra-3-aqt-qt68') or (extra == 'extra-3-aqt-qt66' and extra == 'extra-3-aqt-qt67') or (extra == 'extra-3-aqt-qt66' and extra == 'extra-3-aqt-qt68') or (extra == 'extra-3-aqt-qt67' and extra == 'extra-3-aqt-qt68') or (extra != 'extra-3-aqt-qt' and extra != 'extra-3-aqt-qt66' and extra != 'extra-3-aqt-qt67' and extra != 'extra-3-aqt-qt68')" },
|
||||
{ name = "pyqt6-webengine-qt6", version = "6.9.1", source = { registry = "https://pypi.org/simple" }, marker = "(extra == 'extra-3-aqt-qt' and extra == 'extra-3-aqt-qt66') or (extra == 'extra-3-aqt-qt' and extra == 'extra-3-aqt-qt67') or (extra == 'extra-3-aqt-qt' and extra == 'extra-3-aqt-qt68') or (extra == 'extra-3-aqt-qt66' and extra == 'extra-3-aqt-qt67') or (extra == 'extra-3-aqt-qt66' and extra == 'extra-3-aqt-qt68') or (extra == 'extra-3-aqt-qt67' and extra == 'extra-3-aqt-qt68') or (extra != 'extra-3-aqt-qt' and extra != 'extra-3-aqt-qt66' and extra != 'extra-3-aqt-qt67' and extra != 'extra-3-aqt-qt68')" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/8f/1a/9971af004a7e859347702f816fb71ecd67c3e32b2f0ae8daf1c1ded99f62/pyqt6_webengine-6.9.0.tar.gz", hash = "sha256:6ae537e3bbda06b8e06535e4852297e0bc3b00543c47929541fcc9b11981aa25", size = 34616, upload-time = "2025-04-08T08:57:35.402Z" }
|
||||
wheels = [
|
||||
|
@ -1338,6 +1340,24 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/b0/b5/a641ebe3e5113bee23d911c58fdd2e65061a6e3786a26b068468b988e5d2/PyQt6_WebEngine_Qt6-6.8.1-py3-none-win_amd64.whl", hash = "sha256:0ced2a10433da2571cfa29ed882698e0e164184d54068d17ba73799c45af5f0f", size = 95657750, upload-time = "2024-12-06T13:47:43.048Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyqt6-webengine-qt6"
|
||||
version = "6.8.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
resolution-markers = [
|
||||
"python_full_version >= '3.12'",
|
||||
"python_full_version == '3.11.*'",
|
||||
"python_full_version == '3.10.*'",
|
||||
"python_full_version < '3.10'",
|
||||
]
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/5a/da/639523b821d68a253f7fb2a8a4f2b277f5a03e9adba5a9cfcc2aa1aa9ed1/PyQt6_WebEngine_Qt6-6.8.2-py3-none-macosx_10_14_x86_64.whl", hash = "sha256:84312705615b5fccedb386531bbd505eb110469444d778f09acd6a214836789e", size = 113127300, upload-time = "2025-02-06T12:05:55.965Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/df/bd/33b89cc7cdf54d172be3f98746273b4b6fba73b4802a2e5a6fa757951b47/PyQt6_WebEngine_Qt6-6.8.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:672363b3809973bbe3408048fc49e98f5c54db8629e855d813fd531e05929007", size = 101984083, upload-time = "2025-02-06T12:06:09.736Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/91/90/2693e9de1f064ac7cc10ba25548bbab6ce45a163eef07a22db3ff5ce8b81/PyQt6_WebEngine_Qt6-6.8.2-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:c3be75ef7563b965306de53cae0b357438672d3bf7d9b39edacc307fbeb9965e", size = 105210886, upload-time = "2025-02-06T12:06:23.944Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/42/8a/f30075726c8ac391b6fbbc7ab043795ec79f56e452e9d835b883576738b2/PyQt6_WebEngine_Qt6-6.8.2-py3-none-manylinux_2_39_aarch64.whl", hash = "sha256:72c1b4c45a3226f32f6c821ee474c4418727913536a62506d9787e24a46d6f27", size = 101194628, upload-time = "2025-02-06T12:06:37.196Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f3/2a/4fe2bfd3a1ed0e27d1b8f32a5259ebe966432365391c9a541f290f5438de/PyQt6_WebEngine_Qt6-6.8.2-py3-none-win_amd64.whl", hash = "sha256:4421159f3ac4a796499b7f73e98028797a4ae636b04f920b8165308ca0b8c629", size = 95573175, upload-time = "2025-02-06T12:06:49.642Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyqt6-webengine-qt6"
|
||||
version = "6.9.1"
|
||||
|
|
Loading…
Reference in a new issue