Skip to content

Commit

Permalink
Make tags editor resizable using Henrik's components (ankitects#2046)
Browse files Browse the repository at this point in the history
* Make tags editor resizable using Henrik's components

All credit for the components goes to Henrik. I just tweaked the design a bit and implemented them in NoteEditor.

Co-Authored-By: Henrik Giesel <[email protected]>

* Remove PaneContent padding

Co-Authored-By: Henrik Giesel <[email protected]>

* Add responsive box-shadows on scroll/resize

only shown when content overflows in the respective direction.

* Remove comment

* Fix overflow calculations and shadow mix-up

This happened when I switched from using scrolledToX to overflowX booleans.

* Simplify overflow calculations

* Make drag handles 0 height/width

The remaining height requirement comes from a margin set on NoteEditor.

* Run eslint on components

* Split editor into three panes: Toolbar, Fields, Tags

* Remove upper split for now

to unblock 2.1.55 beta

* Move panes.scss to sass folder

* Use single type for resizable panes

* Implement collapsed state toggled with click on resizer

* Add button to uncollapse tags pane and focus input

* Add indicator for # of tags

* Use dbclick to prevent interference with resize state

* Add utility functions for expand/collapse

* Meddle around with types and formatting

* Fix collapsed state being forgotten on second browser open (dae)

* Fix typecheck (dae)

Our tooling generates .d.ts files from the Svelte files, but it doesn't
expect variables to be exported. By changing them into functions, they
get included in .bazel/bin/ts/components/Pane.svelte.d.ts

* Remove an unnecessary bridgeCommand (dae)

* Fix the bottom of tags getting cut off (dae)

Not sure why offsetHeight is inaccurate in this case.

* Add missing header (dae)

Co-authored-by: Henrik Giesel <[email protected]>
  • Loading branch information
