Skip to content
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

[BUILD-982] feat: Update sidebar immediately when config options change #1078

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 57 additions & 26 deletions ankihub/gui/reviewer.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Modifies Anki's reviewer UI (aqt.reviewer)."""

import json
import uuid
from enum import Enum
from textwrap import dedent
Expand Down Expand Up @@ -28,6 +29,7 @@
from ..gui.webview import AuthenticationRequestInterceptor, CustomWebPage # noqa: F401
from ..main.utils import Resource, mh_tag_to_resource
from ..settings import config, url_login
from .config_dialog import get_config_dialog_manager
from .js_message_handling import VIEW_NOTE_PYCMD, parse_js_message_kwargs
from .utils import get_ah_did_of_deck_or_ancestor_deck, using_qt5
from .web.templates import (
Expand Down Expand Up @@ -336,10 +338,28 @@ def setup():
reviewer_did_show_question.append(_remove_anking_button)
reviewer_did_show_answer.append(_remove_anking_button)

_setup_sidebar_update_on_config_close()

webview_did_receive_js_message.append(_on_js_message)
reviewer_will_end.append(_close_sidebar_and_clear_states_if_exists)


def _setup_sidebar_update_on_config_close() -> None:
"""Sets up the update of the reviewer buttons and resource tabs when the config dialog is closed."""
from .ankiaddonconfig import ConfigWindow

def setup_config_close_callback(window: ConfigWindow) -> None:
window.execute_on_close(notify_elements)

def notify_elements() -> None:
card = aqt.mw.reviewer.card
if card:
_notify_reviewer_buttons_of_card_change(card)
_notify_resource_tabs_of_card_change(card)

get_config_dialog_manager().on_window_open(setup_config_close_callback)


def _add_or_refresh_view_note_button(card: Card) -> None:
"""Adds the "View on AnkiHub" button to the reviewer toolbar if it doesn't exist yet,
or refreshes it if it does exist already."""
Expand Down Expand Up @@ -405,10 +425,7 @@ def _inject_ankihub_features_and_setup_sidebar(
if not isinstance(context, Reviewer):
return

reviewer_button_js = get_reviewer_buttons_js(
theme=_ankihub_theme(),
enabled_buttons=_get_enabled_buttons_list(),
)
reviewer_button_js = get_reviewer_buttons_js(theme=_ankihub_theme())
web_content.body += f"<script>{reviewer_button_js}</script>"

global reviewer_sidebar
Expand All @@ -418,24 +435,6 @@ def _inject_ankihub_features_and_setup_sidebar(
reviewer_sidebar.set_on_auth_failure_hook(_handle_auth_failure)


def _get_enabled_buttons_list() -> List[str]:
result = []

feature_flags = config.get_feature_flags()

if feature_flags.get("chatbot"):
if config.public_config.get("ankihub_ai_chatbot"):
result.append("chatbot")

if feature_flags.get("mh_integration"):
if _get_enabled_steps_for_resource_type(ResourceType.BOARDS_AND_BEYOND):
result.append("b&b")
if _get_enabled_steps_for_resource_type(ResourceType.FIRST_AID):
result.append("fa4")

return result


def _related_ah_deck_has_note_embeddings(note: Note) -> bool:
ah_did_of_note = ankihub_db.ankihub_did_for_anki_nid(note.id)
ah_dids_of_note_type = ankihub_db.ankihub_dids_for_note_type(note.mid)
Expand Down Expand Up @@ -517,21 +516,53 @@ def _notify_reviewer_buttons_of_card_change(card: Card) -> None:
bb_count = len(_get_resources(note.tags, ResourceType.BOARDS_AND_BEYOND))
fa_count = len(_get_resources(note.tags, ResourceType.FIRST_AID))

is_anking_deck = _is_anking_deck(aqt.mw.reviewer.card)
show_chatbot = _related_ah_deck_has_note_embeddings(card.note())
visible_buttons = _get_enabled_buttons() & _get_relevant_buttons_for_card(card)

js = _wrap_with_reviewer_buttons_check(
f"""
ankihubReviewerButtons.updateButtons(
{bb_count},
{fa_count},
{'true' if show_chatbot else 'false'},
{'true' if is_anking_deck else 'false'},
{json.dumps(list(visible_buttons))}
);
"""
)
aqt.mw.reviewer.web.eval(js)


def _get_enabled_buttons() -> Set[str]:
result = set()
feature_flags = config.get_feature_flags()

if feature_flags.get("chatbot") and config.public_config.get("ankihub_ai_chatbot"):
result.add(SidebarPageType.CHATBOT.value)

if feature_flags.get("mh_integration"):
if _get_enabled_steps_for_resource_type(ResourceType.BOARDS_AND_BEYOND):
result.add(SidebarPageType.BOARDS_AND_BEYOND.value)
if _get_enabled_steps_for_resource_type(ResourceType.FIRST_AID):
result.add(SidebarPageType.FIRST_AID.value)

return result


def _get_relevant_buttons_for_card(card: Card) -> Set[str]:
result = set()

show_chatbot = _related_ah_deck_has_note_embeddings(card.note())
if show_chatbot:
result.add(SidebarPageType.CHATBOT.value)

show_mh_buttons = _is_anking_deck(aqt.mw.reviewer.card)
if show_mh_buttons:
result |= {
SidebarPageType.BOARDS_AND_BEYOND.value,
SidebarPageType.FIRST_AID.value,
}

return result


def _show_resources_for_current_card(resource_type: ResourceType) -> None:
tags = aqt.mw.reviewer.card.note().tags
resources = _get_resources(tags, resource_type)
Expand Down
34 changes: 15 additions & 19 deletions ankihub/gui/web/reviewer_buttons.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,8 @@ class AnkiHubReviewerButtons {
constructor() {
this.theme = "{{ THEME }}";
this.isPremiumOrTrialing = "{{ IS_PREMIUM_OR_TRIALING }}" == "True";
this.isAnKingDeck = null;
this.bbCount = 0;
this.faCount = 0;
this.enabledButtons = "{{ ENABLED_BUTTONS }}".split(",");

this.colorButtonLight = "#F9FAFB";
this.colorButtonSelectedLight = "#C7D2FE";
Expand All @@ -25,26 +23,27 @@ class AnkiHubReviewerButtons {
iconPath: "/_b&b_icon.svg",
iconPathDarkTheme: "/_b&b_icon_dark_theme.svg",
active: false,
visible: true,
tooltip: "Boards & Beyond",
},
{
name: "fa4",
iconPath: "/_fa4_icon.svg",
iconPathDarkTheme: "/_fa4_icon_dark_theme.svg",
active: false,
visible: true,
tooltip: "First Aid Forward",
},
{
name: "chatbot",
iconPath: this.isPremiumOrTrialing ? "/_chatbot_icon.svg" : "/_chatbot_icon_sleeping.svg",
iconPathDarkTheme: this.isPremiumOrTrialing ? null : "/_chatbot_icon_sleeping_dark_theme.svg",
active: false,
visible: true,
tooltip: "AI Chatbot"
},
]

this.buttonsData = this.buttonsData.filter(buttonData => this.enabledButtons.includes(buttonData.name));

this.setupButtons();
}

Expand Down Expand Up @@ -290,47 +289,44 @@ class AnkiHubReviewerButtons {
document.head.appendChild(style);
}

updateButtons(bbCount, faCount, showChatbot, isAnKingDeck) {
updateButtons(bbCount, faCount, visibleButtons) {
this.bbCount = bbCount;
this.faCount = faCount;
this.showChatbot = showChatbot;
this.isAnKingDeck = isAnKingDeck;

const visibleButtons = this.getVisibleButtons();
for (const buttonData of this.buttonsData) {
buttonData.visible = visibleButtons.includes(buttonData.name)
}

const visibleButtonElements = this.getVisibleButtons();

// Hide invisible buttons
this.buttonsData.forEach(buttonData => {
if (!visibleButtons.includes(buttonData)) {
if (!visibleButtonElements.includes(buttonData)) {
this.udpateButtonVisibility(buttonData.name, false);
}
});

// Update style of visible buttons
visibleButtons.forEach((buttonData, idx) => {
visibleButtonElements.forEach((buttonData, idx) => {
this.udpateButtonVisibility(buttonData.name, true);
this.updateButtonStyle(
buttonData.name,
idx === 0,
idx === visibleButtons.length - 1
idx === visibleButtonElements.length - 1
);
});

this.updateResourceCountIndicators(visibleButtons);
this.updateResourceCountIndicators(visibleButtonElements);

if (visibleButtons.length === 0) {
if (visibleButtonElements.length === 0) {
this.elementsContainer.style.visibility = "hidden";
} else {
this.elementsContainer.style.visibility = "visible";
}
}

getVisibleButtons() {
return this.buttonsData.filter(
buttonData => (
(buttonData.name === "chatbot" && this.showChatbot) ||
(buttonData.name !== "chatbot" && this.isAnKingDeck)
)
);
return this.buttonsData.filter(buttonData => (buttonData.visible));
}

udpateButtonVisibility(buttonName, isVisible) {
Expand Down
4 changes: 1 addition & 3 deletions ankihub/gui/web/templates.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import pathlib
from typing import List

from jinja2 import Environment, FileSystemLoader, select_autoescape

Expand All @@ -25,12 +24,11 @@ def get_header_webview_html(
)


def get_reviewer_buttons_js(theme: str, enabled_buttons: List[str]) -> str:
def get_reviewer_buttons_js(theme: str) -> str:
client = AnkiHubClient()
return env.get_template("reviewer_buttons.js").render(
{
"THEME": theme,
"ENABLED_BUTTONS": ",".join(enabled_buttons),
"IS_PREMIUM_OR_TRIALING": str(client.is_premium_or_trialing()),
}
)
Expand Down
Loading