Commit Graph

2211 Commits

Author SHA1 Message Date
Henrik Giesel
280f78535b Replace jQuery in webview.py 2021-04-13 21:32:53 +02:00
Henrik Giesel
3e1b42bb62 Satisfy formatter 2021-04-13 20:29:59 +02:00
Henrik Giesel
67754494d2 Remvoe jQuery from deckbrowser.py
It's not quite clear why jQuery was necessary here?
2021-04-13 20:26:06 +02:00
Henrik Giesel
7dd12a523d Remove jquery from reviewer.py
- we no longer actually need to focus the default ease button
2021-04-13 20:11:18 +02:00
Henrik Giesel
bdf1e6324e Remove jQuery from toolbar.py 2021-04-13 19:59:00 +02:00
Henrik Giesel
64c7d405f5 Remove jquery from browser.py 2021-04-13 19:54:45 +02:00
Henrik Giesel
191e3cf69b Satisfy formatter 2021-04-13 19:47:03 +02:00
Henrik Giesel
a76f1d64ad Move compile_sass.bzl to /ts + Remove core.css from data/web (unused?) 2021-04-13 19:47:03 +02:00
Henrik Giesel
ffd159f9a0 Declare dependencies in data/web/css 2021-04-13 19:47:03 +02:00
Henrik Giesel
8f0f8f9af8 Use graphs-base and congrats-base 2021-04-13 19:47:03 +02:00
Henrik Giesel
fee6ee7319 Do not include webview.css for editor 2021-04-13 19:47:03 +02:00
Henrik Giesel
825509ba0c Move card_counts and split off fusion_vars 2021-04-13 19:47:03 +02:00
Henrik Giesel
df7c597a8b Add option not include default css in stdHtml 2021-04-13 19:47:03 +02:00
Henrik Giesel
86262030e3 Purge jQuery from reviewer-bottom 2021-04-13 19:41:04 +02:00
Henrik Giesel
ae3f8adbb3 Remove overview.ts 2021-04-13 19:38:57 +02:00
Damien Elmes
18f03d5cb1
Merge pull request #1119 from hgiesel/reviewerdropjquery
Remove last jQuery from reviewer.ts / Remove fadeTime
2021-04-13 23:12:13 +10:00
Henrik Giesel
be7630648d Add setInnerHTML as a Replacement for $.html 2021-04-13 13:32:50 +02:00
RumovZ
594a98eff1 Move previewer.py into browser folder 2021-04-13 11:38:35 +02:00
RumovZ
e43f11339f Declare all legacy pathnames in browser init 2021-04-13 11:30:32 +02:00
RumovZ
88086596b6 Move find_and_replace.py into browser/dialogs 2021-04-13 11:21:36 +02:00
RumovZ
7ee40e3dce Refactor sidebar.py into browser folder 2021-04-13 11:05:49 +02:00
Damien Elmes
e520e8df02 add missing copyright headers to *.ts 2021-04-13 18:57:08 +10:00
Damien Elmes
948fc5f777 add missing copyright headers to *.py 2021-04-13 18:45:35 +10:00
Henrik Giesel
c9fcf955f4 Remove display:none from mark and flag, and preset hidden attribute instead 2021-04-13 01:00:09 +02:00
Henrik Giesel
74a9d0b0a1 Make imageLoaded adhere to its function signature 2021-04-13 00:42:09 +02:00
Henrik Giesel
f4f0298378 Remove jQuery from mark and flag in reviewer.ts 2021-04-13 00:37:58 +02:00
Henrik Giesel
e131b70c4c Remove jQuery from _updateQA 2021-04-13 00:22:00 +02:00
RumovZ
2000c80fd2 Refactor browser and table into folders 2021-04-12 09:50:44 +02:00
Damien Elmes
12ce632605 handle missing default sort column in notes view
Switching to notes mode was throwing:

  File "/Users/dae/Work/code/dtop/bazel-copy/runanki.runfiles/net_ankiweb_anki/qt/aqt/browser.py", line 449, in on_table_state_changed
    self.table.toggle_state(checked, self._lastSearchTxt)
  File "/Users/dae/Work/code/dtop/bazel-copy/runanki.runfiles/net_ankiweb_anki/qt/aqt/table.py", line 206, in toggle_state
    self._state = self._model.toggle_state(
  File "/Users/dae/Work/code/dtop/bazel-copy/runanki.runfiles/net_ankiweb_anki/qt/aqt/table.py", line 996, in toggle_state
    self.search(context)
  File "/Users/dae/Work/code/dtop/bazel-copy/runanki.runfiles/net_ankiweb_anki/qt/aqt/table.py", line 1005, in search
    context.order = self.columns[self._state.sort_column]
2021-04-12 16:02:51 +10:00
RumovZ
d4159fef19 Save separate browser mode headers 2021-04-11 12:28:11 +02:00
RumovZ
fadec3dc5b Merge branch 'master' into backend-columns 2021-04-11 11:18:15 +02:00
RumovZ
db32179a25 Add browser_did_fetch_columns hook and some doc 2021-04-11 10:27:43 +02:00
RumovZ
eafa2afc0d Resolve config sort in table model 2021-04-10 11:33:59 +02:00
RumovZ
48b70873cb Rename column label fields 2021-04-10 09:14:20 +02:00
RumovZ
c74078ea9e Unify state columns
* Remove duplicate backend columns
* Remove duplicate column routines
* Move columns on frontend from state to model
* Generate available columns from Colum enum
* Add second column label for notes mode
2021-04-08 23:48:24 +02:00
RumovZ
8a131da9a2 Add enum for column sorting 2021-04-08 11:40:24 +02:00
RumovZ
d8a0aa922c Add enum for column alignment 2021-04-08 11:28:29 +02:00
RumovZ
f78401619a Remove Column class and use pb class instead 2021-04-08 11:17:25 +02:00
Damien Elmes
1ee4385fcf i18n tts error message 2021-04-08 09:35:19 +10:00
Damien Elmes
28f830730e fix '\\' being converted to single backslash
Appears the default changed in 3.x
https://github.com/mathjax/MathJax/issues/2532

https://forums.ankiweb.net/t/double-backslashes-in-text-of-notes-are-turned-into-single-backslashes-on-display/9048
2021-04-07 21:50:33 +10:00
RumovZ
6abb05d074 Save key for unsupported add-on columns 2021-04-06 23:02:58 +02:00
RumovZ
a5c02910a6 Use backend column objects on frontend 2021-04-06 19:47:03 +02:00
RumovZ
08226e2004 Change notes mode shortcut 2021-04-06 16:48:35 +02:00
Damien Elmes
6e954e82a5 current deck change is now undoable
- make sure we set flag in changes when config var changed
- move current deck get/set into backend
- set_config() now returns a bool indicating whether a change was
made, so other operations can be gated off it
- active decks generation is deferred until sched.reset()
2021-04-06 21:52:06 +10:00
RumovZ
4903a325d4 Fix get_item_from_card_id() 2021-04-06 12:06:08 +02:00
RumovZ
a1277b0919 Tweak new browsing strings 2021-04-06 11:41:18 +02:00
Damien Elmes
5676ad5101 update find&replace, and remove perform_op() 2021-04-06 17:07:38 +10:00
Damien Elmes
84fe309583 update scheduling ops
- migrate to CollectionOp()
- return actual change count when suspending/burying
- add helper to convert vec to vec of newtype
2021-04-06 16:38:42 +10:00
Damien Elmes
2de8cc1a94 update note ops
remove_note() now returns the count of removed cards, allowing us
to unify the tooltip between browser and review screen

I've left the old translation in - we'll need to write a script at
one point that gathers all references to translations in the code,
and shows ones that are unused.
2021-04-06 14:56:36 +10:00
Damien Elmes
bc78b6ef17 migrate more ops to CollectionOp 2021-04-06 14:36:13 +10:00
Damien Elmes
27c032a158 it seems mypy ignores the generic class bound if it's a type alias 2021-04-06 14:14:55 +10:00
Damien Elmes
b8fc195cdf start migrating perform_op() into builder in separate file
By passing back the builder to the calling code to run, we don't need
to plumb extra arguments like success= and handler= through each
operation, and the ability to override the default tooltip behaviour
comes free on all operations
2021-04-06 12:47:55 +10:00
Damien Elmes
1ece868d02 shift keep-current-selection logic into sidebar's refresh()
By calling refresh() manually after performing an op, we were refreshing
twice, and the selection was being lost when changes were made outside
of the sidebar.

Also drop the after_hooks arg to perform_op(), since nothing is using
it now.
2021-04-06 11:18:13 +10:00
Damien Elmes
3f62f54f14 more perform_op() tweaks
- pass the handler directly
- reviewer special-cases for flags and notes are now applied at
call site
- drop the kind attribute on OpChanges which is not needed
2021-04-06 10:14:11 +10:00
Damien Elmes
9c8148ff0d fix error when opening browser during review while notes mode active 2021-04-05 21:57:06 +10:00
Damien Elmes
758550ea17 fix row cache issues
I think this may have accidentally been changed in the refactoring.
If we discard self._rows, it will result in the entire table flashing
"..." until the new data is available. Instead, we leave the cached
rows alone, and just update the cutoff point, so we can serve stale
content (avoiding any visible redraw) until the new data is available.

I've updated search() to reset the rows there, so we free up memory
on a new search.
2021-04-05 14:42:04 +10:00
Damien Elmes
a18bb2af12 add booleans for various screens to OpChanges
The backend knows exactly which op has executed, and it saves us having
to re-implement this logic on each client.

Fixes the browser table refreshing when toggling decks.
2021-04-05 14:28:56 +10:00
Damien Elmes
f6ec5928ae allow ops to pass metadata into perform_op()
Instances can pass handled_by=self to more easily ignore events they
initiate.

Fixes ugly refresh when expanding/collapsing decks, but we're still
refreshing the card/notes area unnecessarily in that case.
2021-04-05 13:43:09 +10:00
Damien Elmes
996d9f9bbc undo support for tag collapse; expand->collapse for consistency w/ decks 2021-04-05 11:47:12 +10:00
Damien Elmes
2168dfe63d add routine to set deck collapse state
Updating a deck via protobuf is now exposed on the backend, but not
currently on the frontend - I suspect we'll be better off writing
separate routines for the actions we need instead, and we get a better
undo description for free.

This is currently causing an ugly redraw in the browse screen, which
will need fixing.
2021-04-05 11:19:04 +10:00
Damien Elmes
c60b88cd2f expose read-only access to new deck objects 2021-04-04 20:39:56 +10:00
Damien Elmes
12a3d9ec87 ensure page is shown when setHtml() called
https://forums.ankiweb.net/t/looked-into-the-bug-when-pressing-e-and-r-together-in-filtered-deck/8951
2021-04-03 16:59:23 +10:00
Damien Elmes
3a6f2a993e move operations into submodule 2021-04-03 16:26:10 +10:00
Damien Elmes
41c5a25dc8 simplify errors
- use a flat enum instead of oneof messages, most of which were empty
- tidy up the Python side
2021-04-03 16:06:46 +10:00
Damien Elmes
f666f15b63 use perform_op() for undo()
Instead of manually updating the UI after undoing, we just rely
on the same change notification infrastructure regular operations
use.
2021-04-03 14:38:49 +10:00
Damien Elmes
e73359510d move filtered deck labels to backend
- use strum to generate an iterator for the protobuf enum so we don't
forget to add new labels if extending in the future
- no add-ons appear to be using dynOrderLabels(), so it has been removed

@RumovZ perhaps a similar approach might work for listing the available
browser columns as well?
2021-04-01 23:53:38 +10:00
Damien Elmes
8449bbe469
Merge pull request #1108 from RumovZ/more-columns
Even more browser fixes and features
2021-04-01 15:59:06 +10:00
Damien Elmes
dd17da7d5c
Merge pull request #1106 from abdnh/fix-deprwarn
Fix deprecated implicit conversion from float to int using __int__()
2021-04-01 15:14:49 +10:00
Damien Elmes
9c1dc2b62e tweak the wording of some of the tag ops 2021-04-01 15:12:13 +10:00
RumovZ
52b66dc985 Add shortcut and tooltip to switch 2021-03-31 18:53:36 +02:00
RumovZ
fd4b5dc695 Delay switch animation
If the switch triggers heavy GUI action, like a lot of paint()s, the
concurrently running switch animation may look choppy.
A small timer makes these events execute first and the animation will
run smoothly afterwards when the event queue has been drained.
2021-03-31 12:13:09 +02:00
RumovZ
c8146c13c1 Make toggle actions checkable 2021-03-31 10:05:44 +02:00
RumovZ
e5d68df527 Change switch knob colour 2021-03-31 09:16:25 +02:00
RumovZ
8779fb5ede Add note interval column 2021-03-30 23:44:35 +02:00
abdo
d7aca8845f Fix deprecated implicit conversion from float to int using __int__() 2021-03-31 00:32:20 +03:00
RumovZ
d7da1c1578 Fix previewer not changing card 2021-03-30 22:06:58 +02:00
RumovZ
1ad91a5312 Add note due column 2021-03-30 21:39:15 +02:00
RumovZ
ffe77b1291 Add browser column enum for backend 2021-03-30 11:59:52 +02:00
Damien Elmes
49e4dba0d1
Merge pull request #1103 from abdnh/win-openfolder
Fix ResourceWarning in openFolder() on Windows
2021-03-30 19:28:12 +10:00
Damien Elmes
325920aa23
Merge pull request #1102 from RumovZ/more-browser
More browser fixes and features
2021-03-30 19:27:55 +10:00
RumovZ
4933b922f7 Add note lapses column 2021-03-29 16:06:15 +02:00
RumovZ
32e538d0db Add note reps column 2021-03-29 15:52:02 +02:00
RumovZ
18e33f24d3 Make note state equate to False as on backend 2021-03-29 15:51:34 +02:00
RumovZ
ad7ac06398 Add switch for browser states 2021-03-29 12:24:24 +02:00
RumovZ
a3e3e56f2b Implement custom qt button Switch 2021-03-29 12:19:48 +02:00
RumovZ
2c7940e247 Fix browser_did_fetch_row hook 2021-03-29 12:16:50 +02:00
RumovZ
13a0e2c82f Remove fixme for card_ids legacy support 2021-03-29 12:07:24 +02:00
RumovZ
c0950eca30 Explain use of deprecated decorator 2021-03-29 12:04:14 +02:00
RumovZ
b1a06fb807 Flag deleted rows as inactive 2021-03-29 11:55:28 +02:00
RumovZ
fb86320038 Fix state toggling when current is deleted 2021-03-29 11:54:35 +02:00
Damien Elmes
bcb1a2a329 fix 'lame missing' message not being shown
https://forums.ankiweb.net/t/cant-use-voice-recorder/8753
2021-03-29 19:52:30 +10:00
RumovZ
d6bac20c58 Fix issues with scrolling row into view
1) Check whether full row height is in viewport instead of just the
top left corner.
2) Add timer before scrolling to current row so editor will already
be set up.
2021-03-29 11:40:18 +02:00
RumovZ
f3915b914a Stop abusing qt accel string 2021-03-29 11:18:10 +02:00
Damien Elmes
ecb6cfdb4b
Merge pull request #1095 from hgiesel/svgloader2
Use svg-loader to load svgs directly into ts packages
2021-03-29 17:56:48 +10:00
Damien Elmes
bb1a2cb78b fix an unused import 2021-03-29 17:26:20 +10:00
Damien Elmes
8e66595c63 fix default note column headers 2021-03-29 17:22:39 +10:00
Damien Elmes
3383f1742a rename BrowserCardState
Use a more verbose name, and use 'note' rather than 'card', so we
can rely on the default of False
2021-03-29 17:12:45 +10:00
Damien Elmes
0269a4a8f4 Item -> ItemId 2021-03-29 16:52:41 +10:00
Damien Elmes
28aae21d51 List->Sequence in a bunch of table/browser methods
Most code doesn't require a list specifically, and build a list
is an extra step.
2021-03-29 16:48:33 +10:00
RumovZ
0d8b1c9d0b squash merge browser refactor
Closes #1100
2021-03-29 16:14:54 +10:00
abdo
47e80da1f8 Fix ResourceWarning in openFolder() on Windows
subprocess.Popen emits ResourceWarning in the destructor if the status of the process was not read.
Fix by using subprocess.run() instead, which takes care of that.
Using run() is also recommended for simple cases like this in the docs.
2021-03-29 09:09:20 +03:00
Henrik Giesel
bbea9ac9d7 Remove bootstrap-icons from css folder 2021-03-28 16:14:52 +02:00
Damien Elmes
73430d966f fix 'strip formatting' option being inverted 2021-03-28 19:41:15 +10:00
Damien Elmes
cfac40febc switch NoteType to Notetype
When used as a variable, we were typically calling it a 'notetype', not
a 'note type'.
2021-03-27 22:03:19 +10:00
Damien Elmes
716b474314 add Dict suffix to Dict aliases in models.py 2021-03-27 21:46:49 +10:00
Damien Elmes
9f4a06abee ID -> Id in protobuf and Python
follow-up to dc81a7fed0
2021-03-27 21:38:20 +10:00
Damien Elmes
d6b9cc4d9b drop the legacy enum from rslib, and pass separate module/message idx 2021-03-27 11:56:31 +10:00
Damien Elmes
b57e9be46f allow js to request specific i18n modules
Brings the payload on the congrats page with a non-English language
down from about 150k to 15k
2021-03-26 21:43:36 +10:00
Damien Elmes
3d366d5264 add types to some more Fluent variables 2021-03-26 16:52:54 +10:00
Damien Elmes
8cc6758eb1 declare variables with some common names as int instead of a union 2021-03-26 16:33:53 +10:00
Damien Elmes
5a094e78fa enable type checking of aqt/forms, and fix the new typing issues
Referencing an invalid translation should now break the build
2021-03-26 16:06:02 +10:00
Damien Elmes
3079eaa460 symlink forms/*.py in for the benefit of editor completions 2021-03-26 15:33:30 +10:00
Damien Elmes
dbcb997ad7 update TR references in .ui files
We're not benefiting from type checking yet, as mypy and pylint currently
ignore the generated files
2021-03-26 15:03:51 +10:00
Damien Elmes
785db39a56 update remaining TR references in .py files 2021-03-26 14:49:55 +10:00
Damien Elmes
e687552aeb update TR references that crossed multiple lines 2021-03-26 14:38:15 +10:00
Damien Elmes
b7587cb8d2 update TR references that contain arguments 2021-03-26 14:21:04 +10:00
Damien Elmes
0c338bfd53 update no-arg tr references in qt/ 2021-03-26 13:48:26 +10:00
Damien Elmes
efb1ce46d4 switch the Importers global to a callable for i18n
I18n is not set up at init time, so the strings can't be generated
at import.

@kelciour you have a few importing add-ons, so wanted to give you a
heads-up. The importing code is likely to change more in
future months, but for now this should be the only change
2021-03-26 13:28:21 +10:00
Damien Elmes
48354931da update some no-arg TR constants 2021-03-26 12:37:18 +10:00
Damien Elmes
07c6c4044c Merge branch 'int_type' into main 2021-03-26 11:38:34 +10:00
Damien Elmes
bc2c3a57ba fix incorrect constant naming 2021-03-26 11:29:07 +10:00
Damien Elmes
64bb526008 fix incorrect camelCase 2021-03-26 11:28:51 +10:00
Damien Elmes
a16940a246 fix broken string 2021-03-26 11:27:22 +10:00
Arthur Milchior
7ea862931c NF: NoteTypeID type 2021-03-26 11:14:08 +10:00
Arthur Milchior
6ac1e6477e NF: DeckID type 2021-03-26 11:14:08 +10:00
Arthur Milchior
b8f715ffea NF: default_deck_id as a constant
Otherwise it's not clear what this 1 represents
2021-03-26 11:14:08 +10:00
Arthur Milchior
3b6802530d NF: currentDeckID factorize odid or did 2021-03-26 11:14:08 +10:00
Arthur Milchior
986efeed19 NF: CardID type 2021-03-26 11:14:08 +10:00
Arthur Milchior
6ac540927a NF: NoteID type 2021-03-26 11:14:08 +10:00
Damien Elmes
e34412a4b2
Merge pull request #1090 from glutanimate/filtered-deck-dialog-hooks
Add hooks to the filtered deck dialog
2021-03-26 10:28:23 +10:00
Damien Elmes
9aece2a7b8 rework translation handling
Instead of generating a fluent.proto file with a giant enum, create
a .json file representing the translations that downstream consumers
can use for code generation.

This enables the generation of a separate method for each translation,
with a docstring that shows the actual text, and any required arguments
listed in the function signature.

The codebase is still using the old enum for now; updating it will need
to come in future commits, and the old enum will need to be kept
around, as add-ons are referencing it.

Other changes:

- move translation code into a separate crate
- store the translations on a per-file/module basis, which will allow
us to avoid sending 1000+ strings on each JS page load in the future
- drop the undocumented support for external .ftl files, that we weren't
using
- duplicate strings in translation files are now checked for at build
time
- fix i18n test failing when run outside Bazel
- drop slog dependency in i18n module
2021-03-26 09:41:32 +10:00
Glutanimate
6a1c85d62e Run isort 2021-03-25 04:46:22 +01:00
Glutanimate
feaa32b751 Supply deck ID in hook, so that add-ons may update deck config dict
(for newly created filtered decks where the deck ID is not yet set
in the FilteredDeckForUpdate object)
2021-03-25 02:33:15 +01:00
Glutanimate
e9b18c14f2 Fix (?) operation output type hint 2021-03-25 02:28:58 +01:00
Glutanimate
713c8f619d Add three filtered deck dialog hooks
Similar to deckconf hooks
2021-03-25 02:28:01 +01:00
Damien Elmes
4b5944f181 move markdown dep into pylib 2021-03-24 22:19:14 +10:00
Damien Elmes
d382b33585 rework filtered deck screen & search errors
- Filtered deck creation now happens as an atomic operation, and is
undoable.
- The logic for initial search text, normalizing searches and so on
has been pushed into the backend.
- Use protobuf to pass the filtered deck to the updated dialog, so
we don't need to deal with untyped JSON.
- Change the "revise your search?" prompt to be a simple info box -
user has access to cancel and build buttons, and doesn't need a separate
prompt. Tweak the wording so the 'show excluded' button should be more
obvious.
- Filtered decks have a time appended to them instead of a number,
primarily because it's easier to implement. No objections going back to
the old behaviour if someone wants to contribute a clean patch.
The standard de-duplication will happen if two decks are created in the
same minute with the same name.
- Tweak the default sort order, and start with two searches. The UI
will still hide the second search by default, but by starting with two,
the frontend doesn't need logic for creating the starting text.
- Search errors now have their own error type, instead of using
InvalidInput, as that was intended mainly for bad API calls. The markdown
conversion is done when the error is converted from the backend, allowing
errors to printed as a string without any special handling by the calling
code.

TODO: when building a new filtered deck, update_active() is clobbering
the undo log when the overview is refreshed
2021-03-24 22:04:35 +10:00
Damien Elmes
181cda1979 rename&simplify the deck/config type aliases
- QueueConfig is only used by the scheduler
- DeckConfig was being used in places that Config should have been used
- Add "Dict" to the name so that the bare name is free for use with a
stronger type.
2021-03-24 16:29:02 +10:00
Damien Elmes
5a1b00b6e6 dyndeckconf -> filtered_deck 2021-03-24 13:17:12 +10:00
Damien Elmes
12597e1094 support undo of filtered deck build/empty 2021-03-24 12:56:06 +10:00
RumovZ
76b4b7f6b1 Add context action 'Update with Current Search' 2021-03-23 11:20:46 +01:00
Damien Elmes
5d67265658 tweak AbortAddonImport docstring 2021-03-23 19:31:24 +10:00
Damien Elmes
2d48479f4f
Merge pull request #1086 from glutanimate/abort-addon-import-exception
Add an exception that allows add-ons to abort early
2021-03-23 19:28:13 +10:00
Damien Elmes
3199dcb54d add hook to modify browser row content 2021-03-23 19:13:52 +10:00
Damien Elmes
9c456dd7b0 only declare rows deleted if they're the result of a NotFound error
If it's some other error like the DB suddenly becoming accessible,
we don't want to scare the user into thinking their data was deleted,
and we want to know what the error was without popping up tens of
message boxes for each row.
2021-03-23 19:04:15 +10:00
Damien Elmes
5fd79d9246
Merge pull request #1082 from RumovZ/backend-rows
Backend rows
2021-03-23 18:31:42 +10:00
Glutanimate
fa6a0a1283 Add an exception that allows add-ons to abort early 2021-03-22 14:53:04 +01:00
Damien Elmes
84b0c8ba88 switch DeckID to a NewType
Not sure at this point whether this will buy us much in the Python
codebase over a simple int alias, but let's give it a go.
2021-03-22 23:43:54 +10:00
Damien Elmes
01161c8ed2 use perform_op() for deck creation 2021-03-22 23:17:07 +10:00
Damien Elmes
0123a382ec deck rename with perform_op() 2021-03-22 20:38:51 +10:00