kleinerpirat and hgiesel authored Sep 28, 2022
1 parent a54b815 commit f72570c
Show file tree
Hide file tree
Showing 18 changed files with 804 additions and 170 deletions.
10 changes: 10 additions & 0 deletions qt/aqt/editor.py
Original file line number Diff line number Diff line change
Expand Up @@ -533,6 +533,7 @@ def oncallback(arg: Any) -> None:
setNoteId({});
setColorButtons({});
setTags({});
setTagsCollapsed({});
setMathjaxEnabled({});
setShrinkImages({});
""".format(
Expand All @@ -545,6 +546,7 @@ def oncallback(arg: Any) -> None:
json.dumps(self.note.id),
json.dumps([text_color, highlight_color]),
json.dumps(self.note.tags),
json.dumps(self.mw.pm.tags_collapsed(self.editorMode)),
json.dumps(self.mw.col.get_config("renderMathjax", True)),
json.dumps(self.mw.col.get_config("shrinkEditorImages", True)),
)
Expand Down Expand Up @@ -1167,6 +1169,12 @@ def toggleShrinkImages(self) -> None:
not self.mw.col.get_config("shrinkEditorImages", True),
)

def collapseTags(self) -> None:
aqt.mw.pm.set_tags_collapsed(self.editorMode, True)

def expandTags(self) -> None:
aqt.mw.pm.set_tags_collapsed(self.editorMode, False)

# Links from HTML
######################################################################

Expand Down Expand Up @@ -1195,6 +1203,8 @@ def _init_links(self) -> None:
mathjaxChemistry=Editor.insertMathjaxChemistry,
toggleMathjax=Editor.toggleMathjax,
toggleShrinkImages=Editor.toggleShrinkImages,
expandTags=Editor.expandTags,
collapseTags=Editor.collapseTags,
)


Expand Down
16 changes: 16 additions & 0 deletions qt/aqt/profiles.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@

if TYPE_CHECKING:
from aqt.browser.layout import BrowserLayout
from aqt.editor import EditorMode


# Profile handling
Expand Down Expand Up @@ -553,6 +554,21 @@ def browser_layout(self) -> BrowserLayout:
def set_browser_layout(self, layout: BrowserLayout) -> None:
self.meta["browser_layout"] = layout.value

def editor_key(self, mode: EditorMode) -> str:
from aqt.editor import EditorMode

return {
EditorMode.ADD_CARDS: "add",
EditorMode.BROWSER: "browser",
EditorMode.EDIT_CURRENT: "current",
}[mode]

def tags_collapsed(self, mode: EditorMode) -> bool:
return self.meta.get(f"{self.editor_key(mode)}TagsCollapsed", False)

def set_tags_collapsed(self, mode: EditorMode, collapsed: bool) -> None:
self.meta[f"{self.editor_key(mode)}TagsCollapsed"] = collapsed

def legacy_import_export(self) -> bool:
return self.meta.get("legacy_import", False)

Expand Down
8 changes: 8 additions & 0 deletions sass/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,14 @@ sass_library(
visibility = ["//visibility:public"],
)

sass_library(
name = "panes_lib",
srcs = [
"panes.scss",
],
visibility = ["//visibility:public"],
)

sass_library(
name = "breakpoints_lib",
srcs = [
Expand Down
29 changes: 29 additions & 0 deletions sass/panes.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/* Copyright: Ankitects Pty Ltd and contributors
* License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html */

@mixin resizable($direction, $width-resizable, $height-resizable) {
display: flex;
flex-flow: #{$direction} nowrap;

flex-basis: 0;
flex-grow: var(--pane-size);

overflow: hidden;
overflow-y: auto;

&.resize {
flex-basis: auto;

@if $width-resizable {
&.resize-width {
width: var(--resized-width);
}
}

@if $height-resizable {
&.resize-height {
height: var(--resized-height);
}
}
}
}
1 change: 1 addition & 0 deletions ts/components/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ svelte_check(
"//sass:base_lib",
"//sass:button_mixins_lib",
"//sass:scrollbar_lib",
"//sass:panes_lib",
"//sass:breakpoints_lib",
"//sass:elevation_lib",
"//sass/bootstrap",
Expand Down
117 changes: 117 additions & 0 deletions ts/components/HorizontalResizer.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
<!--
Copyright: Ankitects Pty Ltd and contributors
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-->
<script lang="ts">
import { on } from "../lib/events";
import { Callback, singleCallback } from "../lib/typing";
import IconConstrain from "./IconConstrain.svelte";
import { horizontalHandle } from "./icons";
import type { ResizablePane } from "./types";
export let panes: ResizablePane[];
export let index = 0;
export let tip = "";
export let clientHeight: number;
let destroy: Callback;
let before: ResizablePane;
let after: ResizablePane;
$: resizerAmount = panes.length - 1;
$: componentsHeight = clientHeight - resizerHeight * resizerAmount;
export function move(targets: ResizablePane[], targetHeight: number): void {
const [resizeTarget, resizePartner] = targets;
if (targetHeight <= resizeTarget.maxHeight) {
resizeTarget.resizable.getHeightResizer().setSize(targetHeight);
resizePartner.resizable
.getHeightResizer()
.setSize(componentsHeight - targetHeight);
}
}
function onMove(this: Window, { movementY }: PointerEvent): void {
if (movementY < 0) {
if (after.height - movementY <= after.maxHeight) {
const resized = before.resizable.getHeightResizer().resize(movementY);
after.resizable.getHeightResizer().resize(-resized);
} else {
const resized = before.resizable
.getHeightResizer()
.resize(after.height - after.maxHeight);
after.resizable.getHeightResizer().resize(-resized);
}
} else if (before.height + movementY <= before.maxHeight) {
const resized = after.resizable.getHeightResizer().resize(-movementY);
before.resizable.getHeightResizer().resize(-resized);
} else {
const resized = after.resizable
.getHeightResizer()
.resize(before.height - before.maxHeight);
before.resizable.getHeightResizer().resize(-resized);
}
}
let resizerHeight: number;
function releasePointer(this: Window): void {
destroy();
document.exitPointerLock();
for (const pane of panes) {
pane.resizable.getHeightResizer().stop(componentsHeight, panes.length);
}
}
function lockPointer(this: HTMLDivElement) {
this.requestPointerLock();
before = panes[index];
after = panes[index + 1];
for (const pane of panes) {
pane.resizable.getHeightResizer().start();
}
destroy = singleCallback(
on(window, "pointermove", onMove),
on(window, "pointerup", releasePointer),
);
}
</script>

<div
class="horizontal-resizer"
title={tip}
bind:clientHeight={resizerHeight}
on:pointerdown|preventDefault={lockPointer}
on:dblclick
>
<div class="drag-handle">
<IconConstrain iconSize={80}>{@html horizontalHandle}</IconConstrain>
</div>
</div>

<style lang="scss">
.horizontal-resizer {
width: 100%;
cursor: row-resize;
position: relative;
height: 10px;
border-top: 1px solid var(--border);
z-index: 20;
.drag-handle {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
opacity: 0.4;
}
&:hover .drag-handle {
opacity: 0.8;
}
}
</style>
63 changes: 63 additions & 0 deletions ts/components/Pane.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<!--
Copyright: Ankitects Pty Ltd and contributors
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-->
<script lang="ts">
import { createEventDispatcher } from "svelte";
import { writable } from "svelte/store";
import { resizable, Resizer } from "./resizable";
export let baseSize = 600;
const resizes = writable(false);
const paneSize = writable(baseSize);
const [
{ resizesDimension: resizesWidth, resizedDimension: resizedWidth },
widthAction,
widthResizer,
] = resizable(baseSize, resizes, paneSize);
const [
{ resizesDimension: resizesHeight, resizedDimension: resizedHeight },
heightAction,
heightResizer,
] = resizable(baseSize, resizes, paneSize);
const dispatch = createEventDispatcher();
$: resizeArgs = { width: $resizedWidth, height: $resizedHeight };
$: dispatch("resize", resizeArgs);
export function getHeightResizer(): Resizer {
return heightResizer;
}
export function getWidthResizer(): Resizer {
return widthResizer;
}
</script>

<div
class="pane"
class:resize={$resizes}
class:resize-width={$resizesWidth}
class:resize-height={$resizesHeight}
style:--pane-size={$paneSize}
style:--resized-width="{$resizedWidth}px"
style:--resized-height="{$resizedHeight}px"
on:focusin
on:pointerdown
use:widthAction={(element) => element.offsetWidth}
use:heightAction={(element) => element.offsetHeight}
>
<slot />
</div>

<style lang="scss">
@use "sass/panes" as panes;
.pane {
@include panes.resizable(column, true, true);
}
</style>
81 changes: 81 additions & 0 deletions ts/components/PaneContent.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<!--
Copyright: Ankitects Pty Ltd and contributors
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-->
<script lang="ts">
import { promiseWithResolver } from "../lib/promise";
export let scroll = true;
const [element, elementResolve] = promiseWithResolver<HTMLElement>();
let clientWidth = 0;
let clientHeight = 0;
let scrollWidth = 0;
let scrollHeight = 0;
let scrollTop = 0;
let scrollLeft = 0;
$: overflowTop = scrollTop > 0;
$: overflowBottom = scrollTop < scrollHeight - clientHeight;
$: overflowLeft = scrollLeft > 0;
$: overflowRight = scrollLeft < scrollWidth - clientWidth;
$: shadows = {
top: overflowTop ? "0 5px" : null,
bottom: overflowBottom ? "0 -5px" : null,
left: overflowLeft ? "5px 0" : null,
right: overflowRight ? "-5px 0" : null,
};
const rest = "5px -5px var(--shadow)";
$: shadow = Array.from(
Object.values(shadows).filter((v) => v != null),
(v) => `inset ${v} ${rest}`,
).join(", ");
async function updateScrollState(): Promise<void> {
const el = await element;
scrollHeight = el.scrollHeight;
scrollWidth = el.scrollWidth;
scrollTop = el.scrollTop;
scrollLeft = el.scrollLeft;
}
</script>

<div
class="pane-content"
class:scroll
style:--box-shadow={shadow}
style:--client-height="{clientHeight}px"
use:elementResolve
bind:clientHeight
bind:clientWidth
on:scroll={updateScrollState}
on:resize={updateScrollState}
>
<slot />
</div>

<style lang="scss">
.pane-content {
display: flex;
flex-direction: column;
flex-grow: 1;
overflow: hidden;
&.scroll {
overflow: auto;
}
/* force box-shadow to be rendered above children */
&::before {
content: "";
position: fixed;
pointer-events: none;
left: 0;
right: 0;
z-index: 4;
height: var(--client-height);
box-shadow: var(--box-shadow);
transition: box-shadow 0.1s ease-in-out;
}
}
</style>
Loading

0 comments on commit f72570c

Please sign in to comment.