anki/ts/editor/ImageHandle.svelte

217 lines
6.2 KiB
Svelte
Raw Normal View History

2021-07-21 01:32:09 +02:00
<!--
Copyright: Ankitects Pty Ltd and contributors
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-->
<script lang="ts">
2021-08-05 02:58:52 +02:00
import HandleBackground from "./HandleBackground.svelte";
2021-08-04 22:25:05 +02:00
import HandleSelection from "./HandleSelection.svelte";
2021-08-04 22:58:24 +02:00
import HandleControl from "./HandleControl.svelte";
2021-08-05 03:24:04 +02:00
import HandleLabel from "./HandleLabel.svelte";
2021-07-21 16:48:02 +02:00
import ImageHandleFloat from "./ImageHandleFloat.svelte";
2021-07-21 14:13:56 +02:00
import ImageHandleSizeSelect from "./ImageHandleSizeSelect.svelte";
2021-08-05 04:07:04 +02:00
import { onDestroy } from "svelte";
2021-07-21 01:32:09 +02:00
2021-07-21 14:13:56 +02:00
export let container: HTMLElement;
export let sheet: CSSStyleSheet;
export let activeImage: HTMLImageElement | null = null;
export let isRtl: boolean = false;
$: naturalWidth = activeImage?.naturalWidth;
$: naturalHeight = activeImage?.naturalHeight;
$: aspectRatio = naturalWidth && naturalHeight ? naturalWidth / naturalHeight : NaN;
$: showFloat = activeImage ? Number(activeImage!.width) >= 100 : false;
2021-08-05 03:24:04 +02:00
let customDimensions: boolean = false;
let actualWidth = "";
let actualHeight = "";
2021-08-05 03:24:04 +02:00
function updateDimensions() {
/* we do not want the actual width, but rather the intended display width */
2021-07-29 20:37:51 +02:00
const widthAttribute = activeImage!.getAttribute("width");
customDimensions = false;
2021-07-29 20:37:51 +02:00
if (widthAttribute) {
actualWidth = widthAttribute;
customDimensions = true;
} else {
actualWidth = String(naturalWidth);
}
2021-07-29 20:37:51 +02:00
const heightAttribute = activeImage!.getAttribute("height");
if (heightAttribute) {
actualHeight = heightAttribute;
customDimensions = true;
2021-07-29 20:37:51 +02:00
} else if (customDimensions) {
actualHeight = String(Math.trunc(Number(actualWidth) / aspectRatio));
} else {
actualHeight = String(naturalHeight);
}
}
2021-08-05 22:48:36 +02:00
let updateSelection: () => void;
async function updateSizesWithDimensions() {
2021-08-05 22:48:36 +02:00
updateSelection();
2021-08-05 03:24:04 +02:00
updateDimensions();
}
/* window resizing */
const resizeObserver = new ResizeObserver(async () => {
if (activeImage) {
await updateSizesWithDimensions();
}
});
function startObserving() {
resizeObserver.observe(container);
}
function stopObserving() {
resizeObserver.unobserve(container);
}
startObserving();
/* memoized position of image on resize start
* prevents frantic behavior when image shift into the next/previous line */
let getDragWidth: (event: PointerEvent) => number;
let getDragHeight: (event: PointerEvent) => number;
2021-07-21 01:32:09 +02:00
2021-08-04 22:58:24 +02:00
function setPointerCapture({ detail }: CustomEvent): void {
if (!active || detail.originalEvent.pointerId !== 1) {
return;
}
const imageRect = activeImage!.getBoundingClientRect();
2021-08-04 22:58:24 +02:00
const imageLeft = imageRect!.left;
const imageRight = imageRect!.right;
const [multX, imageX] = detail.west ? [-1, imageRight] : [1, -imageLeft];
2021-08-04 22:58:24 +02:00
getDragWidth = ({ clientX }) => multX * clientX + imageX;
2021-08-04 22:58:24 +02:00
const imageTop = imageRect!.top;
const imageBottom = imageRect!.bottom;
const [multY, imageY] = detail.north ? [-1, imageBottom] : [1, -imageTop];
2021-08-04 22:58:24 +02:00
getDragHeight = ({ clientY }) => multY * clientY + imageY;
2021-08-04 22:58:24 +02:00
stopObserving();
2021-08-04 22:58:24 +02:00
const target = detail.originalEvent.target as Element;
target.setPointerCapture(detail.originalEvent.pointerId);
}
2021-07-21 01:32:09 +02:00
$: [minResizeWidth, minResizeHeight] =
aspectRatio > 1 ? [5 * aspectRatio, 5] : [5, 5 / aspectRatio];
async function resize(event: PointerEvent) {
const element = event.target! as Element;
if (!element.hasPointerCapture(event.pointerId)) {
2021-07-21 01:32:09 +02:00
return;
}
const dragWidth = getDragWidth(event);
const dragHeight = getDragHeight(event);
const widthIncrease = dragWidth / naturalWidth!;
const heightIncrease = dragHeight / naturalHeight!;
2021-07-21 02:37:07 +02:00
2021-08-05 22:48:36 +02:00
let width: number;
if (widthIncrease > heightIncrease) {
width = Math.max(Math.trunc(dragWidth), minResizeWidth);
2021-07-21 02:37:07 +02:00
} else {
2021-08-05 22:48:36 +02:00
let height = Math.max(Math.trunc(dragHeight), minResizeHeight);
width = Math.trunc(naturalWidth! * (height / naturalHeight!));
2021-07-21 02:37:07 +02:00
}
showFloat = width >= 100;
activeImage!.width = width;
await updateSizesWithDimensions();
2021-07-21 01:32:09 +02:00
}
2021-08-05 22:48:36 +02:00
let toggleActualSize: () => void;
let active = false;
onDestroy(() => resizeObserver.disconnect());
2021-07-21 01:32:09 +02:00
</script>
2021-08-05 22:48:36 +02:00
<HandleSelection bind:updateSelection {container} {activeImage}>
{#if activeImage}
2021-08-05 22:48:36 +02:00
<HandleBackground on:dblclick={toggleActualSize} />
2021-08-05 02:58:52 +02:00
{/if}
{#if sheet}
<div class="image-handle-size-select" class:is-rtl={isRtl}>
<ImageHandleSizeSelect
2021-08-05 22:48:36 +02:00
bind:toggleActualSize
2021-08-05 02:58:52 +02:00
bind:active
{container}
{sheet}
{activeImage}
{isRtl}
on:update={updateSizesWithDimensions}
/>
</div>
{/if}
2021-08-05 02:58:52 +02:00
{#if activeImage}
{#if showFloat}
<div
class="image-handle-float"
class:is-rtl={isRtl}
on:click={updateSizesWithDimensions}
>
<ImageHandleFloat {activeImage} {isRtl} />
</div>
{/if}
2021-08-05 03:24:04 +02:00
<HandleLabel {isRtl} on:mount={updateDimensions}>
<span>{actualWidth}&times;{actualHeight}</span>
2021-08-05 03:24:04 +02:00
{#if customDimensions}
<span>(Original: {naturalWidth}&times;{naturalHeight})</span>
{/if}
</HandleLabel>
2021-08-04 22:58:24 +02:00
<HandleControl
{active}
2021-08-06 00:16:15 +02:00
activeSize={7}
offsetX={5}
offsetY={5}
2021-08-04 22:58:24 +02:00
on:pointerclick={setPointerCapture}
on:pointerup={startObserving}
on:pointermove={resize}
/>
{/if}
2021-08-04 22:25:05 +02:00
</HandleSelection>
2021-07-21 01:32:09 +02:00
<style lang="scss">
div {
position: absolute;
}
.image-handle-float {
left: 3px;
2021-08-05 03:24:04 +02:00
top: 3px;
2021-07-21 16:48:02 +02:00
&.is-rtl {
left: auto;
right: 3px;
}
}
2021-07-21 14:13:56 +02:00
.image-handle-size-select {
2021-07-21 16:48:02 +02:00
right: 3px;
2021-08-05 03:24:04 +02:00
top: 3px;
2021-07-21 16:48:02 +02:00
&.is-rtl {
right: auto;
left: 3px;
}
}
2021-07-21 01:32:09 +02:00
</style>