forked from ankitects/anki
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Make tags editor resizable using Henrik's components (ankitects#2046)
* 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
1 parent
a54b815
commit f72570c
Showing
18 changed files
with
804 additions
and
170 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
Oops, something went wrong.