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-937] fix: Reviewer UI fixes #1060

Merged
merged 12 commits into from
Dec 20, 2024
64 changes: 44 additions & 20 deletions ankihub/gui/reviewer.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import aqt.webview
from anki.cards import Card
from anki.notes import Note
from aqt import colors
from aqt import QTimer, colors, qconnect
from aqt.gui_hooks import (
reviewer_did_show_answer,
reviewer_did_show_question,
Expand Down Expand Up @@ -124,6 +124,12 @@ def _setup_ui(self):
self.content_webview = aqt.webview.AnkiWebView()
self.content_webview.setMinimumWidth(self.original_mw_min_width)

self.update_header_button_timer = QTimer(self.content_webview)
qconnect(
self.update_header_button_timer.timeout, self._update_header_button_state
)
self.update_header_button_timer.start(200)

self.url_page = CustomWebPage(self.profile, self.content_webview._onBridgeCmd)
self.interceptor = AuthenticationRequestInterceptor(self.content_webview)
self.url_page.profile().setUrlRequestInterceptor(self.interceptor)
Expand Down Expand Up @@ -190,6 +196,18 @@ def update_tabs(
self._update_content_webview()
self._update_header_webview()

def _update_header_button_state(self):
if not self.resources:
return

# We only want to enable the "Open in Browser" button if the content is not hosted on AnkiHub.
enable_open_in_browser_button = not self.get_content_url().startswith(
config.app_url
)
self.header_webview.eval(
f"setOpenInBrowserButtonState({'true' if enable_open_in_browser_button else 'false'});"
)

def _update_content_webview(self):
if not self.resources:
self.set_content_url(None)
Expand Down Expand Up @@ -357,21 +375,25 @@ def _add_ankihub_ai_and_sidebar_and_buttons(web_content: WebContent, context):
return

feature_flags = config.get_feature_flags()
if not (feature_flags.get("mh_integration") or feature_flags.get("chatbot")):
return

if feature_flags.get("mh_integration"):
ankihub_ai_js_template_name = "ankihub_ai.js"
else:
ankihub_ai_js_template_name = "ankihub_ai_old.js"

# TODO This condition is not placed correctly. It should be used to
# show/hide the AnkiHub AI chatbot button when the reviewer_did_show_question hook is called.
# The consquence of the current implementation is that the chatbot icon is shown for notes it
# shouldn't be shown for and (maybe) isn't shown for notes it should be shown for in some cases.
# However, we don't have to fix it for the old chatbot implementation, because we will switch
# to a new one soon. For the new implementation, we should implement the correct logic.
if _related_ah_deck_has_note_embeddings(aqt.mw.reviewer.card.note()):
if feature_flags.get("chatbot"):
if feature_flags.get("mh_integration"):
ankihub_ai_js_template_name = "ankihub_ai.js"
else:
# The new chatbot js (ankihub_ai.js) doesn't show a button, so we can always set it up.
# However, the old chatbot js (ankihub_ai_old.js) shows a button when executed, so we only
# want to execute it when the note has note embeddings.
if not _related_ah_deck_has_note_embeddings(aqt.mw.reviewer.card.note()):
return

ankihub_ai_js_template_name = "ankihub_ai_old.js"

ankihub_ai_js = get_ankihub_ai_js(
template_name=ankihub_ai_js_template_name,
knox_token=config.token(),
Expand All @@ -382,16 +404,17 @@ def _add_ankihub_ai_and_sidebar_and_buttons(web_content: WebContent, context):
)
web_content.body += f"<script>{ankihub_ai_js}</script>"

global reviewer_sidebar
if not reviewer_sidebar:
reviewer_sidebar = ReviewerSidebar(context)
reviewer_sidebar.set_on_auth_failure_hook(_handle_auth_failure)
if feature_flags.get("mh_integration"):
global reviewer_sidebar
if not reviewer_sidebar:
reviewer_sidebar = ReviewerSidebar(context)
reviewer_sidebar.set_on_auth_failure_hook(_handle_auth_failure)

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


def _get_enabled_buttons_list() -> List[str]:
Expand Down Expand Up @@ -495,11 +518,13 @@ def _notify_reviewer_buttons_of_card_change(card: Card) -> None:
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())
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'},
);
"""
Expand All @@ -524,8 +549,7 @@ def _get_resources(tags: List[str], resource_type: ResourceType) -> List[Resourc
if (title_and_slug := mh_tag_to_resource_title_and_slug(tag))
for title, slug in [title_and_slug]
}
result = list(sorted(result, key=lambda x: x.title))
return result
return list(sorted(result, key=lambda x: x.title))


def _get_resource_tags(tags: List[str], resource_type: ResourceType) -> Set[str]:
Expand Down
1 change: 1 addition & 0 deletions ankihub/gui/web/mh_no_urls_empty_state.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
box-sizing: border-box;
padding: 0;
margin: 0;
font-family: "Merriweather Sans", sans-serif;
}
.empty-state-container {
max-width: 1200px;
Expand Down
54 changes: 42 additions & 12 deletions ankihub/gui/web/reviewer_buttons.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@ class AnkiHubReviewerButtons {
this.colorButtonLight = "#F9FAFB";
this.colorButtonSelectedLight = "#C7D2FE";
this.colorButtonBorderLight = "#D1D5DB";
this.colorButtonHoverLight = "#E5E7EB"

this.colorButtonDark = "#030712";
this.colorButtonSelectedDark = "#3730A3";
this.colorButtonBorderDark = "#4b5563";
this.colorButtonHoverDark = "#1f2937";

this.buttonsData = [
{
Expand Down Expand Up @@ -45,7 +47,7 @@ class AnkiHubReviewerButtons {
}

setupButtons() {
const elementsContainer = document.createElement("div");
this.elementsContainer = document.createElement("div");
const buttonContainer = document.createElement("div");
const toggleButtonsButton = document.createElement("button");

Expand Down Expand Up @@ -73,8 +75,9 @@ class AnkiHubReviewerButtons {
}

this.setToggleButtonsButtonStyle(toggleButtonsButton);
this.setElementsContainerStyle(elementsContainer);
this.setElementsContainerStyle(this.elementsContainer);
this.setButtonContainerStyle(buttonContainer);
this.injectReviewerButtonStyleSheet()

this.buttonsData.forEach((buttonData) => {
const container = document.createElement("div");
Expand All @@ -84,6 +87,8 @@ class AnkiHubReviewerButtons {

const buttonElement = document.createElement("button");
buttonElement.id = `ankihub-${buttonData.name}-button`;
buttonElement.classList.add("ankihub-reviewer-button");

this.setButtonStyle(
buttonElement,
(
Expand Down Expand Up @@ -121,15 +126,29 @@ class AnkiHubReviewerButtons {
buttonContainer.appendChild(container);
})

elementsContainer.appendChild(buttonContainer);
elementsContainer.appendChild(toggleButtonsButton);
document.body.appendChild(elementsContainer);
this.elementsContainer.appendChild(buttonContainer);
this.elementsContainer.appendChild(toggleButtonsButton);
document.body.appendChild(this.elementsContainer);
this.injectResourceCountIndicatorStylesheet();
if (this.buttonsData.length == 0) {
toggleButtonsButton.style.display = "none";
}
}

injectReviewerButtonStyleSheet() {
const style = document.createElement("style");
style.innerHTML = `
.ankihub-reviewer-button {
background-color: ${this.theme == "light" ? this.colorButtonLight : this.colorButtonDark};
}

.ankihub-reviewer-button:hover {
background-color: ${this.theme == "light" ? this.colorButtonHoverLight : this.colorButtonHoverDark};
}
`
document.head.appendChild(style);
}

getButtonElement(buttonName) {
return document.getElementById(`ankihub-${buttonName}-button`);
}
Expand All @@ -142,7 +161,7 @@ class AnkiHubReviewerButtons {
setToggleButtonsButtonStyle(toggleButtonsButton) {
const styles = `
.ankihub-toggle-buttons-button:hover {
background: ${this.theme == "light" ? "#e5e7eb" : "#1f2937"} !important;
background: ${this.theme == "light" ? this.colorButtonHoverLight : this.colorButtonHoverDark} !important;
}
.ankihub-toggle-buttons-button-slide {
transform: translateX(100px);
Expand Down Expand Up @@ -221,7 +240,6 @@ class AnkiHubReviewerButtons {
button.style.backgroundSize = "cover";
button.style.backgroundPosition = "center";
button.style.backgroundRepeat = "no-repeat";
button.style.backgroundColor = this.theme == "light" ? this.colorButtonLight : this.colorButtonDark;

button.style.cursor = "pointer";
}
Expand Down Expand Up @@ -269,9 +287,10 @@ class AnkiHubReviewerButtons {
document.head.appendChild(style);
}

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

const visibleButtons = this.getVisibleButtons();
Expand All @@ -294,10 +313,21 @@ class AnkiHubReviewerButtons {
});

this.updateResourceCountIndicators(visibleButtons);

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

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

udpateButtonVisibility(buttonName, isVisible) {
Expand All @@ -308,11 +338,11 @@ class AnkiHubReviewerButtons {
updateButtonStyle(buttonName, isTopButton, isBottomButton) {
const buttonElement = this.getButtonElement(buttonName);
if (isTopButton && isBottomButton) {
buttonElement.style.borderRadius = "8px 0px 0px 8px";
buttonElement.style.borderRadius = "4px 0px 0px 4px";
} else if (isBottomButton) {
buttonElement.style.borderRadius = "0px 0px 0px 8px";
buttonElement.style.borderRadius = "0px 0px 0px 4px";
} else if (isTopButton) {
buttonElement.style.borderRadius = "8px 0px 0px 0px";
buttonElement.style.borderRadius = "4px 0px 0px 0px";
} else {
buttonElement.style.borderRadius = "0px";
}
Expand Down
33 changes: 29 additions & 4 deletions ankihub/gui/web/sidebar_tabs.html
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,29 @@
border-bottom: 2px solid transparent;
color: #6b7280;
transition: color 0.3s, border-color 0.3s;
flex-grow: 1;
flex: 1 1 0;
height: 100%;
align-items: center;
justify-content: center;
overflow: hidden;
}

.webview-tab span {
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
display: block;
width: 100%;
}

.webview-tab:hover {
color: #4B5563;
border-bottom: 2px solid #D1D5DB;
}

.dark .webview-tab:hover {
color: #9CA3AF;
border-bottom: 2px solid #6B7280;
}

.webview-tab.selected {
Expand Down Expand Up @@ -135,8 +155,8 @@
<div class="webview-browser-bar">
<span class="webview-browser-page-title">{{ page_title }}</span>
<div class="button-container">
<button id="open-in-browser-button" onclick="pycmd('ankihub_open_sidebar_content_in_browser')" {% if not
current_active_tab_url %} disabled {% endif %}>
<button id="open-in-browser-button" disabled onclick="pycmd('ankihub_open_sidebar_content_in_browser')" {%
if not current_active_tab_url %} disabled {% endif %}>
<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"
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" />
Expand All @@ -156,7 +176,7 @@
<div class="webview-tab {% if current_active_tab_url == tab.url %}selected{% endif %}"
onclick="selectTab(this); pycmd('ankihub_load_url_in_sidebar { &quot;url&quot;: &quot;{{ tab.url }}&quot; }')"
data-tab="{{ tab.url }}">
{{ tab.title }}
<span>{{ tab.title }}</span>
</div>
{% endfor %}
</nav>
Expand All @@ -171,6 +191,11 @@
element.classList.add('selected');
}

function setOpenInBrowserButtonState(enabled) {
const openInBrowserButton = document.querySelector('#open-in-browser-button');
openInBrowserButton.disabled = !enabled;
}

(function () {
const openInBrowserButton = document.querySelector('#open-in-browser-button');
addTooltip(openInBrowserButton, "Open in browser");
Expand Down
Loading