Skip to content

Commit

Permalink
Merge branch 'main' into BUILD-950
Browse files Browse the repository at this point in the history
  • Loading branch information
pedroven committed Jan 16, 2025
2 parents 6b84995 + df82d16 commit a8f84d3
Show file tree
Hide file tree
Showing 8 changed files with 187 additions and 72 deletions.
6 changes: 4 additions & 2 deletions ankihub/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
"debug_level_logs": false,
"use_staging": false,
"ankihub_ai_chatbot": true,
"boards_and_beyond": true,
"first_aid_forward": true
"boards_and_beyond_step_1": true,
"boards_and_beyond_step_2": true,
"first_aid_forward_step_1": true,
"first_aid_forward_step_2": true
}
57 changes: 55 additions & 2 deletions ankihub/gui/config_dialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@

from typing import cast

from aqt import qconnect
from aqt.qt import QCheckBox, Qt

from ..settings import config

_config_dialog_manager = None
Expand Down Expand Up @@ -56,8 +59,14 @@ def _general_tab(conf_window) -> None:
if config.get_feature_flags().get("mh_integration"):
tab.text("Sidebar", bold=True)
tab.checkbox("ankihub_ai_chatbot", "AnkiHub AI Chatbot")
tab.checkbox("boards_and_beyond", "Boards and Beyond")
tab.checkbox("first_aid_forward", "First Aid Forward")

add_nested_checkboxes(
tab, key_prefix="boards_and_beyond", description="Boards and Beyond"
)
add_nested_checkboxes(
tab, key_prefix="first_aid_forward", description="First Aid Forward"
)

tab.hseparator()
tab.space(8)

Expand All @@ -66,3 +75,47 @@ def _general_tab(conf_window) -> None:
tab.checkbox("debug_level_logs", "Verbose logs (restart required)")

tab.stretch()


def add_nested_checkboxes(config_layout, key_prefix: str, description: str) -> None:

from .ankiaddonconfig.window import ConfigLayout

config_layout = cast(ConfigLayout, config_layout)

main_checkbox = QCheckBox(description)
config_layout.addWidget(main_checkbox)

container_outer = config_layout.hcontainer()
container_outer.setContentsMargins(0, 2, 0, 2)

container_inner = container_outer.vcontainer()
container_inner.setContentsMargins(30, 0, 0, 0)

step_1_checkbox = container_inner.checkbox(
f"{key_prefix}_step_1", description="USMLE Step 1"
)
step_2_checkbox = container_inner.checkbox(
f"{key_prefix}_step_2", description="USMLE Step 2"
)

def update_main_checkbox() -> None:
checkboxes = [step_1_checkbox, step_2_checkbox]
checked_count = sum(checkbox.isChecked() for checkbox in checkboxes)

if checked_count == 0:
main_checkbox.setCheckState(Qt.CheckState.Unchecked)
elif checked_count == len(checkboxes):
main_checkbox.setCheckState(Qt.CheckState.Checked)
else:
main_checkbox.setCheckState(Qt.CheckState.PartiallyChecked)

def on_main_checkbox_clicked() -> None:
is_checked = main_checkbox.checkState() != Qt.CheckState.Unchecked
main_checkbox.setChecked(is_checked)
step_1_checkbox.setChecked(is_checked)
step_2_checkbox.setChecked(is_checked)

qconnect(step_1_checkbox.stateChanged, update_main_checkbox)
qconnect(step_2_checkbox.stateChanged, update_main_checkbox)
qconnect(main_checkbox.clicked, on_main_checkbox_clicked)
5 changes: 5 additions & 0 deletions ankihub/gui/js_message_handling.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from ..db import ankihub_db
from ..gui.terms_dialog import TermsAndConditionsDialog
from ..settings import url_view_note
from .config_dialog import get_config_dialog_manager
from .operations.scheduling import suspend_notes, unsuspend_notes

VIEW_NOTE_PYCMD = "ankihub_view_note"
Expand All @@ -32,6 +33,7 @@
TERMS_AGREEMENT_NOT_ACCEPTED = "terms_agreement_not_accepted"
TERMS_AGREEMENT_ACCEPTED = "terms_agreement_accepted"

OPEN_CONFIG_PYCMD = "ankihub_open_config"

