mirror of
https://github.com/ankitects/anki.git
synced 2025-09-20 15:02:21 -04:00
Manage CSSStyleSheet from within SizeSelect where each image has one rule
This commit is contained in:
parent
e581d593d3
commit
d3e46e9da4
5 changed files with 329 additions and 116 deletions
|
@ -2,33 +2,25 @@
|
||||||
Copyright: Ankitects Pty Ltd and contributors
|
Copyright: Ankitects Pty Ltd and contributors
|
||||||
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
-->
|
-->
|
||||||
<script lang="typescript">
|
<script lang="ts">
|
||||||
import ImageHandleFloat from "./ImageHandleFloat.svelte";
|
import ImageHandleFloat from "./ImageHandleFloat.svelte";
|
||||||
import ImageHandleSizeSelect from "./ImageHandleSizeSelect.svelte";
|
import ImageHandleSizeSelect from "./ImageHandleSizeSelect.svelte";
|
||||||
|
|
||||||
import { onDestroy, getContext } from "svelte";
|
import { onDestroy, getContext } from "svelte";
|
||||||
import { nightModeKey } from "components/context-keys";
|
import { nightModeKey } from "components/context-keys";
|
||||||
|
|
||||||
export let image: HTMLImageElement | null = null;
|
export let container: HTMLElement;
|
||||||
export let imageRule: CSSStyleRule | null = null;
|
export let sheet: CSSStyleSheet;
|
||||||
|
export let activeImage: HTMLImageElement | null = null;
|
||||||
export let isRtl: boolean = false;
|
export let isRtl: boolean = false;
|
||||||
|
|
||||||
export let container: HTMLElement;
|
$: naturalWidth = activeImage?.naturalWidth;
|
||||||
|
$: naturalHeight = activeImage?.naturalHeight;
|
||||||
$: selector = `:not([src="${image?.getAttribute("src")}"])`;
|
|
||||||
$: naturalWidth = image?.naturalWidth;
|
|
||||||
$: naturalHeight = image?.naturalHeight;
|
|
||||||
$: aspectRatio = naturalWidth && naturalHeight ? naturalWidth / naturalHeight : NaN;
|
$: aspectRatio = naturalWidth && naturalHeight ? naturalWidth / naturalHeight : NaN;
|
||||||
|
|
||||||
$: showDimensions = image
|
$: showDimensions = activeImage ? Number(activeImage!.height) >= 50 : false;
|
||||||
? parseInt(getComputedStyle(image).getPropertyValue("height")) >= 50
|
|
||||||
: false;
|
|
||||||
|
|
||||||
$: showFloat = image
|
$: showFloat = activeImage ? Number(activeImage!.width) >= 100 : false;
|
||||||
? parseInt(getComputedStyle(image).getPropertyValue("width")) >= 100
|
|
||||||
: false;
|
|
||||||
|
|
||||||
$: active = imageRule?.selectorText.includes(selector) as boolean;
|
|
||||||
|
|
||||||
let actualWidth = "";
|
let actualWidth = "";
|
||||||
let actualHeight = "";
|
let actualHeight = "";
|
||||||
|
@ -42,39 +34,48 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
let width = 0;
|
let width = 0;
|
||||||
let height = 0;
|
let height = 0;
|
||||||
|
|
||||||
$: if (image) {
|
$: if (activeImage) {
|
||||||
updateSizes();
|
updateSizes();
|
||||||
|
} else {
|
||||||
|
resetSizes();
|
||||||
}
|
}
|
||||||
|
|
||||||
const observer = new ResizeObserver(() => {
|
const resizeObserver = new ResizeObserver(() => {
|
||||||
if (image) {
|
if (activeImage) {
|
||||||
updateSizes();
|
updateSizes();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function startObserving() {
|
function startObserving() {
|
||||||
observer.observe(container);
|
resizeObserver.observe(container);
|
||||||
}
|
}
|
||||||
|
|
||||||
function stopObserving() {
|
function stopObserving() {
|
||||||
observer.unobserve(container);
|
resizeObserver.unobserve(container);
|
||||||
}
|
}
|
||||||
|
|
||||||
startObserving();
|
startObserving();
|
||||||
|
|
||||||
|
function resetSizes() {
|
||||||
|
top = 0;
|
||||||
|
left = 0;
|
||||||
|
width = 0;
|
||||||
|
height = 0;
|
||||||
|
}
|
||||||
|
|
||||||
function updateSizes() {
|
function updateSizes() {
|
||||||
const containerRect = container.getBoundingClientRect();
|
const containerRect = container.getBoundingClientRect();
|
||||||
const imageRect = image!.getBoundingClientRect();
|
const imageRect = activeImage!.getBoundingClientRect();
|
||||||
|
|
||||||
containerTop = containerRect.top;
|
containerTop = containerRect.top;
|
||||||
containerLeft = containerRect.left;
|
containerLeft = containerRect.left;
|
||||||
top = imageRect!.top - containerTop;
|
top = imageRect!.top - containerTop;
|
||||||
left = imageRect!.left - containerLeft;
|
left = imageRect!.left - containerLeft;
|
||||||
width = image!.clientWidth;
|
width = activeImage!.clientWidth;
|
||||||
height = image!.clientHeight;
|
height = activeImage!.clientHeight;
|
||||||
|
|
||||||
/* we do not want the actual width, but rather the intended display width */
|
/* we do not want the actual width, but rather the intended display width */
|
||||||
const widthProperty = image!.style.width;
|
const widthProperty = activeImage!.style.width;
|
||||||
let inPixel = false;
|
let inPixel = false;
|
||||||
customDimensions = false;
|
customDimensions = false;
|
||||||
|
|
||||||
|
@ -91,7 +92,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
actualWidth = String(naturalWidth);
|
actualWidth = String(naturalWidth);
|
||||||
}
|
}
|
||||||
|
|
||||||
const heightProperty = image!.style.height;
|
const heightProperty = activeImage!.style.height;
|
||||||
if (inPixel || heightProperty === "auto") {
|
if (inPixel || heightProperty === "auto") {
|
||||||
actualHeight = String(Math.trunc(Number(actualWidth) / aspectRatio));
|
actualHeight = String(Math.trunc(Number(actualWidth) / aspectRatio));
|
||||||
} else if (heightProperty) {
|
} else if (heightProperty) {
|
||||||
|
@ -104,14 +105,43 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function setPointerCapture(event: PointerEvent): void {
|
/* 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;
|
||||||
|
|
||||||
|
const setPointerCapture =
|
||||||
|
(north: boolean, west: boolean) =>
|
||||||
|
(event: PointerEvent): void => {
|
||||||
if (!active || event.pointerId !== 1) {
|
if (!active || event.pointerId !== 1) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const containerRect = container.getBoundingClientRect();
|
||||||
|
const imageRect = activeImage!.getBoundingClientRect();
|
||||||
|
|
||||||
|
const originalContainerY = containerRect.top;
|
||||||
|
const originalContainerX = containerRect.left;
|
||||||
|
const originalY = imageRect!.top - containerTop;
|
||||||
|
const originalX = imageRect!.left - containerLeft;
|
||||||
|
|
||||||
|
getDragWidth = (event) =>
|
||||||
|
west
|
||||||
|
? activeImage!.clientWidth -
|
||||||
|
event.clientX +
|
||||||
|
(originalContainerX + originalX)
|
||||||
|
: event.clientX - originalContainerX - originalX;
|
||||||
|
|
||||||
|
getDragHeight = (event) =>
|
||||||
|
north
|
||||||
|
? activeImage!.clientHeight -
|
||||||
|
event.clientY +
|
||||||
|
(originalContainerY + originalY)
|
||||||
|
: event.clientY - originalContainerY - originalY;
|
||||||
|
|
||||||
stopObserving();
|
stopObserving();
|
||||||
(event.target as Element).setPointerCapture(event.pointerId);
|
(event.target as Element).setPointerCapture(event.pointerId);
|
||||||
}
|
};
|
||||||
|
|
||||||
function resize(event: PointerEvent): void {
|
function resize(event: PointerEvent): void {
|
||||||
const element = event.target! as Element;
|
const element = event.target! as Element;
|
||||||
|
@ -120,8 +150,8 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const dragWidth = event.clientX - containerLeft - left;
|
const dragWidth = getDragWidth(event);
|
||||||
const dragHeight = event.clientY - containerTop - top;
|
const dragHeight = getDragHeight(event);
|
||||||
|
|
||||||
const widthIncrease = dragWidth / naturalWidth!;
|
const widthIncrease = dragWidth / naturalWidth!;
|
||||||
const heightIncrease = dragHeight / naturalHeight!;
|
const heightIncrease = dragHeight / naturalHeight!;
|
||||||
|
@ -137,11 +167,11 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
showDimensions = height >= 50;
|
showDimensions = height >= 50;
|
||||||
showFloat = width >= 100;
|
showFloat = width >= 100;
|
||||||
|
|
||||||
image!.style.width = `${width}px`;
|
activeImage!.width = width;
|
||||||
image!.style.removeProperty("height");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let sizeSelect: any;
|
let sizeSelect: any;
|
||||||
|
let active = false;
|
||||||
|
|
||||||
function onDblclick() {
|
function onDblclick() {
|
||||||
sizeSelect.toggleActualSize();
|
sizeSelect.toggleActualSize();
|
||||||
|
@ -149,35 +179,26 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
const nightMode = getContext(nightModeKey);
|
const nightMode = getContext(nightModeKey);
|
||||||
|
|
||||||
onDestroy(() => observer.disconnect());
|
onDestroy(() => resizeObserver.disconnect());
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if image && imageRule}
|
<div
|
||||||
<div
|
|
||||||
style="--top: {top}px; --left: {left}px; --width: {width}px; --height: {height}px;"
|
style="--top: {top}px; --left: {left}px; --width: {width}px; --height: {height}px;"
|
||||||
class="image-handle-selection"
|
class="image-handle-selection"
|
||||||
>
|
>
|
||||||
|
{#if activeImage}
|
||||||
<div
|
<div
|
||||||
class="image-handle-bg"
|
class="image-handle-bg"
|
||||||
on:mousedown|preventDefault
|
on:mousedown|preventDefault
|
||||||
on:dblclick={onDblclick}
|
on:dblclick={onDblclick}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{#if showFloat}
|
{#if showFloat}
|
||||||
<div class="image-handle-float" class:is-rtl={isRtl}>
|
<div class="image-handle-float" class:is-rtl={isRtl} on:click={updateSizes}>
|
||||||
<ImageHandleFloat {isRtl} bind:float={image.style.float} />
|
<ImageHandleFloat {activeImage} {isRtl} />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<div class="image-handle-size-select" class:is-rtl={isRtl}>
|
|
||||||
<ImageHandleSizeSelect
|
|
||||||
bind:this={sizeSelect}
|
|
||||||
bind:active
|
|
||||||
{image}
|
|
||||||
{imageRule}
|
|
||||||
{selector}
|
|
||||||
{isRtl}
|
|
||||||
on:update={updateSizes}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{#if showDimensions}
|
{#if showDimensions}
|
||||||
<div class="image-handle-dimensions" class:is-rtl={isRtl}>
|
<div class="image-handle-dimensions" class:is-rtl={isRtl}>
|
||||||
<span>{actualWidth}×{actualHeight}</span>
|
<span>{actualWidth}×{actualHeight}</span>
|
||||||
|
@ -186,22 +207,47 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
>{/if}
|
>{/if}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if sheet}
|
||||||
|
<div class="image-handle-size-select" class:is-rtl={isRtl}>
|
||||||
|
<ImageHandleSizeSelect
|
||||||
|
bind:this={sizeSelect}
|
||||||
|
bind:active
|
||||||
|
{container}
|
||||||
|
{sheet}
|
||||||
|
{activeImage}
|
||||||
|
{isRtl}
|
||||||
|
on:update={updateSizes}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if activeImage}
|
||||||
<div
|
<div
|
||||||
class:nightMode
|
class:nightMode
|
||||||
|
class:active
|
||||||
class="image-handle-control image-handle-control-nw"
|
class="image-handle-control image-handle-control-nw"
|
||||||
on:mousedown|preventDefault
|
on:mousedown|preventDefault
|
||||||
|
on:pointerdown={setPointerCapture(true, true)}
|
||||||
|
on:pointerup={startObserving}
|
||||||
|
on:pointermove={resize}
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
class:nightMode
|
class:nightMode
|
||||||
|
class:active
|
||||||
class="image-handle-control image-handle-control-ne"
|
class="image-handle-control image-handle-control-ne"
|
||||||
on:mousedown|preventDefault
|
on:mousedown|preventDefault
|
||||||
|
on:pointerdown={setPointerCapture(true, false)}
|
||||||
|
on:pointerup={startObserving}
|
||||||
|
on:pointermove={resize}
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
class:nightMode
|
class:nightMode
|
||||||
class:active
|
class:active
|
||||||
class="image-handle-control image-handle-control-sw"
|
class="image-handle-control image-handle-control-sw"
|
||||||
on:mousedown|preventDefault
|
on:mousedown|preventDefault
|
||||||
on:pointerdown={setPointerCapture}
|
on:pointerdown={setPointerCapture(false, true)}
|
||||||
on:pointerup={startObserving}
|
on:pointerup={startObserving}
|
||||||
on:pointermove={resize}
|
on:pointermove={resize}
|
||||||
/>
|
/>
|
||||||
|
@ -210,12 +256,12 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
class:active
|
class:active
|
||||||
class="image-handle-control image-handle-control-se"
|
class="image-handle-control image-handle-control-se"
|
||||||
on:mousedown|preventDefault
|
on:mousedown|preventDefault
|
||||||
on:pointerdown={setPointerCapture}
|
on:pointerdown={setPointerCapture(false, false)}
|
||||||
on:pointerup={startObserving}
|
on:pointerup={startObserving}
|
||||||
on:pointermove={resize}
|
on:pointermove={resize}
|
||||||
/>
|
/>
|
||||||
</div>
|
{/if}
|
||||||
{/if}
|
</div>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
div {
|
div {
|
||||||
|
@ -301,6 +347,10 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
left: -5px;
|
left: -5px;
|
||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
border-right: none;
|
border-right: none;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
cursor: nw-resize;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.image-handle-control-ne {
|
.image-handle-control-ne {
|
||||||
|
@ -308,6 +358,10 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
right: -5px;
|
right: -5px;
|
||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
border-left: none;
|
border-left: none;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
cursor: ne-resize;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.image-handle-control-sw {
|
.image-handle-control-sw {
|
||||||
|
|
|
@ -11,7 +11,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
import { floatNoneIcon, floatLeftIcon, floatRightIcon } from "./icons";
|
import { floatNoneIcon, floatLeftIcon, floatRightIcon } from "./icons";
|
||||||
|
|
||||||
export let float: string;
|
export let activeImage: HTMLImageElement;
|
||||||
export let isRtl: boolean;
|
export let isRtl: boolean;
|
||||||
|
|
||||||
const leftValues = {
|
const leftValues = {
|
||||||
|
@ -33,18 +33,20 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
<ButtonGroupItem>
|
<ButtonGroupItem>
|
||||||
<IconButton
|
<IconButton
|
||||||
tooltip={tr.editingFloatNone()}
|
tooltip={tr.editingFloatNone()}
|
||||||
active={float === "" || float === "none"}
|
active={activeImage.style.float === "" ||
|
||||||
|
activeImage.style.float === "none"}
|
||||||
flipX={isRtl}
|
flipX={isRtl}
|
||||||
on:click={() => (float = "")}>{@html floatNoneIcon}</IconButton
|
on:click={() => (activeImage.style.float = "")}
|
||||||
|
>{@html floatNoneIcon}</IconButton
|
||||||
>
|
>
|
||||||
</ButtonGroupItem>
|
</ButtonGroupItem>
|
||||||
|
|
||||||
<ButtonGroupItem>
|
<ButtonGroupItem>
|
||||||
<IconButton
|
<IconButton
|
||||||
tooltip={inlineStart.label}
|
tooltip={inlineStart.label}
|
||||||
active={float === inlineStart.position}
|
active={activeImage.style.float === inlineStart.position}
|
||||||
flipX={isRtl}
|
flipX={isRtl}
|
||||||
on:click={() => (float = inlineStart.position)}
|
on:click={() => (activeImage.style.float = inlineStart.position)}
|
||||||
>{@html floatLeftIcon}</IconButton
|
>{@html floatLeftIcon}</IconButton
|
||||||
>
|
>
|
||||||
</ButtonGroupItem>
|
</ButtonGroupItem>
|
||||||
|
@ -52,9 +54,9 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
<ButtonGroupItem>
|
<ButtonGroupItem>
|
||||||
<IconButton
|
<IconButton
|
||||||
tooltip={inlineEnd.label}
|
tooltip={inlineEnd.label}
|
||||||
active={float === inlineEnd.position}
|
active={activeImage.style.float === inlineEnd.position}
|
||||||
flipX={isRtl}
|
flipX={isRtl}
|
||||||
on:click={() => (float = inlineEnd.position)}
|
on:click={() => (activeImage.style.float = inlineEnd.position)}
|
||||||
>{@html floatRightIcon}</IconButton
|
>{@html floatRightIcon}</IconButton
|
||||||
>
|
>
|
||||||
</ButtonGroupItem>
|
</ButtonGroupItem>
|
||||||
|
|
|
@ -9,37 +9,177 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
import ButtonGroupItem from "components/ButtonGroupItem.svelte";
|
import ButtonGroupItem from "components/ButtonGroupItem.svelte";
|
||||||
import IconButton from "components/IconButton.svelte";
|
import IconButton from "components/IconButton.svelte";
|
||||||
|
|
||||||
import { createEventDispatcher } from "svelte";
|
import { createEventDispatcher, onDestroy } from "svelte";
|
||||||
import { sizeActual, sizeMinimized } from "./icons";
|
import { sizeActual, sizeMinimized } from "./icons";
|
||||||
|
import { nodeIsElement } from "./helpers";
|
||||||
|
|
||||||
|
export let container: HTMLElement;
|
||||||
|
export let sheet: CSSStyleSheet;
|
||||||
|
|
||||||
|
export let activeImage: HTMLImageElement | null;
|
||||||
|
export let active: boolean = false;
|
||||||
|
|
||||||
export let image: HTMLImageElement;
|
|
||||||
export let imageRule: CSSStyleRule;
|
|
||||||
export let selector: string;
|
|
||||||
export let active: boolean;
|
|
||||||
export let isRtl: boolean;
|
export let isRtl: boolean;
|
||||||
|
export let maxWidth = 250;
|
||||||
|
export let maxHeight = 125;
|
||||||
|
|
||||||
$: icon = active ? sizeActual : sizeMinimized;
|
$: icon = active ? sizeActual : sizeMinimized;
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
export function toggleActualSize() {
|
function createPathRecursive(tokens: string[], element: Element): string[] {
|
||||||
if (!image.hasAttribute("src")) {
|
const tagName = element.tagName.toLowerCase();
|
||||||
|
|
||||||
|
if (!element.parentElement) {
|
||||||
|
const nth =
|
||||||
|
Array.prototype.indexOf.call(
|
||||||
|
(element.parentNode! as Document | ShadowRoot).children,
|
||||||
|
element
|
||||||
|
) + 1;
|
||||||
|
return [`${tagName}:nth-child(${nth})`, ...tokens];
|
||||||
|
} else {
|
||||||
|
const nth =
|
||||||
|
Array.prototype.indexOf.call(element.parentElement.children, element) +
|
||||||
|
1;
|
||||||
|
return createPathRecursive(
|
||||||
|
[`${tagName}:nth-child(${nth})`, ...tokens],
|
||||||
|
element.parentElement
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createPath(element: Element): string {
|
||||||
|
return createPathRecursive([], element).join(" > ");
|
||||||
|
}
|
||||||
|
|
||||||
|
let images: HTMLImageElement[] = [];
|
||||||
|
|
||||||
|
$: for (const [index, image] of images.entries()) {
|
||||||
|
const rule = sheet.cssRules[index] as CSSStyleRule;
|
||||||
|
rule.selectorText = createPath(image);
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterImages(nodes: HTMLCollection | Node[]): HTMLImageElement[] {
|
||||||
|
const result: HTMLImageElement[] = [];
|
||||||
|
|
||||||
|
for (const node of nodes) {
|
||||||
|
if (!nodeIsElement(node)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.tagName === "IMG") {
|
||||||
|
result.push(node as HTMLImageElement);
|
||||||
|
} else {
|
||||||
|
result.push(...filterImages(node.children));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setImageRule(image: HTMLImageElement, rule: CSSStyleRule): void {
|
||||||
|
const width = Number(image.getAttribute("width")) | image.width;
|
||||||
|
|
||||||
|
rule.style.setProperty(
|
||||||
|
"width",
|
||||||
|
width < maxWidth ? `${width}px` : "auto",
|
||||||
|
"important"
|
||||||
|
);
|
||||||
|
rule.style.setProperty("height", "auto", "important");
|
||||||
|
rule.style.setProperty("max-width", `min(${maxWidth}px, 100%)`, "important");
|
||||||
|
rule.style.setProperty("max-height", `${maxHeight}px`, "important");
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetImageRule(rule: CSSStyleRule): void {
|
||||||
|
rule.style.removeProperty("width");
|
||||||
|
rule.style.removeProperty("height");
|
||||||
|
rule.style.removeProperty("max-width");
|
||||||
|
rule.style.removeProperty("max-height");
|
||||||
|
}
|
||||||
|
|
||||||
|
function addImage(image: HTMLImageElement): void {
|
||||||
|
if (!container.contains(image)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (active) {
|
images.push(image);
|
||||||
imageRule.selectorText = imageRule.selectorText.replace(selector, "");
|
const index = sheet.insertRule(
|
||||||
active = false;
|
`${createPath(image)} {}`,
|
||||||
|
sheet.cssRules.length
|
||||||
|
);
|
||||||
|
const rule = sheet.cssRules[index] as CSSStyleRule;
|
||||||
|
setImageRule(image, rule);
|
||||||
|
|
||||||
|
images = images;
|
||||||
|
}
|
||||||
|
|
||||||
|
function addImageOnLoad(image: HTMLImageElement): void {
|
||||||
|
if (image.complete && image.naturalWidth !== 0 && image.naturalHeight !== 0) {
|
||||||
|
addImage(image);
|
||||||
} else {
|
} else {
|
||||||
imageRule.selectorText += selector;
|
image.addEventListener("load", () => {
|
||||||
active = true;
|
addImage(image);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeImage(image: HTMLImageElement): void {
|
||||||
|
const index = images.indexOf(image);
|
||||||
|
if (index < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
images.splice(index, 1);
|
||||||
|
sheet.deleteRule(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
const mutationObserver = new MutationObserver((mutations) => {
|
||||||
|
const addedImages = mutations.flatMap((mutation) =>
|
||||||
|
filterImages([...mutation.addedNodes])
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const image of addedImages) {
|
||||||
|
addImageOnLoad(image);
|
||||||
|
}
|
||||||
|
|
||||||
|
const removedImages = mutations.flatMap((mutation) =>
|
||||||
|
filterImages([...mutation.removedNodes])
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const image of removedImages) {
|
||||||
|
removeImage(image);
|
||||||
|
}
|
||||||
|
|
||||||
|
images = images;
|
||||||
|
});
|
||||||
|
|
||||||
|
$: if (container) {
|
||||||
|
mutationObserver.observe(container, { childList: true, subtree: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toggleActualSize() {
|
||||||
|
const index = images.indexOf(activeImage!);
|
||||||
|
if (index === -1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const rule = sheet.cssRules[index] as CSSStyleRule;
|
||||||
|
active = !active;
|
||||||
|
|
||||||
|
if (active) {
|
||||||
|
resetImageRule(rule);
|
||||||
|
} else {
|
||||||
|
setImageRule(activeImage!, rule);
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch("update", active);
|
dispatch("update", active);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onDestroy(() => mutationObserver.disconnect());
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ButtonGroup size={1.6}>
|
{#if activeImage}
|
||||||
|
<ButtonGroup size={1.6}>
|
||||||
<ButtonGroupItem>
|
<ButtonGroupItem>
|
||||||
<IconButton
|
<IconButton
|
||||||
{active}
|
{active}
|
||||||
|
@ -48,4 +188,5 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
on:click={toggleActualSize}>{@html icon}</IconButton
|
on:click={toggleActualSize}>{@html icon}</IconButton
|
||||||
>
|
>
|
||||||
</ButtonGroupItem>
|
</ButtonGroupItem>
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
|
{/if}
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
export class EditableContainer extends HTMLDivElement {
|
export class EditableContainer extends HTMLDivElement {
|
||||||
baseStyle: HTMLStyleElement;
|
baseStyle: HTMLStyleElement;
|
||||||
baseRule?: CSSStyleRule;
|
baseRule?: CSSStyleRule;
|
||||||
imageRule?: CSSStyleRule;
|
imageStyle?: HTMLStyleElement;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
@ -25,6 +25,7 @@ export class EditableContainer extends HTMLDivElement {
|
||||||
|
|
||||||
this.baseStyle = document.createElement("style");
|
this.baseStyle = document.createElement("style");
|
||||||
this.baseStyle.setAttribute("rel", "stylesheet");
|
this.baseStyle.setAttribute("rel", "stylesheet");
|
||||||
|
this.baseStyle.id = "baseStyle";
|
||||||
shadow.appendChild(this.baseStyle);
|
shadow.appendChild(this.baseStyle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,11 +33,6 @@ export class EditableContainer extends HTMLDivElement {
|
||||||
const sheet = this.baseStyle.sheet as CSSStyleSheet;
|
const sheet = this.baseStyle.sheet as CSSStyleSheet;
|
||||||
const baseIndex = sheet.insertRule("anki-editable {}");
|
const baseIndex = sheet.insertRule("anki-editable {}");
|
||||||
this.baseRule = sheet.cssRules[baseIndex] as CSSStyleRule;
|
this.baseRule = sheet.cssRules[baseIndex] as CSSStyleRule;
|
||||||
|
|
||||||
const imageIndex = sheet.insertRule("anki-editable img {}");
|
|
||||||
this.imageRule = sheet.cssRules[imageIndex] as CSSStyleRule;
|
|
||||||
this.imageRule.style.setProperty("max-width", "min(250px, 100%)", "important");
|
|
||||||
this.imageRule.style.setProperty("max-height", "200px", "important");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
initialize(color: string): void {
|
initialize(color: string): void {
|
||||||
|
|
|
@ -23,7 +23,7 @@ function onCutOrCopy(): void {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class EditingArea extends HTMLDivElement {
|
export class EditingArea extends HTMLDivElement {
|
||||||
imageHandle: ImageHandle;
|
imageHandle: Promise<ImageHandle>;
|
||||||
editableContainer: EditableContainer;
|
editableContainer: EditableContainer;
|
||||||
editable: Editable;
|
editable: Editable;
|
||||||
codable: Codable;
|
codable: Codable;
|
||||||
|
@ -35,9 +35,12 @@ export class EditingArea extends HTMLDivElement {
|
||||||
this.editableContainer = document.createElement("div", {
|
this.editableContainer = document.createElement("div", {
|
||||||
is: "anki-editable-container",
|
is: "anki-editable-container",
|
||||||
}) as EditableContainer;
|
}) as EditableContainer;
|
||||||
|
|
||||||
|
const imageStyle = document.createElement("style");
|
||||||
|
imageStyle.setAttribute("rel", "stylesheet");
|
||||||
|
imageStyle.id = "imageHandleStyle";
|
||||||
|
|
||||||
this.editable = document.createElement("anki-editable") as Editable;
|
this.editable = document.createElement("anki-editable") as Editable;
|
||||||
this.editableContainer.shadowRoot!.appendChild(this.editable);
|
|
||||||
this.appendChild(this.editableContainer);
|
|
||||||
|
|
||||||
const context = new Map();
|
const context = new Map();
|
||||||
context.set(
|
context.set(
|
||||||
|
@ -45,14 +48,28 @@ export class EditingArea extends HTMLDivElement {
|
||||||
document.documentElement.classList.contains("night-mode")
|
document.documentElement.classList.contains("night-mode")
|
||||||
);
|
);
|
||||||
|
|
||||||
this.imageHandle = new ImageHandle({
|
let imageHandleResolve: (value: ImageHandle) => void;
|
||||||
|
this.imageHandle = new Promise<ImageHandle>((resolve) => {
|
||||||
|
imageHandleResolve = resolve;
|
||||||
|
});
|
||||||
|
|
||||||
|
imageStyle.addEventListener("load", () =>
|
||||||
|
imageHandleResolve(
|
||||||
|
new ImageHandle({
|
||||||
target: this,
|
target: this,
|
||||||
anchor: this.editableContainer,
|
anchor: this.editableContainer,
|
||||||
props: {
|
props: {
|
||||||
container: this.editable,
|
container: this.editable,
|
||||||
|
sheet: imageStyle.sheet,
|
||||||
},
|
},
|
||||||
context,
|
context,
|
||||||
} as any);
|
} as any)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
this.editableContainer.shadowRoot!.appendChild(imageStyle);
|
||||||
|
this.editableContainer.shadowRoot!.appendChild(this.editable);
|
||||||
|
this.appendChild(this.editableContainer);
|
||||||
|
|
||||||
this.codable = document.createElement("textarea", {
|
this.codable = document.createElement("textarea", {
|
||||||
is: "anki-codable",
|
is: "anki-codable",
|
||||||
|
@ -75,7 +92,7 @@ export class EditingArea extends HTMLDivElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
set fieldHTML(content: string) {
|
set fieldHTML(content: string) {
|
||||||
this.activeInput.fieldHTML = content;
|
this.imageHandle.then(() => (this.activeInput.fieldHTML = content));
|
||||||
}
|
}
|
||||||
|
|
||||||
get fieldHTML(): string {
|
get fieldHTML(): string {
|
||||||
|
@ -186,19 +203,21 @@ export class EditingArea extends HTMLDivElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
resetImageHandle(): void {
|
resetImageHandle(): void {
|
||||||
(this.imageHandle as any).$set({
|
this.imageHandle.then((imageHandle) =>
|
||||||
image: null,
|
(imageHandle as any).$set({
|
||||||
imageRule: null,
|
activeImage: null,
|
||||||
});
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
showImageHandle(event: MouseEvent): void {
|
showImageHandle(event: MouseEvent): void {
|
||||||
if (event.target instanceof HTMLImageElement) {
|
if (event.target instanceof HTMLImageElement) {
|
||||||
(this.imageHandle as any).$set({
|
this.imageHandle.then((imageHandle) =>
|
||||||
image: event.target,
|
(imageHandle as any).$set({
|
||||||
imageRule: this.editableContainer.imageRule,
|
activeImage: event.target,
|
||||||
isRtl: this.isRightToLeft(),
|
isRtl: this.isRightToLeft(),
|
||||||
});
|
})
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
this.resetImageHandle();
|
this.resetImageHandle();
|
||||||
}
|
}
|
||||||
|
@ -211,6 +230,7 @@ export class EditingArea extends HTMLDivElement {
|
||||||
this.fieldHTML = this.codable.teardown();
|
this.fieldHTML = this.codable.teardown();
|
||||||
this.editable.hidden = false;
|
this.editable.hidden = false;
|
||||||
} else {
|
} else {
|
||||||
|
this.resetImageHandle();
|
||||||
this.editable.hidden = true;
|
this.editable.hidden = true;
|
||||||
this.codable.setup(this.editable.fieldHTML);
|
this.codable.setup(this.editable.fieldHTML);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue