-
Notifications
You must be signed in to change notification settings - Fork 12
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
allow use of multiple editors and fix initial button state #30
Conversation
While this is an improvement over the original state of things, the state of the toggle is currently shared across all open editors rather than being toggleable on a per-editor basis. Also, if the button is toggled in one editor, it will not be toggled in the other editors. I'm not sure of a good mechanism to clean up editor references when they are closed out so I wanted to avoid doing that for now. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was trying to fix #27 yesterday so I happen to be familiar with this code.
The onFocusLost
changes seem good. I'm fairly certain getting rid of self.editor
is correct as previously EditManager
was leaking an Editor
reference.
I am not sure about the button state changes.
if not self.buttonOn: | ||
return False | ||
return changed |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are you certain changed
is meant to be returned through?
Seems like it's fine, although it appears false
is always passed here? (There is no API as far as I can tell...)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah changed
should be returned here. The value is intended to be propagated through the hooks. My guess is that this is so hooks later on can detect if a hook before them modified the note already.
It is in the built code so I don't have a great way of linking to it but you can see this here:
class _EditorDidUnfocusFieldFilter:
_hooks: list[Callable[["bool", "anki.notes.Note", "int"], bool]] = []
def append(
self, callback: Callable[["bool", "anki.notes.Note", "int"], bool]
) -> None:
"""(changed: bool, note: anki.notes.Note, current_field_idx: int)"""
self._hooks.append(callback)
def remove(
self, callback: Callable[["bool", "anki.notes.Note", "int"], bool]
) -> None:
if callback in self._hooks:
self._hooks.remove(callback)
def count(self) -> int:
return len(self._hooks)
def __call__(
self, changed: bool, note: anki.notes.Note, current_field_idx: int
) -> bool:
for filter in self._hooks:
try:
changed = filter(changed, note, current_field_idx)
except:
# if the hook fails, remove it
self._hooks.remove(filter)
raise
# legacy support
changed = anki.hooks.runFilter(
"editFocusLost", changed, note, current_field_idx
)
return changed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mdyan I cleaned up the code a little, I think this version is a little more structured, what are your thoughts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The changes to onFocusLost look good to me.
For posterity I'd like to point out that these change the focus behavior, but I think this is fine.
chinese/edit.py
Outdated
if note_type := note.note_type(): | ||
allFields = mw.col.models.field_names(note_type) | ||
field = allFields[index] | ||
we_changed |= update_fields(note, field, allFields) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If changed
is supposed to be returned, I believe this can be all simplified to
changed = update_fields(note, field, allFields)
and then we_changed
can be deleted
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The code is a little awkward here, I am open to suggestions and while we would be able to |=
with changed
, I would like to avoid reassigning function parameters.
If we wanted to get rid of we_changed
we could instead return early, something like what I have below. I actually like this pattern a little more than what I currently have.
if note_type := note.note_type():
allFields = mw.col.models.field_names(note_type)
field = allFields[index]
if not update_fields(note, field, allFields):
return changed
return True
I'm not too sure about the race condition you mentioned, but I would guess one way to fix it might be to add a |
Thank you for this! |
@mdyan The |
@Gustaf-C good luck with the midterms 😤. I too am not a fan of trying to smooth over the race condition with the delay, I don't really think of this so much as a solution as instead a reliable alleviation. I took another look this time with less tired eyes and I think we can use the work done with the Edit: not ideal is that this hook is new in 23.10 so anyone with older clients will have issues |
aa8fcc9
to
f850743
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
if not self.buttonOn: | ||
return False | ||
return changed |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The changes to onFocusLost look good to me.
For posterity I'd like to point out that these change the focus behavior, but I think this is fine.
chinese/edit.py
Outdated
def on_editor_state_did_change( | ||
self, editor: aqt.editor.Editor, new_state: aqt.editor.EditorState, old_state: aqt.editor.EditorState | ||
): | ||
# if the editor just loaded, then we need to set the toggle status of the addon button | ||
if old_state is aqt.editor.EditorState.INITIAL: | ||
self.updateButton(editor) | ||
|
||
def on_load_note(self, editor: aqt.editor.Editor): | ||
# if the editor is still in the initial state then the `NoteEditor` component has not mounted to the DOM yet | ||
# meaning that `toggleEditorButton` has not yet been injected and so we can't update the ui button | ||
# in this case, we rely on the `editor_state_did_change` to let us know when the editor is ready | ||
if editor.state is aqt.editor.EditorState.INITIAL: | ||
return | ||
self.updateButton(editor) | ||
|
||
def updateButton(self, editor: aqt.editor.Editor): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I really don't understand how Svelte works but from my naive glance at NoteEditor.svelte this solution seems correct as EditorState is updated and broadcast in the onMount callback.
Since you reported loadNote
was being called before the UI is mounted to the DOM (I'm a bit fuzzy on what this actually means though), this code should fix the race condition.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for taking a look. As for the DOM business, in that piece of code you pointed out, since it is in the onMount
, it runs when the component has been "mounted" aka rendered for the first time. This is problematic for us since even though the browser might be set up, if the component hasn't been rendered yet then the toggle function wouldn't be available for us to use (since it is injected in the onMount
). Hopefully this clears things up.
I just saw your comment about EditorState only being available in 23.10. What if we use EDIT: On second thought, I don't think that will work, as the editor does not necessarily get focus. |
Good idea but yeah, I think for example the editor in the browse menu doesn't automatically focus the first field. If it was really important to maintain backwards compatibility, we could add a check for the Anki version and only register the hook / check for the editor state if the version was high enough along with adding the delay-based approach from earlier to smooth over the race condition to handle things. I'm not sure what the userbase looks like of the new fork though, considering it was created in general to be compatible with 23.10, I have few qualms about leaving the older versions behind. |
Yes I forked this so people could run it with 23.10, and the fact that the fixes so far seem to have been backwards compatible is just a bonus. I'll leave up the old version with backwards compatible changes but restrict it to those running <= 2.1.66, and once this fix has been integrated it will be released as a new version restricted to >= 23.10. Making lots of code just in case someone is using an old version is not something I feel like maintaining. |
415a0f2
to
23dd6f1
Compare
self.updateButton(editor) | ||
|
||
def on_editor_state_did_change( | ||
self, editor: aqt.editor.Editor, new_state: aqt.editor.EditorState, old_state: aqt.editor.EditorState |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think new_state is unused?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah it is not used, do you have a linter complaining? If not I would tend towards not _
ing it in the parameter list.
Looks good! Not sure why those imports fail the tests though... |
23dd6f1
to
1a9f898
Compare
|
Once we merge #35 and get these rebased, the tests should pass. |
Previously when a note was modified, the plugin would manually reload the note in the editor. This commit modifies things to take advantage of the built in hook infrastructure to auto-reload the note. As a bonus, this fixes a bug caused assuming only one editor would be open at a time and closes Gustaf-C#27.
The `loadNote` / `editor_did_load_note` hooks run as soon as a note is loaded, but not necesarrily after the ui component has been mounted to the DOM. We add a check to ensure that the `NoteEditor` component has completed mounting before trying to change the button state.
1a9f898
to
fd66bc3
Compare
I'm not completely sure why that worked, but at least it did. |
Hmm, I am also not sure, that is going to cause issues with linters though since they cannot explore that far to find a module. I'll mess around with it in test PR. |
This PR fixes two things currently broken with the editor integration:
update editor focus lost hook to report when note was changed
Previously when a note was modified, the plugin would manually reload the note in the editor. This commit modifies things to take advantage of the built in hook infrastructure to auto-reload the note.
As a bonus, this fixes a bug caused assuming only one editor would be open at a time and closes #27.
ensure button status is correctly set when editor opens
The
loadNote
/editor_did_load_note
hooks run as soon as a note is loaded, but not necesarrily after the ui component has been mounted to the DOM. By adding a delay of 50 ms before setting button state, this should help alleviate the race condition.