POST_MESSAGE_TO_ANKIHUB_JS_PATH = (
Path(__file__).parent / "web/post_message_to_ankihub_js.js"
Expand Down Expand Up @@ -139,6 +141,9 @@ def _on_js_message(handled: Tuple[bool, Any], message: str, context: Any) -> Any
openLink(url)

return (True, None)
elif message == OPEN_CONFIG_PYCMD:
get_config_dialog_manager().open_config()
return (True, None)

return handled

Expand Down
53 changes: 28 additions & 25 deletions ankihub/gui/reviewer.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
"""Modifies Anki's reviewer UI (aqt.reviewer)."""

import uuid
from dataclasses import dataclass
from enum import Enum
from textwrap import dedent
from typing import Any, Callable, Dict, List, Optional, Set, Tuple
Expand All @@ -27,8 +26,8 @@
from ..db import ankihub_db
from ..gui.menu import AnkiHubLogin
from ..gui.webview import AuthenticationRequestInterceptor, CustomWebPage # noqa: F401
from ..main.utils import mh_tag_to_resource_title_and_slug
from ..settings import config, url_login, url_mh_integrations_preview
from ..main.utils import Resource, mh_tag_to_resource
from ..settings import config, url_login
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 @@ -71,12 +70,6 @@ class ResourceType(Enum):
}


@dataclass(frozen=True)
class Resource:
title: str
url: str


class ReviewerSidebar:
def __init__(self, reviewer: Reviewer):
self.reviewer = reviewer
Expand Down Expand Up @@ -444,26 +437,21 @@ def _inject_ankihub_features_and_setup_sidebar(


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

feature_flags = config.get_feature_flags()

if feature_flags.get("chatbot"):
buttons_map["ankihub_ai_chatbot"] = "chatbot"
if config.public_config.get("ankihub_ai_chatbot"):
result.append("chatbot")

if feature_flags.get("mh_integration"):
buttons_map.update(
{
"boards_and_beyond": "b&b",
"first_aid_forward": "fa4",
}
)
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 [
buttons_map[key]
for key, value in config.public_config.items()
if key in buttons_map and value
]
return result


def _related_ah_deck_has_note_embeddings(note: Note) -> bool:
Expand Down Expand Up @@ -572,14 +560,29 @@ def _show_resources_for_current_card(resource_type: ResourceType) -> None:
def _get_resources(tags: List[str], resource_type: ResourceType) -> List[Resource]:
resource_tags = _get_resource_tags(tags, resource_type)
result = {
Resource(title=title, url=url_mh_integrations_preview(slug))
resource
for tag in resource_tags
if (title_and_slug := mh_tag_to_resource_title_and_slug(tag))
for title, slug in [title_and_slug]
if (
(resource := mh_tag_to_resource(tag)).usmle_step
in _get_enabled_steps_for_resource_type(resource_type)
)
}
return list(sorted(result, key=lambda x: x.title))


def _get_enabled_steps_for_resource_type(resource_type: ResourceType) -> Set[int]:
resource_type_to_config_key_prefix = {
ResourceType.BOARDS_AND_BEYOND: "boards_and_beyond",
ResourceType.FIRST_AID: "first_aid_forward",
}
config_key_prefix = resource_type_to_config_key_prefix[resource_type]
return {
step
for step in [1, 2]
if config.public_config.get(f"{config_key_prefix}_step_{step}")
}


def _get_resource_tags(tags: List[str], resource_type: ResourceType) -> Set[str]:
"""Get all (v12) tags matching a specific resource type."""
search_pattern = f"v12::{RESOURCE_TYPE_TO_TAG_PART[resource_type]}".lower()
Expand Down
9 changes: 9 additions & 0 deletions ankihub/gui/web/sidebar_tabs.html
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,12 @@
d="M15.75 2.25L21 2.25C21.1989 2.25 21.3897 2.32902 21.5303 2.46967C21.671 2.61032 21.75 2.80109 21.75 3V8.25C21.75 8.66421 21.4142 9 21 9C20.5858 9 20.25 8.66421 20.25 8.25V4.81066L8.03033 17.0303C7.73744 17.3232 7.26256 17.3232 6.96967 17.0303C6.67678 16.7374 6.67678 16.2626 6.96967 15.9697L19.1893 3.75L15.75 3.75C15.3358 3.75 15 3.41421 15 3C15 2.58579 15.3358 2.25 15.75 2.25ZM5.25 6.75C4.42157 6.75 3.75 7.42157 3.75 8.25V18.75C3.75 19.5784 4.42157 20.25 5.25 20.25H15.75C16.5784 20.25 17.25 19.5784 17.25 18.75V10.5C17.25 10.0858 17.5858 9.75 18 9.75C18.4142 9.75 18.75 10.0858 18.75 10.5V18.75C18.75 20.4069 17.4069 21.75 15.75 21.75H5.25C3.59315 21.75 2.25 20.4069 2.25 18.75V8.25C2.25 6.59315 3.59315 5.25 5.25 5.25H13.5C13.9142 5.25 14.25 5.58579 14.25 6C14.25 6.41421 13.9142 6.75 13.5 6.75H5.25Z" />
</svg>
</button>
<button id="open-config-button" onclick="pycmd('ankihub_open_config')">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd"
d="M6.27176 1.44389C6.34655 1.06995 6.67488 0.800781 7.05623 0.800781H8.94454C9.32589 0.800781 9.65422 1.06995 9.72901 1.44389L9.99339 2.76581C10.5568 2.98045 11.0767 3.28344 11.536 3.65784L12.8147 3.2253C13.1759 3.1031 13.5732 3.25286 13.7639 3.58311L14.708 5.21844C14.8987 5.54869 14.8297 5.96762 14.5433 6.21936L13.5299 7.10994C13.5763 7.40006 13.6004 7.6976 13.6004 8.00078C13.6004 8.30395 13.5763 8.6015 13.5299 8.89161L14.5433 9.78219C14.8297 10.0339 14.8987 10.4529 14.708 10.7831L13.7639 12.4184C13.5732 12.7487 13.1759 12.8985 12.8147 12.7763L11.5361 12.3437C11.0767 12.7181 10.5568 13.0211 9.99339 13.2357L9.72901 14.5577C9.65422 14.9316 9.32589 15.2008 8.94454 15.2008H7.05623C6.67488 15.2008 6.34655 14.9316 6.27176 14.5577L6.00738 13.2357C5.44393 13.0211 4.92407 12.7181 4.46473 12.3437L3.1861 12.7763C2.82486 12.8985 2.42759 12.7487 2.23692 12.4185L1.29276 10.7831C1.10209 10.4529 1.17103 10.0339 1.45748 9.7822L2.47085 8.89162C2.42448 8.60151 2.40039 8.30396 2.40039 8.00078C2.40039 7.69761 2.42448 7.40006 2.47085 7.10995L1.45748 6.21937C1.17103 5.96763 1.10209 5.54871 1.29276 5.21845L2.23692 3.58312C2.42759 3.25287 2.82486 3.10311 3.1861 3.22531L4.46472 3.65785C4.92406 3.28344 5.44392 2.98045 6.00738 2.76581L6.27176 1.44389ZM8.00039 10.4008C9.32587 10.4008 10.4004 9.32626 10.4004 8.00078C10.4004 6.6753 9.32587 5.60078 8.00039 5.60078C6.6749 5.60078 5.60039 6.6753 5.60039 8.00078C5.60039 9.32626 6.6749 10.4008 8.00039 10.4008Z" />
</svg>
</button>
<button onclick="pycmd('ankihub_close_sidebar')">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd"
Expand Down Expand Up @@ -199,6 +205,9 @@
(function () {
const openInBrowserButton = document.querySelector('#open-in-browser-button');
addTooltip(openInBrowserButton, "Open in browser");

const openConfigButton = document.querySelector('#open-config-button');
addTooltip(openConfigButton, "Configuration");
})();
</script>

Expand Down
13 changes: 11 additions & 2 deletions ankihub/main/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import time
from collections import defaultdict
from concurrent.futures import Future
from dataclasses import dataclass
from pathlib import Path
from textwrap import dedent
from typing import Any, Collection, Dict, Iterable, List, Optional, Sequence, Set, Tuple
Expand All @@ -24,6 +25,7 @@
ANKIHUB_NOTE_TYPE_FIELD_NAME,
ANKIHUB_NOTE_TYPE_MODIFICATION_STRING,
ANKIHUB_TEMPLATE_END_COMMENT,
url_mh_integrations_preview,
url_view_note,
)

