anki/ts/editor/ImageHandle.svelte

328 lines
8.3 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="typescript">
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";
import { onDestroy, getContext } from "svelte";
2021-07-21 01:32:09 +02:00
import { nightModeKey } from "components/context-keys";
export let image: HTMLImageElement | null = null;
2021-07-21 14:13:56 +02:00
export let imageRule: CSSStyleRule | null = null;
2021-07-21 16:48:02 +02:00
export let isRtl: boolean = false;
2021-07-21 14:13:56 +02:00
export let container: HTMLElement;
$: selector = `:not([src="${image?.getAttribute("src")}"])`;
$: naturalWidth = image?.naturalWidth;
$: naturalHeight = image?.naturalHeight;
$: aspectRatio = naturalWidth && naturalHeight ? naturalWidth / naturalHeight : NaN;
$: showDimensions = image
? parseInt(getComputedStyle(image).getPropertyValue("width")) > 100
: false;
$: active = imageRule?.selectorText.includes(selector) as boolean;
let actualWidth = "";
let actualHeight = "";
let customDimensions = false;
let containerTop = 0;
let containerLeft = 0;
let top = 0;
let left = 0;
let width = 0;
let height = 0;
$: if (image) {
2021-07-21 14:13:56 +02:00
updateSizes();
}
const observer = new ResizeObserver(() => {
if (image) {
updateSizes();
}
});
function startObserving() {
observer.observe(container);
}
function stopObserving() {
observer.unobserve(container);
}
startObserving();
2021-07-21 14:13:56 +02:00
function updateSizes() {
2021-07-21 16:48:02 +02:00
const containerRect = container.getBoundingClientRect();
const imageRect = image!.getBoundingClientRect();
2021-07-21 02:37:07 +02:00
containerTop = containerRect.top;
containerLeft = containerRect.left;
top = imageRect!.top - containerTop;
left = imageRect!.left - containerLeft;
2021-07-21 14:13:56 +02:00
width = image!.clientWidth;
height = image!.clientHeight;
/* we do not want the actual width, but rather the intended display width */
const widthProperty = image!.style.width;
let inPixel = false;
customDimensions = false;
if (widthProperty) {
if (widthProperty.endsWith("px")) {
actualWidth = widthProperty.substring(0, widthProperty.length - 2);
inPixel = true;
} else {
actualWidth = widthProperty;
}
customDimensions = true;
} else {
actualWidth = String(naturalWidth);
}
const heightProperty = image!.style.height;
if (heightProperty) {
actualHeight = heightProperty.endsWith("px")
? heightProperty.substring(0, heightProperty.length - 2)
: heightProperty;
customDimensions = true;
} else if (inPixel) {
actualHeight = String(Math.trunc(Number(actualWidth) / aspectRatio));
} else {
actualHeight = String(naturalHeight);
}
}
2021-07-21 01:32:09 +02:00
function setPointerCapture(event: PointerEvent): void {
if (!active || event.pointerId !== 1) {
2021-07-21 01:32:09 +02:00
return;
}
stopObserving();
2021-07-21 01:32:09 +02:00
(event.target as Element).setPointerCapture(event.pointerId);
}
function resize(event: PointerEvent): void {
const element = event.target! as Element;
if (!element.hasPointerCapture(event.pointerId)) {
2021-07-21 01:32:09 +02:00
return;
}
const dragWidth = event.clientX - containerLeft - left;
const dragHeight = event.clientY - containerTop - top;
const widthIncrease = dragWidth / naturalWidth!;
const heightIncrease = dragHeight / naturalHeight!;
2021-07-21 02:37:07 +02:00
if (widthIncrease > heightIncrease) {
width = Math.trunc(dragWidth);
height = Math.trunc(naturalHeight! * widthIncrease);
2021-07-21 02:37:07 +02:00
} else {
height = Math.trunc(dragHeight);
width = Math.trunc(naturalWidth! * heightIncrease);
2021-07-21 02:37:07 +02:00
}
showDimensions = width > 100;
2021-07-21 02:37:07 +02:00
image!.style.width = `${width}px`;
image!.style.removeProperty("height");
2021-07-21 01:32:09 +02:00
}
let sizeSelect: any;
function onDblclick() {
sizeSelect.toggleActualSize();
}
2021-07-21 01:32:09 +02:00
const nightMode = getContext(nightModeKey);
onDestroy(() => observer.disconnect());
2021-07-21 01:32:09 +02:00
</script>
2021-07-21 14:13:56 +02:00
{#if image && imageRule}
<div
style="--top: {top}px; --left: {left}px; --width: {width}px; --height: {height}px;"
class="image-handle-selection"
>
<div
class="image-handle-bg"
on:mousedown|preventDefault
on:dblclick={onDblclick}
/>
<div class="image-handle-float" class:is-rtl={isRtl}>
2021-07-21 16:48:02 +02:00
<ImageHandleFloat {isRtl} bind:float={image.style.float} />
</div>
2021-07-21 16:48:02 +02:00
<div class="image-handle-size-select" class:is-rtl={isRtl}>
2021-07-21 14:13:56 +02:00
<ImageHandleSizeSelect
bind:this={sizeSelect}
bind:active
2021-07-21 14:13:56 +02:00
{image}
{imageRule}
2021-07-21 16:48:02 +02:00
{selector}
{isRtl}
2021-07-21 14:13:56 +02:00
on:update={updateSizes}
/>
</div>
{#if showDimensions}
2021-07-21 16:48:02 +02:00
<div class="image-handle-dimensions" class:is-rtl={isRtl}>
<span>{actualWidth}&times;{actualHeight}</span>
{#if customDimensions}<span
>(Original: {naturalWidth}&times;{naturalHeight})</span
>{/if}
</div>
{/if}
<div
class:nightMode
class="image-handle-control image-handle-control-nw"
on:mousedown|preventDefault
/>
<div
class:nightMode
class="image-handle-control image-handle-control-ne"
on:mousedown|preventDefault
/>
2021-07-21 03:59:58 +02:00
<div
class:nightMode
2021-07-21 14:13:56 +02:00
class:active
class="image-handle-control image-handle-control-sw"
on:mousedown|preventDefault
2021-07-21 03:59:58 +02:00
on:pointerdown={setPointerCapture}
on:pointerup={startObserving}
2021-07-21 03:59:58 +02:00
on:pointermove={resize}
/>
<div
class:nightMode
2021-07-21 14:13:56 +02:00
class:active
class="image-handle-control image-handle-control-se"
on:mousedown|preventDefault
on:pointerdown={setPointerCapture}
on:pointerup={startObserving}
on:pointermove={resize}
/>
</div>
{/if}
2021-07-21 01:32:09 +02:00
<style lang="scss">
div {
position: absolute;
}
.image-handle-selection {
top: var(--top);
left: var(--left);
width: var(--width);
height: var(--height);
}
.image-handle-bg {
width: 100%;
height: 100%;
background-color: black;
opacity: 0.2;
}
.image-handle-float {
top: 3px;
left: 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 {
top: 3px;
right: 3px;
2021-07-21 16:48:02 +02:00
&.is-rtl {
right: auto;
left: 3px;
2021-07-21 16:48:02 +02:00
}
2021-07-21 14:13:56 +02:00
}
.image-handle-dimensions {
pointer-events: none;
user-select: none;
2021-07-21 14:13:56 +02:00
font-size: 13px;
color: white;
background-color: rgba(0 0 0 / 0.3);
border-color: black;
border-radius: 0.25rem;
padding: 0 5px;
2021-07-21 16:48:02 +02:00
bottom: 3px;
right: 3px;
2021-07-26 17:12:51 +02:00
margin-left: 3px;
2021-07-21 16:48:02 +02:00
&.is-rtl {
right: auto;
left: 3px;
2021-07-26 17:12:51 +02:00
margin-right: 3px;
2021-07-21 16:48:02 +02:00
}
}
2021-07-21 01:32:09 +02:00
.image-handle-control {
width: 7px;
height: 7px;
border: 1px solid black;
2021-07-21 14:13:56 +02:00
&.active {
2021-07-21 01:32:09 +02:00
background-color: black;
}
&.nightMode {
border-color: white;
2021-07-21 14:13:56 +02:00
&.active {
2021-07-21 01:32:09 +02:00
background-color: white;
}
}
}
.image-handle-control-nw {
top: -5px;
left: -5px;
border-bottom: none;
border-right: none;
}
.image-handle-control-ne {
top: -5px;
right: -5px;
border-bottom: none;
border-left: none;
}
.image-handle-control-sw {
bottom: -5px;
left: -5px;
border-top: none;
border-right: none;
2021-07-21 03:59:58 +02:00
2021-07-21 14:13:56 +02:00
&.active {
2021-07-21 03:59:58 +02:00
cursor: sw-resize;
}
2021-07-21 01:32:09 +02:00
}
.image-handle-control-se {
bottom: -5px;
right: -5px;
border-top: none;
border-left: none;
2021-07-21 14:13:56 +02:00
&.active {
2021-07-21 01:32:09 +02:00
cursor: se-resize;
}
}
</style>