anki/ts/editor/ImageHandle.svelte

221 lines
7.4 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-06 02:19:36 +02:00
import WithDropdown from "components/WithDropdown.svelte";
import ButtonDropdown from "components/ButtonDropdown.svelte";
import Item from "components/Item.svelte";
import WithImageConstrained from "./WithImageConstrained.svelte";
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;
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 {
2021-08-06 02:19:36 +02:00
if (detail.originalEvent.pointerId !== 1) {
2021-08-04 22:58:24 +02:00
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
}
activeImage!.width = width;
2021-07-21 01:32:09 +02:00
}
onDestroy(() => resizeObserver.disconnect());
2021-07-21 01:32:09 +02:00
</script>
2021-08-06 02:19:36 +02:00
{#if sheet}
<WithDropdown
placement="bottom"
autoOpen={true}
autoClose={false}
let:createDropdown
let:dropdownObject
2021-08-06 02:19:36 +02:00
>
<WithImageConstrained
{sheet}
{container}
{activeImage}
on:update={() => {
updateSizesWithDimensions();
dropdownObject.update();
}}
let:toggleActualSize
let:active
>
{#if activeImage}
2021-08-06 02:19:36 +02:00
<HandleSelection
bind:updateSelection
{container}
image={activeImage}
on:mount={(event) => createDropdown(event.detail.selection)}
>
<HandleBackground
on:click={(event) => event.stopPropagation()}
2021-08-06 02:19:36 +02:00
on:dblclick={toggleActualSize}
/>
<HandleLabel {isRtl} on:mount={updateDimensions}>
<span>{actualWidth}&times;{actualHeight}</span>
{#if customDimensions}
<span>(Original: {naturalWidth}&times;{naturalHeight})</span
>
{/if}
</HandleLabel>
<HandleControl
{active}
activeSize={8}
2021-08-06 02:19:36 +02:00
offsetX={5}
offsetY={5}
on:click={(event) => event.stopPropagation()}
2021-08-06 02:19:36 +02:00
on:pointerclick={(event) => {
if (active) {
setPointerCapture(event);
}
}}
on:pointerup={startObserving}
on:pointermove={(event) => {
resize(event);
updateSizesWithDimensions();
dropdownObject.update();
}}
2021-08-06 02:19:36 +02:00
/>
</HandleSelection>
<ButtonDropdown>
<div on:click={updateSizesWithDimensions}>
<Item>
<ImageHandleFloat
image={activeImage}
{isRtl}
on:update={dropdownObject.update}
/>
2021-08-06 02:19:36 +02:00
</Item>
<Item>
<ImageHandleSizeSelect
{active}
{isRtl}
on:click={toggleActualSize}
/>
</Item>
</div>
</ButtonDropdown>
{/if}
</WithImageConstrained>
</WithDropdown>
2021-08-06 02:19:36 +02:00
{/if}