Expand Down Expand Up @@ -659,7 +661,14 @@ def collection_schema() -> int:
return aqt.mw.col.db.scalar("select scm from col")


def mh_tag_to_resource_title_and_slug(tag: str) -> Optional[Tuple[str, str]]:
@dataclass(frozen=True)
class Resource:
title: str
url: str
usmle_step: int


def mh_tag_to_resource(tag: str) -> Optional[Resource]:
"""Converts a McGrawHill tag to a title and URL for the MH resource preview.
Example:
Expand Down Expand Up @@ -695,4 +704,4 @@ def mh_tag_to_resource_title_and_slug(tag: str) -> Optional[Tuple[str, str]]:
# We want to ignore any tags that don't match the expected format
return None

return title, slug
return Resource(title=title, url=url_mh_integrations_preview(slug), usmle_step=step)
8 changes: 8 additions & 0 deletions ankihub/public_config_migrations.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,11 @@ def migrate_public_config() -> None:
if "ankihub_url" in addon_config:
addon_config.pop("ankihub_url")
aqt.mw.addonManager.writeConfig(__name__, addon_config)

if "boards_and_beyond" in addon_config:
addon_config.pop("boards_and_beyond")
aqt.mw.addonManager.writeConfig(__name__, addon_config)

if "first_aid_forward" in addon_config:
addon_config.pop("first_aid_forward")
aqt.mw.addonManager.writeConfig(__name__, addon_config)
Loading

0 comments on commit a8f84d3

Please sign in to comment.