* Fix sync login with custom server URL
Call update_network() before showing login dialog to ensure
the custom sync server URL is saved before attempting login.
Previously, the custom URL was only saved when closing the
preferences dialog, causing login to fail on first attempt.
Fixes#4395
* Add contributor info
Prevent adding a note that has already been added by checking if
note.id is set before attempting to add it. This fixes a race
condition when the Add button is clicked multiple times quickly.
Fixes#4376
* qt: normalize large progress totals to kilobytes to avoid QProgressBar overflow (#4341); docs: update CONTRIBUTORS
* update
* revert: keep ProgressManager generic; no autoscaling in progress.py
* revert: keep ProgressManager generic; do scaling only in full sync
* Simplify sync progress update by scaling to KB, fix CONTRIBUTORS
* add anki_i18n and locale_config crates to launcher
* add launcher.ftl
* add tr to state
* replace most hardcoded strings with translations
* add support for `launcher` rustcfg to trim translations
* use marker structs to denote type of translations
* move underscores into generated code
* Update cargo-license, which may fix the license order issue (dae)
* add GetCustomColours rpc method
* save colours as rgb instead of argb
* show saved custom colours as possible options in colour picker
this is primarily for mobile clients, as qt currently ignores this
* save custom colours on colour picker change (for desktop)
* add SaveCustomColours rpc method
* restore custom colour palette on editor init
* save custom colour palette on colour picker open and input
there doesn't seem to be an event fired when the picker is
cancelled/closed, so it's still possible for work to be lost
* save colours on `change` instead of `input`
`input` is supposed to be fired on every adjustment to the picker
whereas `change` is only fired when the picker is accepted, but qt
seems to treat both as the latter, so this is currently a no-op
* Store colors in the collection
One minor tweak to the logic while I was there: an invalid color no
longer invalidates all the rest.
---------
Co-authored-by: Damien Elmes <gpg@ankiweb.net>
* Improved margins in Card Browser's "Preview" pane
* Alternate approach that looks good on Mac too
---------
Co-authored-by: Damien Elmes <gpg@ankiweb.net>
Co-authored-by: Damien Elmes <dae@users.noreply.github.com>
* Make timebox message translatable with flexible variable order
Currently, the timebox dialog message is built from two separate strings,
each containing one variable:
"{ $count } cards studied in" + "{ $count } minutes."
As a result, translators cannot freely reorder the variables in their translations.
This change introduces a single string with both variables, allowing translators
to adjust the order for more natural expressions in their languages.
* Preserve old string for now
* Ensure message doesn't display over two lines
---------
Co-authored-by: Damien Elmes <gpg@ankiweb.net>
* Replace media-record.png with SVG icon
- Added SVG version of the microphone icon (from Font Awesome Free v7.0.0)
- Updated sound.py to use QIcon for proper SVG support
- Icon now displays at 60x60 pixels
* Remove obsolete media-record.png icon
The PNG icon has been replaced with an SVG version
* Update CONTRIBUTORS
* Replace icon with AnkiMobile's record icon
CC-BY requires attribution, and we don't currently have a way to describe
those attributions in a way the mobile clients can also include automatically.
For now, I've switched it to an icon I authored myself to avoid the issue.
---------
Co-authored-by: Damien Elmes <gpg@ankiweb.net>
Co-authored-by: Damien Elmes <dae@users.noreply.github.com>
* fix show_exception's messagebox always formatting as plaintext
* Revert "fix show_exception's messagebox always formatting as plaintext"
This reverts commit aec6dd9be8.
* convert SearchError msg to markdown when in browser
While testing the previous PR, I noticed that if stdout is set to None,
the same behaviour is shown as in the following report:
https://forums.ankiweb.net/t/cannot-switch-versions/64565
This leads me to wonder whether IsTerminal is behaving differently
on that user's system, and the use of an env var may be more reliable.
* backend part
* split memorised and cost
* slapdash frontend
* extract some simulator logic
* Add zoomed version of graph
* ./check
* Fix: Tooltip
* Fix: Simulator/workload transition
* remove "time"
* Update ts/routes/graphs/simulator.ts
Co-authored-by: user1823 <92206575+user1823@users.noreply.github.com>
* Added: Mode toggle
* Disable Dr in workload mode
* keep button order consistant between modes
* dont clear points on mode swap
* add review count graph
* Revert "dont clear points on mode swap"
This reverts commit fc89efb1d9.
* "Help me pick" button
* unrelated title case change
* Add translation strings
* fix: missing translation string
* Fix: Layout shift
* Add: Experimental
* Fix Time / Memorized
* per day values
* set review limit to 9999 on open
* keep default at currently set value
* Do DR calculation in parallel (dae)
Approx 5x faster on my machine
---------
Co-authored-by: user1823 <92206575+user1823@users.noreply.github.com>
Co-authored-by: Damien Elmes <gpg@ankiweb.net>
- The pyproject copy change broke the initial run case
- Avoid calling 'uv pip show' before venv created, as it causes
a pop-up prompting the user to install developer tools on macOS (#4227)
- Add a tip to the user about the unwanted install_name_tool pop-up
(Also tracked in #4227)
- Print 'Checking for updates...' prior to the potentially slow network
request
The 'latest' and 'choose version' paths always read from the the
dist file these days, so the file doesn't need to be copied in advance.
The other reason for the current behaviour was so that when the user
manually installs a new launcher, it opens into the launcher on next
run, as that's probably what the user wanted. But this causes problems
when the launcher is updated automatically by things like homebrew.
https://forums.ankiweb.net/t/anki-homebrew-issues-terminal-and-crash-on-exit/64413/4
- include stdout/stderr when utf8_output() fails
- don't swallow the error returned by handle_Version_install_or_update
- skip codesigning when NODMG set
Closes#4224
This changes 'keep existing version' to 'sync project changes'
when changes to the pyproject.toml file have been detected that
are newer than the installer's version.
Also adds a way to temporarily enable the launcher, as we needed some
other trigger than the pyproject.toml file anyway, and this approach
also solves #4165.
And removes the 'quit' option, since it's an uncommon operation, and
the user can just close the window instead.
Short-term caveat: users with older launchers/addon will trigger the old
pyproject.toml mtime bump, leading to a 'sync project changes' message
that will not make much sense to a typical user.
* set UV_PYTHON_DOWNLOADS=auto when doing `uv sync`
* Clear env vars prior to invoking uv, and add --no-config
---------
Co-authored-by: Damien Elmes <gpg@ankiweb.net>
It had some downsides:
- the lockfile was discarded when switching between beta/non-beta
- it could result in beta versions of transitory dependencies
By adding 'anki' and 'aqt' as first-party packages with explicit
version numbers (validated by the version list we get from PyPi),
we can allow them to be installed without breaking other deps.
https://forums.ankiweb.net/t/bundling-numpy-in-an-add-on/62669/15
The inclusion of files under qt/aqt is handled by qt/pyproject.toml,
not qt/hatch_build.py, which works with the files under out/qt/_aqt.
Excluding qt/aqt/data and all files ending in .ui in qt/pyproject.toml
fixes the issue, since the other unwanted files (**/*.scss, **/*.ts, and
**/tsconfig*) all reside under qt/aqt/data.
Fixes: 04996c77f3
We can't show an AnkiWeb page for a locally-installed add-on, and
having a custom config action is the only way we can easily expose
this for older clients as well.
Re: #4158
* Launcher: Run `uv python install` before running `uv sync`
* Less copy/paste.
* Minor readability improvements
* Make sure we check file presence before attempting to read
---------
Co-authored-by: Damien Elmes <gpg@ankiweb.net>
* Feat/Add legacy evaluate config bool
* Minor tweaks based on PR suggestions (dae)
New enabling command:
from anki.config import Config
mw.col.set_config_bool(Config.Bool.FSRS_LEGACY_EVALUATE, True)
While something we probably don't want to encourage much of, this
may enable some previously-unshared add-ons.
https://forums.ankiweb.net/t/bundling-numpy-in-an-add-on/62669/5
The 'uv add' command is transaction, so if an add-on tries to inject
incompatible dependencies into the environment, the venv will be
left as-is. And each Anki upgrade/downgrade resets the requirements,
so the requested packages shouldn't cause errors down the line.
Sample add-on:
import subprocess
from aqt import mw
from aqt.operations import QueryOp
from aqt.qt import QAction
from aqt.utils import showInfo
def ensure_spacy(col):
print("trying to import spacy")
try:
import spacy
print("successful import")
return
except Exception as e:
print("error importing:", e)
print("attempting add")
try:
from aqt.package import add_python_requirements as add
except Exception as e:
raise Exception(f"package unavailable, can't install: {e}")
# be explicit about version, or Anki beta users will get
# a beta wheel that may break
(success, output) = add(["spacy==3.8.7", "https://github.com/explosion/spacy-models/releases/download/ko_core_news_sm-3.8.0/ko_core_news_sm-3.8.0-py3-none-any.whl"])
if not success:
raise Exception(f"adding failed: {output}")
print("success")
# alterantively:
# from aqt.package import venv_binary
# subprocess.run([venv_binary("spacy"), "download", "ko_core_news_sm"], check=True)
# print("model added")
# large packages will freeze for a while on first import on macOS
import spacy
print("spacy import successful")
def activate_spacy():
def on_success(res):
mw.progress.single_shot(1000, lambda: showInfo("Spacy installed"))
QueryOp(parent=mw, op=ensure_spacy, success=on_success).with_progress("Installing spacy...").run_in_background()
action = QAction("Activate Spacy", mw)
action.triggered.connect(activate_spacy)
mw.form.menuTools.addAction(action)