mirror of
https://github.com/ankitects/anki.git
synced 2025-09-24 08:46:37 -04:00
Fix toolbar add-on breakages and introduce toolbar tray layout & API (#2301)
* Layout toolbar using CSS grid, introducing left and right trays The trays provide a space for add-ons to introduce their own widgets to the toolbar without interfering with each other. * Align tray items to the top * Move absolutely positioned add-on items to right toolbar tray Workaround that fixes breakages in add-ons like AMBOSS, Study Timer, and potentially others that currently still inject absolutely positioned elements into the toolbar using `top_toolbar_did_init_links`. * Account for add-ons that add manual padding (e.g. Study Timer) * Add docstrings and slightly refactor * Tweak item alignment * Introduce hooks for extending left and right toolbar trays * Assign CSS classes to all tray items * Add disclaimer on transitional nature of new hooks
This commit is contained in:
parent
cef672a6a1
commit
91d563278f
4 changed files with 142 additions and 6 deletions
|
@ -6,8 +6,33 @@
|
|||
@use "sass/elevation" as *;
|
||||
@use "sass/button-mixins" as button;
|
||||
|
||||
.header {
|
||||
height: 41px;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
align-items: start;
|
||||
align-content: space-between;
|
||||
}
|
||||
|
||||
.left-tray {
|
||||
justify-self: start;
|
||||
}
|
||||
|
||||
.right-tray {
|
||||
justify-self: end;
|
||||
}
|
||||
|
||||
.left-tray,
|
||||
.right-tray {
|
||||
align-self: start;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
display: inline-block;
|
||||
height: 31px;
|
||||
justify-self: center;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
border-bottom-left-radius: prop(border-radius-large);
|
||||
|
|
|
@ -27,3 +27,63 @@ function updateSyncColor(state: SyncState) {
|
|||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Dealing with legacy add-ons that used CSS to absolutely position
|
||||
// themselves at toolbar edges
|
||||
|
||||
function isAbsolutelyPositioned(node: Node): boolean {
|
||||
if (!(node instanceof HTMLElement)) {
|
||||
return false;
|
||||
}
|
||||
return getComputedStyle(node).position === "absolute";
|
||||
}
|
||||
|
||||
function isLegacyAddonElement(node: Node): boolean {
|
||||
if (isAbsolutelyPositioned(node)) {
|
||||
return true;
|
||||
}
|
||||
for (const child of node.childNodes) {
|
||||
if (isAbsolutelyPositioned(child)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function getElementDimensions(element: HTMLElement): [number, number] {
|
||||
const widths = [element.offsetWidth];
|
||||
const heights = [element.offsetHeight];
|
||||
// Some add-ons inject spans or anchors into the toolbar whose dimensions,
|
||||
// as reported by the properties above are zero, but still occupy space due
|
||||
// to their child elements:
|
||||
for (const child of element.childNodes) {
|
||||
if (!(child instanceof HTMLElement)) {
|
||||
continue;
|
||||
}
|
||||
widths.push(child.offsetWidth);
|
||||
heights.push(child.offsetHeight);
|
||||
}
|
||||
return [Math.max(...widths), Math.max(...heights)];
|
||||
}
|
||||
|
||||
function moveLegacyAddonsToTray() {
|
||||
const rightTray = document.getElementsByClassName("right-tray")[0];
|
||||
const toolbarChildren = document.querySelectorAll<HTMLElement>(".toolbar > *");
|
||||
const legacyAddonElements: HTMLElement[] = Array.from(toolbarChildren)
|
||||
.reverse() // restore original add-on load order
|
||||
.filter(isLegacyAddonElement);
|
||||
|
||||
for (const element of legacyAddonElements) {
|
||||
const wrapperElement = document.createElement("div");
|
||||
const dimensions = getElementDimensions(element);
|
||||
element.style.right = "0px"; // remove manual padding
|
||||
wrapperElement.append(element);
|
||||
wrapperElement.style.cssText = `\
|
||||
width: ${dimensions[0]}px; height: ${dimensions[1]}}px;
|
||||
margin-left: 5px; margin-right: 5px; position: relative;`;
|
||||
wrapperElement.className = "tray-item tray-item-legacy";
|
||||
rightTray.append(wrapperElement);
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", moveLegacyAddonsToTray);
|
||||
|
|
|
@ -114,8 +114,13 @@ class Toolbar:
|
|||
web_context = web_context or TopToolbar(self)
|
||||
link_handler = link_handler or self._linkHandler
|
||||
self.web.set_bridge_command(link_handler, web_context)
|
||||
body = self._body.format(
|
||||
toolbar_content=self._centerLinks(),
|
||||
left_tray_content=self._left_tray_content(),
|
||||
right_tray_content=self._right_tray_content(),
|
||||
)
|
||||
self.web.stdHtml(
|
||||
self._body % self._centerLinks(),
|
||||
body,
|
||||
css=["css/toolbar.css"],
|
||||
js=["js/vendor/jquery.min.js", "js/toolbar.js"],
|
||||
context=web_context,
|
||||
|
@ -204,6 +209,22 @@ class Toolbar:
|
|||
|
||||
return "\n".join(links)
|
||||
|
||||
# Add-ons
|
||||
######################################################################
|
||||
|
||||
def _left_tray_content(self) -> str:
|
||||
left_tray_content: list[str] = []
|
||||
gui_hooks.top_toolbar_will_set_left_tray_content(left_tray_content, self)
|
||||
return self._process_tray_content(left_tray_content)
|
||||
|
||||
def _right_tray_content(self) -> str:
|
||||
right_tray_content: list[str] = []
|
||||
gui_hooks.top_toolbar_will_set_right_tray_content(right_tray_content, self)
|
||||
return self._process_tray_content(right_tray_content)
|
||||
|
||||
def _process_tray_content(self, content: list[str]) -> str:
|
||||
return "\n".join(f"""<div class="tray-item">{item}</div>""" for item in content)
|
||||
|
||||
# Sync
|
||||
######################################################################
|
||||
|
||||
|
@ -265,11 +286,11 @@ class Toolbar:
|
|||
######################################################################
|
||||
|
||||
_body = """
|
||||
<center id="outer">
|
||||
<div id="header">
|
||||
<div class="toolbar">%s<div>
|
||||
<div class="header">
|
||||
<div class="left-tray">{left_tray_content}</div>
|
||||
<div class="toolbar">{toolbar_content}</div>
|
||||
<div class="right-tray">{right_tray_content}</div>
|
||||
</div>
|
||||
</center>
|
||||
"""
|
||||
|
||||
|
||||
|
|
|
@ -795,6 +795,36 @@ gui_hooks.webview_did_inject_style_into_page.append(mytest)
|
|||
links.append(my_link)
|
||||
""",
|
||||
),
|
||||
Hook(
|
||||
name="top_toolbar_will_set_left_tray_content",
|
||||
args=["content: list[str]", "top_toolbar: aqt.toolbar.Toolbar"],
|
||||
doc="""Used to add custom add-on components to the *left* area of Anki's main
|
||||
window toolbar
|
||||
|
||||
'content' is a list of HTML strings added by add-ons which you can append your
|
||||
own components or elements to. To equip your components with logic and styling
|
||||
please see `webview_will_set_content` and `webview_did_receive_js_message`.
|
||||
|
||||
Please note that Anki's main screen is due to undergo a significant refactor
|
||||
in the future and, as a result, add-ons subscribing to this hook will likely
|
||||
require changes to continue working.
|
||||
""",
|
||||
),
|
||||
Hook(
|
||||
name="top_toolbar_will_set_right_tray_content",
|
||||
args=["content: list[str]", "top_toolbar: aqt.toolbar.Toolbar"],
|
||||
doc="""Used to add custom add-on components to the *right* area of Anki's main
|
||||
window toolbar
|
||||
|
||||
'content' is a list of HTML strings added by add-ons which you can append your
|
||||
own components or elements to. To equip your components with logic and styling
|
||||
please see `webview_will_set_content` and `webview_did_receive_js_message`.
|
||||
|
||||
Please note that Anki's main screen is due to undergo a significant refactor
|
||||
in the future and, as a result, add-ons subscribing to this hook will likely
|
||||
require changes to continue working.
|
||||
""",
|
||||
),
|
||||
Hook(
|
||||
name="top_toolbar_did_redraw",
|
||||
args=["top_toolbar: aqt.toolbar.Toolbar"],
|
||||
|
|
Loading…
Reference in a new issue