From 41c4be2f54766f96ed48d78b946d04404a870477 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Mon, 19 Jul 2021 23:27:11 +1000 Subject: [PATCH 01/59] Introduce editable-container Contains the shadow root, and references to the styles. Is ignorant of Editable. Is necessary, so our we editable.scss does not need to contain information about Codable, ImageHandle or all those other things which have nothing to do with Editable --- ts/editor/FormatBlockButtons.svelte | 11 +++--- ts/editor/TemplateButtons.svelte | 3 +- ts/editor/change-timer.ts | 7 ++-- ts/editor/editable-container.ts | 57 ++++++++++++++++++++++++++++ ts/editor/editing-area.ts | 59 +++++++++++------------------ ts/editor/fields.scss | 9 +++++ ts/editor/focus-handlers.ts | 5 ++- ts/editor/helpers.ts | 6 +++ ts/editor/index.ts | 10 ++--- ts/editor/wrap.ts | 3 +- 10 files changed, 112 insertions(+), 58 deletions(-) create mode 100644 ts/editor/editable-container.ts diff --git a/ts/editor/FormatBlockButtons.svelte b/ts/editor/FormatBlockButtons.svelte index 28c0a2dd7..d2fc2a140 100644 --- a/ts/editor/FormatBlockButtons.svelte +++ b/ts/editor/FormatBlockButtons.svelte @@ -3,7 +3,6 @@ Copyright: Ankitects Pty Ltd and contributors License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html --> + +
+
+
+
+
+
+
+ + diff --git a/ts/editor/ImageHandleContainer.svelte b/ts/editor/ImageHandleContainer.svelte new file mode 100644 index 000000000..c3f2b640d --- /dev/null +++ b/ts/editor/ImageHandleContainer.svelte @@ -0,0 +1,17 @@ + + + +
+ +
diff --git a/ts/editor/change-timer.ts b/ts/editor/change-timer.ts index 0ba729920..033e9f9ed 100644 --- a/ts/editor/change-timer.ts +++ b/ts/editor/change-timer.ts @@ -23,7 +23,9 @@ function clearChangeTimer(): void { export function saveField(currentField: EditingArea, type: "blur" | "key"): void { clearChangeTimer(); - const command = `${type}:${currentField.ord}:${getNoteId()}:${currentField.fieldHTML}` + const command = `${type}:${currentField.ord}:${getNoteId()}:${ + currentField.fieldHTML + }`; bridgeCommand(command); } diff --git a/ts/editor/editing-area.ts b/ts/editor/editing-area.ts index 512b89d37..efc754f93 100644 --- a/ts/editor/editing-area.ts +++ b/ts/editor/editing-area.ts @@ -5,6 +5,8 @@ @typescript-eslint/no-non-null-assertion: "off", */ +import ImageHandleContainer from "./ImageHandleContainer.svelte"; + import type { EditableContainer } from "./editable-container"; import type { Editable } from "./editable"; import type { Codable } from "./codable"; @@ -13,12 +15,14 @@ import { updateActiveButtons } from "./toolbar"; import { bridgeCommand } from "./lib"; import { onInput, onKey, onKeyUp } from "./input-handlers"; import { onFocus, onBlur } from "./focus-handlers"; +import { nightModeKey } from "components/context-keys"; function onCutOrCopy(): void { bridgeCommand("cutOrCopy"); } export class EditingArea extends HTMLDivElement { + imageHandle: ImageHandleContainer; editableContainer: EditableContainer; editable: Editable; codable: Codable; @@ -34,12 +38,32 @@ export class EditingArea extends HTMLDivElement { this.editableContainer.shadowRoot!.appendChild(this.editable); this.appendChild(this.editableContainer); + const context = new Map(); + context.set( + nightModeKey, + document.documentElement.classList.contains("night-mode") + ); + + this.imageHandle = new ImageHandleContainer({ + target: this, + anchor: this.editableContainer, + props: { + hidden: true, + top: 0, + left: 0, + width: 0, + height: 0, + }, + context, + } as any); + this.codable = document.createElement("textarea", { is: "anki-codable", }) as Codable; this.appendChild(this.codable); this.onPaste = this.onPaste.bind(this); + this.showImageHandle = this.showImageHandle.bind(this); } get activeInput(): Editable | Codable { @@ -68,6 +92,7 @@ export class EditingArea extends HTMLDivElement { this.addEventListener("copy", onCutOrCopy); this.addEventListener("oncut", onCutOrCopy); this.addEventListener("mouseup", updateActiveButtons); + this.editable.addEventListener("click", this.showImageHandle); } disconnectedCallback(): void { @@ -80,6 +105,7 @@ export class EditingArea extends HTMLDivElement { this.removeEventListener("copy", onCutOrCopy); this.removeEventListener("oncut", onCutOrCopy); this.removeEventListener("mouseup", updateActiveButtons); + this.editable.removeEventListener("click", this.showImageHandle); } initialize(color: string, content: string): void { @@ -144,6 +170,26 @@ export class EditingArea extends HTMLDivElement { this.activeInput.onPaste(event); } + showImageHandle(event: MouseEvent): void { + if (event.target instanceof HTMLImageElement) { + const image = event.target; + const imageRect = image.getBoundingClientRect(); + const editableRect = this.editable.getBoundingClientRect(); + + (this.imageHandle as any).$set({ + hidden: false, + top: imageRect.top - editableRect.top, + left: imageRect.left - editableRect.left, + width: image.clientWidth, + height: image.clientHeight, + }); + } else { + (this.imageHandle as any).$set({ + hidden: true, + }); + } + } + toggleHtmlEdit(): void { const hadFocus = this.hasFocus(); diff --git a/ts/editor/fields.scss b/ts/editor/fields.scss index acf255509..2d8a9d395 100644 --- a/ts/editor/fields.scss +++ b/ts/editor/fields.scss @@ -15,6 +15,7 @@ } .field { + position: relative; border: 1px solid var(--border); background: var(--frame-bg); From 1756bca212a70d380ca0db3285f5d081d8d9610e Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Wed, 21 Jul 2021 02:15:10 +0200 Subject: [PATCH 03/59] Add image-handle-dimensions to show dimensions directly on image --- ts/editor/ImageHandle.svelte | 62 ++++++++++++++++++++++++--- ts/editor/ImageHandleContainer.svelte | 17 -------- ts/editor/editing-area.ts | 26 +++-------- 3 files changed, 63 insertions(+), 42 deletions(-) delete mode 100644 ts/editor/ImageHandleContainer.svelte diff --git a/ts/editor/ImageHandle.svelte b/ts/editor/ImageHandle.svelte index 68e8f73b8..098c3fc92 100644 --- a/ts/editor/ImageHandle.svelte +++ b/ts/editor/ImageHandle.svelte @@ -6,12 +6,41 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html import { getContext } from "svelte"; import { nightModeKey } from "components/context-keys"; - export let hidden: boolean; + export let image: HTMLImageElement | null = null; + export let container: HTMLElement | null = null; - export let top: number = 0; - export let left: number = 0; - export let width: number = 0; - export let height: number = 0; + let hidden = true; + + let naturalWidth = 0; + let naturalHeight = 0; + + let containerTop = 0; + let containerLeft = 0; + + let top = 0; + let left = 0; + let width = 0; + let height = 0; + + $: if (image) { + const imageRect = image.getBoundingClientRect(); + const containerRect = ( + container ?? document.documentElement + ).getBoundingClientRect(); + + naturalWidth = image.naturalWidth; + naturalHeight = image.naturalHeight; + + (containerTop = containerRect.top), + (containerLeft = containerRect.left), + (top = imageRect.top - containerTop), + (left = imageRect.left - containerLeft), + (width = image.clientWidth), + (height = image.clientHeight), + (hidden = false); + } else { + hidden = true; + } function setPointerCapture(event: PointerEvent): void { if (event.pointerId !== 1) { @@ -25,6 +54,14 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html if (!(event.target as Element).hasPointerCapture(event.pointerId)) { return; } + + const dragWidth = event.clientX - containerLeft - left; + const dragHeight = event.clientY - containerTop - top; + + width = dragWidth; + image!.style.width = `${dragWidth}px`; + height = dragHeight; + image!.style.height = `${dragHeight}px`; } const nightMode = getContext(nightModeKey); @@ -36,6 +73,9 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html {hidden} >
+
+ {width}×{height} (Original: {naturalWidth}×{naturalHeight}) +
@@ -66,6 +106,18 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html opacity: 0.2; } + .image-handle-dimensions { + pointer-events: none; + user-select: none; + bottom: 3px; + right: 3px; + + background-color: rgba(0 0 0 / 0.3); + border-color: black; + border-radius: 0.25rem; + padding: 0 5px; + } + .image-handle-control { width: 7px; height: 7px; diff --git a/ts/editor/ImageHandleContainer.svelte b/ts/editor/ImageHandleContainer.svelte deleted file mode 100644 index c3f2b640d..000000000 --- a/ts/editor/ImageHandleContainer.svelte +++ /dev/null @@ -1,17 +0,0 @@ - - - -
- -
diff --git a/ts/editor/editing-area.ts b/ts/editor/editing-area.ts index efc754f93..ecee9ac1a 100644 --- a/ts/editor/editing-area.ts +++ b/ts/editor/editing-area.ts @@ -5,7 +5,7 @@ @typescript-eslint/no-non-null-assertion: "off", */ -import ImageHandleContainer from "./ImageHandleContainer.svelte"; +import ImageHandle from "./ImageHandle.svelte"; import type { EditableContainer } from "./editable-container"; import type { Editable } from "./editable"; @@ -22,7 +22,7 @@ function onCutOrCopy(): void { } export class EditingArea extends HTMLDivElement { - imageHandle: ImageHandleContainer; + imageHandle: ImageHandle; editableContainer: EditableContainer; editable: Editable; codable: Codable; @@ -44,16 +44,10 @@ export class EditingArea extends HTMLDivElement { document.documentElement.classList.contains("night-mode") ); - this.imageHandle = new ImageHandleContainer({ + this.imageHandle = new ImageHandle({ target: this, anchor: this.editableContainer, - props: { - hidden: true, - top: 0, - left: 0, - width: 0, - height: 0, - }, + props: { container: this.editable }, context, } as any); @@ -172,20 +166,12 @@ export class EditingArea extends HTMLDivElement { showImageHandle(event: MouseEvent): void { if (event.target instanceof HTMLImageElement) { - const image = event.target; - const imageRect = image.getBoundingClientRect(); - const editableRect = this.editable.getBoundingClientRect(); - (this.imageHandle as any).$set({ - hidden: false, - top: imageRect.top - editableRect.top, - left: imageRect.left - editableRect.left, - width: image.clientWidth, - height: image.clientHeight, + image: event.target, }); } else { (this.imageHandle as any).$set({ - hidden: true, + image: null, }); } } From 49da806d9196c9117ecb56c5495f6092522934ac Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Wed, 21 Jul 2021 02:37:07 +0200 Subject: [PATCH 04/59] Image resizes preserves ratio --- ts/editor/ImageHandle.svelte | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/ts/editor/ImageHandle.svelte b/ts/editor/ImageHandle.svelte index 098c3fc92..03bec711b 100644 --- a/ts/editor/ImageHandle.svelte +++ b/ts/editor/ImageHandle.svelte @@ -31,13 +31,13 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html naturalWidth = image.naturalWidth; naturalHeight = image.naturalHeight; - (containerTop = containerRect.top), - (containerLeft = containerRect.left), - (top = imageRect.top - containerTop), - (left = imageRect.left - containerLeft), - (width = image.clientWidth), - (height = image.clientHeight), - (hidden = false); + containerTop = containerRect.top; + containerLeft = containerRect.left; + top = imageRect.top - containerTop; + left = imageRect.left - containerLeft; + width = image.clientWidth; + height = image.clientHeight; + hidden = false; } else { hidden = true; } @@ -58,10 +58,19 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html const dragWidth = event.clientX - containerLeft - left; const dragHeight = event.clientY - containerTop - top; - width = dragWidth; - image!.style.width = `${dragWidth}px`; - height = dragHeight; - image!.style.height = `${dragHeight}px`; + const widthIncrease = dragWidth / naturalWidth; + const heightIncrease = dragHeight / naturalHeight; + + if (widthIncrease > heightIncrease) { + width = dragWidth; + height = naturalHeight * widthIncrease; + } else { + height = dragHeight; + width = naturalWidth * heightIncrease; + } + + image!.style.width = `${width}px`; + image!.style.height = `${height}px`; } const nightMode = getContext(nightModeKey); From 8429d0008134247a62e8a0f02bbeda9246d785ae Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Wed, 21 Jul 2021 03:18:33 +0200 Subject: [PATCH 05/59] Add functionality to set float of image --- ts/editor/BUILD.bazel | 5 ++++ ts/editor/ImageHandle.svelte | 25 +++++++++++++++++++ ts/editor/ImageHandleButtons.svelte | 38 +++++++++++++++++++++++++++++ ts/editor/icons.ts | 5 ++++ 4 files changed, 73 insertions(+) create mode 100644 ts/editor/ImageHandleButtons.svelte diff --git a/ts/editor/BUILD.bazel b/ts/editor/BUILD.bazel index 18221bd9e..e71ee51c9 100644 --- a/ts/editor/BUILD.bazel +++ b/ts/editor/BUILD.bazel @@ -118,6 +118,11 @@ copy_mdi_icons( "format-color-text.svg", "format-color-highlight.svg", "color-helper.svg", + + # image handle + "format-float-left.svg", + "format-float-right.svg", + "close-circle-outline.svg", ], visibility = ["//visibility:public"], ) diff --git a/ts/editor/ImageHandle.svelte b/ts/editor/ImageHandle.svelte index 03bec711b..1e4553e5b 100644 --- a/ts/editor/ImageHandle.svelte +++ b/ts/editor/ImageHandle.svelte @@ -3,6 +3,7 @@ Copyright: Ankitects Pty Ltd and contributors License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html --> @@ -82,6 +95,13 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html {hidden} >
+
+ +
{width}×{height} (Original: {naturalWidth}×{naturalHeight})
@@ -115,6 +135,11 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html opacity: 0.2; } + .image-handle-buttons { + top: 3px; + left: 3px; + } + .image-handle-dimensions { pointer-events: none; user-select: none; diff --git a/ts/editor/ImageHandleButtons.svelte b/ts/editor/ImageHandleButtons.svelte new file mode 100644 index 000000000..ddcd9198a --- /dev/null +++ b/ts/editor/ImageHandleButtons.svelte @@ -0,0 +1,38 @@ + + + + + + + dispatch("floatleft")} + >{@html floatLeftIcon} + + + + dispatch("floatright")} + >{@html floatRightIcon} + + + + dispatch("floatreset")} + >{@html resetIcon} + + + diff --git a/ts/editor/icons.ts b/ts/editor/icons.ts index 003cdcd01..d80d5282a 100644 --- a/ts/editor/icons.ts +++ b/ts/editor/icons.ts @@ -33,3 +33,8 @@ export { default as xmlIcon } from "./xml.svg"; export const arrowIcon = ''; + +// image handle +export { default as floatLeftIcon } from "./format-float-left.svg"; +export { default as floatRightIcon } from "./format-float-right.svg"; +export { default as resetIcon } from "./close-circle-outline.svg"; From b3c921b86cb576764dfe78d179697084b914b8de Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Wed, 21 Jul 2021 03:50:52 +0200 Subject: [PATCH 06/59] Bind to style properties instead of events --- ts/editor/ImageHandle.svelte | 46 ++++++++++++++--------------- ts/editor/ImageHandleButtons.svelte | 13 ++++---- 2 files changed, 28 insertions(+), 31 deletions(-) diff --git a/ts/editor/ImageHandle.svelte b/ts/editor/ImageHandle.svelte index 1e4553e5b..3a444877f 100644 --- a/ts/editor/ImageHandle.svelte +++ b/ts/editor/ImageHandle.svelte @@ -89,32 +89,30 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html const nightMode = getContext(nightModeKey); -
-
-
- +
+
+ +
+
+ {width}×{height} (Original: {naturalWidth}×{naturalHeight}) +
+
+
+
+
-
- {width}×{height} (Original: {naturalWidth}×{naturalHeight}) -
-
-
-
-
-
+{/if} diff --git a/ts/components/IconButton.svelte b/ts/components/IconButton.svelte index e23a4d5d3..2f9d7b61a 100644 --- a/ts/components/IconButton.svelte +++ b/ts/components/IconButton.svelte @@ -18,6 +18,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html export let iconSize: number = 75; export let widthMultiplier: number = 1; + export let flipX: boolean = false; let buttonRef: HTMLButtonElement; @@ -44,7 +45,9 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html on:click on:mousedown|preventDefault > - + + + diff --git a/ts/editor/ImageHandle.svelte b/ts/editor/ImageHandle.svelte index e4b585790..a7391bd17 100644 --- a/ts/editor/ImageHandle.svelte +++ b/ts/editor/ImageHandle.svelte @@ -3,6 +3,7 @@ Copyright: Ankitects Pty Ltd and contributors License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html --> -
{#if activeImage}
{/if} -
+ diff --git a/ts/editor/ImageHandle.svelte b/ts/editor/ImageHandle.svelte index a7391bd17..f787d7337 100644 --- a/ts/editor/ImageHandle.svelte +++ b/ts/editor/ImageHandle.svelte @@ -4,6 +4,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html --> From 1c99d163d194683655e23448f5e719a3e7846d23 Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Thu, 5 Aug 2021 02:58:52 +0200 Subject: [PATCH 43/59] Implement HandleBackground --- ts/editor/HandleBackground.svelte | 14 ++++++++++ ts/editor/ImageHandle.svelte | 46 ++++++++++++------------------- 2 files changed, 32 insertions(+), 28 deletions(-) create mode 100644 ts/editor/HandleBackground.svelte diff --git a/ts/editor/HandleBackground.svelte b/ts/editor/HandleBackground.svelte new file mode 100644 index 000000000..b6da7ed78 --- /dev/null +++ b/ts/editor/HandleBackground.svelte @@ -0,0 +1,14 @@ + +
+ + diff --git a/ts/editor/ImageHandle.svelte b/ts/editor/ImageHandle.svelte index 3c9ae55a3..508ece350 100644 --- a/ts/editor/ImageHandle.svelte +++ b/ts/editor/ImageHandle.svelte @@ -3,6 +3,7 @@ Copyright: Ankitects Pty Ltd and contributors License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html --> + +
+ +
+ + diff --git a/ts/editor/ImageHandle.svelte b/ts/editor/ImageHandle.svelte index 508ece350..1d1c07087 100644 --- a/ts/editor/ImageHandle.svelte +++ b/ts/editor/ImageHandle.svelte @@ -6,6 +6,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html import HandleBackground from "./HandleBackground.svelte"; import HandleSelection from "./HandleSelection.svelte"; import HandleControl from "./HandleControl.svelte"; + import HandleLabel from "./HandleLabel.svelte"; import ImageHandleFloat from "./ImageHandleFloat.svelte"; import ImageHandleSizeSelect from "./ImageHandleSizeSelect.svelte"; @@ -52,12 +53,11 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html height = 0; } + let customDimensions: boolean = false; let actualWidth = ""; let actualHeight = ""; - let customDimensions = false; - let overflowFix = 0; - function updateDimensions(dimensions: HTMLDivElement) { + function updateDimensions() { /* we do not want the actual width, but rather the intended display width */ const widthAttribute = activeImage!.getAttribute("width"); customDimensions = false; @@ -78,22 +78,11 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html } else { actualHeight = String(naturalHeight); } - - tick().then(() => { - const boundingClientRect = dimensions.getBoundingClientRect(); - const overflow = isRtl - ? window.innerWidth - boundingClientRect.x - boundingClientRect.width - : boundingClientRect.x; - - overflowFix = Math.min(0, overflowFix + overflow, overflow); - }); } - let dimensions: HTMLDivElement; - async function updateSizesWithDimensions() { updateSizes(); - updateDimensions(dimensions); + updateDimensions(); } /* window resizing */ @@ -224,18 +213,12 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
{/if} -
+ {actualWidth}×{actualHeight} - {#if customDimensions}(Original: {naturalWidth}×{naturalHeight}){/if} -
+ {#if customDimensions} + (Original: {naturalWidth}×{naturalHeight}) + {/if} + From 96fd1f5b7785c62acab3a5d17e9eabe5bd60bbb4 Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Thu, 5 Aug 2021 04:07:04 +0200 Subject: [PATCH 45/59] Remove unused import tick --- ts/editor/ImageHandle.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ts/editor/ImageHandle.svelte b/ts/editor/ImageHandle.svelte index 1d1c07087..31862957a 100644 --- a/ts/editor/ImageHandle.svelte +++ b/ts/editor/ImageHandle.svelte @@ -10,7 +10,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html import ImageHandleFloat from "./ImageHandleFloat.svelte"; import ImageHandleSizeSelect from "./ImageHandleSizeSelect.svelte"; - import { onDestroy, tick } from "svelte"; + import { onDestroy } from "svelte"; export let container: HTMLElement; export let sheet: CSSStyleSheet; From f2f93ef67e2d14ea69917f74013078bf89ca2bc5 Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Thu, 5 Aug 2021 22:48:36 +0200 Subject: [PATCH 46/59] Move more logic into HandleSelection --- ts/editor/HandleSelection.svelte | 42 ++++++++++++++++- ts/editor/ImageHandle.svelte | 63 ++++---------------------- ts/editor/ImageHandleSizeSelect.svelte | 2 + 3 files changed, 53 insertions(+), 54 deletions(-) diff --git a/ts/editor/HandleSelection.svelte b/ts/editor/HandleSelection.svelte index ce9753aaa..52fd5dfc7 100644 --- a/ts/editor/HandleSelection.svelte +++ b/ts/editor/HandleSelection.svelte @@ -2,7 +2,47 @@ Copyright: Ankitects Pty Ltd and contributors License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html --> -
+ + +
diff --git a/ts/editor/ImageHandle.svelte b/ts/editor/ImageHandle.svelte index 31862957a..1933dd4bc 100644 --- a/ts/editor/ImageHandle.svelte +++ b/ts/editor/ImageHandle.svelte @@ -22,37 +22,6 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html $: aspectRatio = naturalWidth && naturalHeight ? naturalWidth / naturalHeight : NaN; $: showFloat = activeImage ? Number(activeImage!.width) >= 100 : false; - /* SIZES */ - let containerLeft = 0; - let containerTop = 0; - - let left = 0; - let top = 0; - let width = 0; - let height = 0; - - function updateSizes() { - const containerRect = container.getBoundingClientRect(); - const imageRect = activeImage!.getBoundingClientRect(); - - containerLeft = containerRect.left; - containerTop = containerRect.top; - - left = imageRect!.left - containerLeft; - top = imageRect!.top - containerTop; - width = activeImage!.clientWidth; - height = activeImage!.clientHeight; - } - - function resetSizes() { - activeImage = null; - - left = 0; - top = 0; - width = 0; - height = 0; - } - let customDimensions: boolean = false; let actualWidth = ""; let actualHeight = ""; @@ -80,8 +49,10 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html } } + let updateSelection: () => void; + async function updateSizesWithDimensions() { - updateSizes(); + updateSelection(); updateDimensions(); } @@ -148,11 +119,12 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html const widthIncrease = dragWidth / naturalWidth!; const heightIncrease = dragHeight / naturalHeight!; + let width: number; + if (widthIncrease > heightIncrease) { width = Math.max(Math.trunc(dragWidth), minResizeWidth); - height = Math.trunc(naturalHeight! * (width / naturalWidth!)); } else { - height = Math.max(Math.trunc(dragHeight), minResizeHeight); + let height = Math.max(Math.trunc(dragHeight), minResizeHeight); width = Math.trunc(naturalWidth! * (height / naturalHeight!)); } @@ -162,36 +134,21 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html await updateSizesWithDimensions(); } - let sizeSelect: any; + let toggleActualSize: () => void; let active = false; - $: if (activeImage && sizeSelect?.images.includes(activeImage)) { - updateSizes(); - } else { - resetSizes(); - } - - function onDblclick() { - sizeSelect.toggleActualSize(); - } - onDestroy(() => resizeObserver.disconnect()); - + {#if activeImage} - + {/if} {#if sheet}
= 0) { const rule = sheet.cssRules[index] as CSSStyleRule; active = rule.cssText.endsWith("{ }"); + } else { + activeImage = null; } } $: icon = active ? sizeActual : sizeMinimized; From 017b6f9ff1c898920416d7ce1120510a7a7b6575 Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Fri, 6 Aug 2021 00:16:15 +0200 Subject: [PATCH 47/59] Parameterize overlay handle offsets --- ts/editor/HandleControl.svelte | 155 +++++++++++++++++-------------- ts/editor/HandleSelection.svelte | 13 ++- ts/editor/ImageHandle.svelte | 3 + 3 files changed, 95 insertions(+), 76 deletions(-) diff --git a/ts/editor/HandleControl.svelte b/ts/editor/HandleControl.svelte index ef713a866..3b44375a4 100644 --- a/ts/editor/HandleControl.svelte +++ b/ts/editor/HandleControl.svelte @@ -6,7 +6,11 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html import { createEventDispatcher, getContext } from "svelte"; import { nightModeKey } from "components/context-keys"; - export let active: boolean; + export let offsetX = 0; + export let offsetY = 0; + + export let active = false; + export let activeSize = 5; const dispatch = createEventDispatcher(); const nightMode = getContext(nightModeKey); @@ -19,48 +23,57 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-
-
-
+ class="d-contents" + style="--offsetX: {offsetX}px; --offsetY: {offsetY}px; --activeSize: {activeSize}px;" +> +
+
+
+
+
diff --git a/ts/editor/HandleSelection.svelte b/ts/editor/HandleSelection.svelte index 52fd5dfc7..c9a9724d9 100644 --- a/ts/editor/HandleSelection.svelte +++ b/ts/editor/HandleSelection.svelte @@ -6,6 +6,9 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html export let container: HTMLElement; export let activeImage: HTMLImageElement | null; + export let offsetX = 0; + export let offsetY = 0; + $: if (activeImage) { updateSelection(); } else { @@ -41,7 +44,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
@@ -50,9 +53,9 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html div { position: absolute; - left: var(--left, 0); - top: var(--top, 0); - width: var(--width); - height: var(--height); + left: calc(var(--left, 0px) - var(--offsetX, 0px)); + top: calc(var(--top, 0px) - var(--offsetY, 0px)); + width: calc(var(--width) + 2 * var(--offsetX, 0px)); + height: calc(var(--height) + 2 * var(--offsetY, 0px)); } diff --git a/ts/editor/ImageHandle.svelte b/ts/editor/ImageHandle.svelte index 1933dd4bc..8685bee4d 100644 --- a/ts/editor/ImageHandle.svelte +++ b/ts/editor/ImageHandle.svelte @@ -179,6 +179,9 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html Date: Fri, 6 Aug 2021 02:19:36 +0200 Subject: [PATCH 48/59] Introduce WithImageConstrained --- ts/components/WithDropdown.svelte | 2 +- ts/editor/HandleBackground.svelte | 11 +- ts/editor/HandleSelection.svelte | 29 ++-- ts/editor/ImageHandle.svelte | 142 ++++++++--------- ts/editor/ImageHandleFloat.svelte | 16 +- ts/editor/ImageHandleSizeSelect.svelte | 209 ++----------------------- ts/editor/WithImageConstrained.svelte | 199 +++++++++++++++++++++++ 7 files changed, 302 insertions(+), 306 deletions(-) create mode 100644 ts/editor/WithImageConstrained.svelte diff --git a/ts/components/WithDropdown.svelte b/ts/components/WithDropdown.svelte index c50008fdb..487f7fa61 100644 --- a/ts/components/WithDropdown.svelte +++ b/ts/components/WithDropdown.svelte @@ -28,7 +28,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html + +{/if} diff --git a/ts/editor/ImageHandleFloat.svelte b/ts/editor/ImageHandleFloat.svelte index bf000d7de..769721d88 100644 --- a/ts/editor/ImageHandleFloat.svelte +++ b/ts/editor/ImageHandleFloat.svelte @@ -11,7 +11,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html import { floatNoneIcon, floatLeftIcon, floatRightIcon } from "./icons"; - export let activeImage: HTMLImageElement; + export let image: HTMLImageElement; export let isRtl: boolean; const leftValues = { @@ -33,20 +33,18 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html (activeImage.style.float = "")} - >{@html floatNoneIcon} (image.style.float = "")}>{@html floatNoneIcon} (activeImage.style.float = inlineStart.position)} + on:click={() => (image.style.float = inlineStart.position)} >{@html floatLeftIcon} @@ -54,9 +52,9 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html (activeImage.style.float = inlineEnd.position)} + on:click={() => (image.style.float = inlineEnd.position)} >{@html floatRightIcon} diff --git a/ts/editor/ImageHandleSizeSelect.svelte b/ts/editor/ImageHandleSizeSelect.svelte index 37452a512..bed0d71b2 100644 --- a/ts/editor/ImageHandleSizeSelect.svelte +++ b/ts/editor/ImageHandleSizeSelect.svelte @@ -9,209 +9,18 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html import ButtonGroupItem from "components/ButtonGroupItem.svelte"; import IconButton from "components/IconButton.svelte"; - import { createEventDispatcher, onDestroy } from "svelte"; 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; - - $: { - const index = images.indexOf(activeImage!); - - if (index >= 0) { - const rule = sheet.cssRules[index] as CSSStyleRule; - active = rule.cssText.endsWith("{ }"); - } else { - activeImage = null; - } - } - $: icon = active ? sizeActual : sizeMinimized; + export let active: boolean; export let isRtl: boolean; - export let maxWidth = 250; - export let maxHeight = 125; - $: restrictionAspectRatio = maxWidth / maxHeight; - - const dispatch = createEventDispatcher(); - - function createPathRecursive(tokens: string[], element: Element): string[] { - 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(" > "); - } - - export const 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 aspectRatio = image.naturalWidth / image.naturalHeight; - - if (restrictionAspectRatio - aspectRatio > 1) { - // restricted by height - rule.style.setProperty("width", "auto", "important"); - - const width = Number(image.getAttribute("width")) || image.width; - const height = Number(image.getAttribute("height")) || width / aspectRatio; - rule.style.setProperty( - "height", - height < maxHeight ? `${height}px` : "auto", - "important" - ); - } else { - // square or restricted by width - 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; - } - - images.push(image); - const index = sheet.insertRule( - `${createPath(image)} {}`, - sheet.cssRules.length - ); - const rule = sheet.cssRules[index] as CSSStyleRule; - setImageRule(image, rule); - } - - function addImageOnLoad(image: HTMLImageElement): void { - if (image.complete && image.naturalWidth !== 0 && image.naturalHeight !== 0) { - addImage(image); - } else { - image.addEventListener("load", () => { - 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); - } - }); - - $: 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); - } - - onDestroy(() => mutationObserver.disconnect()); + $: icon = active ? sizeActual : sizeMinimized; -{#if activeImage} - - - {@html icon} - - -{/if} + + + {@html icon} + + diff --git a/ts/editor/WithImageConstrained.svelte b/ts/editor/WithImageConstrained.svelte new file mode 100644 index 000000000..a9e303b2d --- /dev/null +++ b/ts/editor/WithImageConstrained.svelte @@ -0,0 +1,199 @@ + + + +{#if activeImage} + +{/if} From cc2641095fb381307aa16ac097fe82e4963b8b65 Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Fri, 6 Aug 2021 03:53:44 +0200 Subject: [PATCH 49/59] Fix automatic positioning of ButtonDropdown after changing float property --- ts/components/WithDropdown.svelte | 30 +++++++++++++++++-- ts/editor/HandleControl.svelte | 4 +++ ts/editor/ImageHandle.svelte | 50 ++++++++++++++++++++----------- ts/editor/ImageHandleFloat.svelte | 20 +++++++++---- 4 files changed, 79 insertions(+), 25 deletions(-) diff --git a/ts/components/WithDropdown.svelte b/ts/components/WithDropdown.svelte index 487f7fa61..cf14700f5 100644 --- a/ts/components/WithDropdown.svelte +++ b/ts/components/WithDropdown.svelte @@ -8,27 +8,51 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html import { setContext, onDestroy } from "svelte"; import { dropdownKey } from "./context-keys"; + export let autoOpen = false; + export let autoClose: boolean | "inside" | "outside" = true; + + export let placement = "bottom-start"; + setContext(dropdownKey, { dropdown: true, "data-bs-toggle": "dropdown", }); let dropdown: Dropdown; + let dropdownObject: Dropdown; const noop = () => {}; function createDropdown(toggle: HTMLElement): Dropdown { /* avoid focusing element toggle on menu activation */ toggle.focus = noop; - dropdown = new Dropdown(toggle, {} as any); + dropdown = new Dropdown(toggle, { + autoClose, + popperConfig: (defaultConfig: Record) => ({ + ...defaultConfig, + placement, + }), + } as any); - return dropdown; + if (autoOpen) { + dropdown.show(); + } + + dropdownObject = { + show: dropdown.show.bind(dropdown), + toggle: dropdown.toggle.bind(dropdown), + hide: dropdown.hide.bind(dropdown), + update: dropdown.update.bind(dropdown), + dispose: dropdown.dispose.bind(dropdown), + }; + + return dropdownObject; } onDestroy(() => dropdown?.dispose()); diff --git a/ts/components/ButtonGroupItem.svelte b/ts/components/ButtonGroupItem.svelte index a378bf45d..79d214469 100644 --- a/ts/components/ButtonGroupItem.svelte +++ b/ts/components/ButtonGroupItem.svelte @@ -22,19 +22,22 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html const radius = "5px"; + const leftStyle = `--border-left-radius: ${radius}; --border-right-radius: 0; `; + const rightStyle = `--border-left-radius: 0; --border-right-radius: ${radius}; `; + $: { switch (position_) { case ButtonPosition.Standalone: style = `--border-left-radius: ${radius}; --border-right-radius: ${radius}; `; break; - case ButtonPosition.Leftmost: - style = `--border-left-radius: ${radius}; --border-right-radius: 0; `; + case ButtonPosition.InlineStart: + style = leftStyle; break; case ButtonPosition.Center: style = "--border-left-radius: 0; --border-right-radius: 0; "; break; - case ButtonPosition.Rightmost: - style = `--border-left-radius: 0; --border-right-radius: ${radius}; `; + case ButtonPosition.InlineEnd: + style = rightStyle; break; } } diff --git a/ts/components/buttons.ts b/ts/components/buttons.ts index cd64bb1ca..9c57fc08f 100644 --- a/ts/components/buttons.ts +++ b/ts/components/buttons.ts @@ -5,9 +5,9 @@ import type { Registration } from "./registration"; export enum ButtonPosition { Standalone, - Leftmost, + InlineStart, Center, - Rightmost, + InlineEnd, } export interface ButtonRegistration extends Registration { diff --git a/ts/editor/HandleBackground.svelte b/ts/editor/HandleBackground.svelte index fb5f80561..fc6fc4eaf 100644 --- a/ts/editor/HandleBackground.svelte +++ b/ts/editor/HandleBackground.svelte @@ -11,11 +11,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html onMount(() => dispatch("mount", { background })); -
+