word/edit: custom crop interface superior to croppie
authorMischa POSLAWSKY <perl@shiar.org>
Mon, 10 Jan 2022 16:13:17 +0000 (17:13 +0100)
committerMischa POSLAWSKY <perl@shiar.org>
Mon, 7 Feb 2022 17:42:33 +0000 (18:42 +0100)
Some minimal js and absolute positioning can replace v1.12-226-gca5f2bee8e
(2022-02-07) [word/edit: croppie js thumbnail selection interface]
to exact demands, without rounding errors and restricted zoom control.

word/edit.plp
word/editor.css
word/editor.js

index 0f212b86702d05e211cbc2edea6fa52c414e8e31..40bf8afbb97547b3b3639261d4db4d3ded4fd2af 100644 (file)
@@ -10,8 +10,6 @@ Html({
        raw => <<'EOT',
 <link rel="stylesheet" type="text/css" media="all" href="/word/editor.css" />
 <script src="/word/editor.js"></script>
-<script src="https://cdnjs.cloudflare.com/ajax/libs/croppie/2.6.5/croppie.min.js"></script>
-<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/croppie/2.6.5/croppie.min.css" />
 EOT
 });
 
@@ -262,7 +260,7 @@ for my $col (@wordcols) {
                printf '<span class=inline>';
                print $row->input($col => $attr);
                if (my $imgsrc = $attr->{-src}) {
-                       printf('<img id="%spreview" src="/%s" alt="%s"%s />',
+                       printf('<span id="%spreview"><img src="/%s" alt="%s"%s /></span>',
                                $col, $_, $row->{form}, $col eq 'source' && ' hidden'
                        ) for grep { -e } $imgsrc->($row);
                }
index 2e89bb5a3651b1be98eff0495818d246d543e9d8..c3717d531327fbb8aaf4ff33bd750336be8a418f 100644 (file)
@@ -40,8 +40,11 @@ select {
 }
 #convertpreview {
        width: 300px;
+       height: 200px;
        align-self: start;
        flex-shrink: 0;
+       position: relative;
+       overflow: hidden;
 }
 
 .popup {
index 0e54c62fe349a571727d1266af945bf12bddcbc2..5fc985446b6f128e8e3859e8061b43fe6237a413 100644 (file)
@@ -120,26 +120,74 @@ document.addEventListener('DOMContentLoaded', () => {
        let thumbpreview = document.getElementById('convertpreview');
        if (thumbpreview && imgpreview) {
                thumbpreview.onclick = e => {
+                       thumbpreview.onclick = null; // setup once
                        const cropinput = document.getElementById('crop32');
-                       let border = { width: 600, height: 400 };
-                       let crop = new Croppie(thumbpreview, {
-                               boundary: { width: border.width * 1.3, height: border.height * 1.2 },
-                               viewport: border,
-                               update: e => {
-                                       cropinput.value = e.points.map((pos, axis) => {
-                                               Math.round(1000 * pos / (axis % 2 ? border.height : border.width))
-                                       }).join(',');
-                               },
-                       });
-                       crop.bind({
-                               url: imgpreview.src,
-                               points: cropinput.value.split(/[^0-9]/).map((rel, axis) => {
-                                       return rel * (axis % 2 ? border.height : border.width) / 1000
-                               }),
-                       });
-                       crop.elements.overlay.addEventListener('dblclick', e => {
-                               crop.destroy();
-                       });
+                       const imgselect = thumbpreview.children[0];
+                       const border = [300,200];
+                       let crop = cropinput.value.split(/[^0-9]/).map(pos => pos / 1000);
+                       let scale = 1 / (crop[2] - crop[0]) || 1;
+                       crop.push(0); // defined y dimension
+                       crop.splice(2); // end coordinates applied to zoom
+                       crop = crop.map((rel, axis) => rel * border[axis % 2] * scale);
+
+                       let drag = false;
+                       function applydrag(e) {
+                               const pos = [e.pageX, e.pageY];
+                               if (drag) {
+                                       // apply drag delta to crop position
+                                       crop[0] += drag[0] - pos[0];
+                                       crop[1] += drag[1] - pos[1];
+                                       recrop();
+                               }
+                               drag = pos;
+                       }
+                       function recrop() {
+                               [0, 1].forEach(axis => {
+                                       if (crop[axis] > border[axis] * (scale - 1)) {
+                                               crop[axis] = border[axis] * (scale - 1);
+                                       }
+                                       if (crop[axis] < 0) {
+                                               crop[axis] = 0;
+                                       }
+                               });
+                               imgselect.style.left = -crop[0]+'px';
+                               imgselect.style.top  = -crop[1]+'px';
+                               imgselect.style.width = (scale * 100)+'%';
+                               cropinput.value = [
+                                       crop[0] / border[0],     crop[1] / border[1],
+                                       crop[0] / border[0] + 1, crop[1] / border[1] + 1,
+                               ].map(pos => Math.round(1000 * pos / scale));
+                       }
+
+                       imgselect.src = imgpreview.children[0].src;
+                       imgselect.style.position = 'absolute';
+                       recrop();
+
+                       imgselect.onmousedown = e => {
+                               e.preventDefault();
+                               applydrag(e);
+                       };
+                       imgselect.onmouseup = e => {
+                               e.preventDefault();
+                               drag = false;
+                       };
+                       imgselect.onmousemove = e => {
+                               if (!drag) return;
+                               applydrag(e);
+                       };
+                       imgselect.onwheel = e => {
+                               e.preventDefault();
+                               let zoom = e.deltaY * -.05 * scale;
+                               if (scale + zoom < 1) {
+                                       zoom = 1 - scale; // scale = 1
+                               }
+                               [0, 1].forEach(axis => {
+                                       // same area center at altered scale
+                                       crop[axis] += (crop[axis] + border[axis] / 2) / scale * zoom;
+                               });
+                               scale += zoom;
+                               recrop();
+                       };
                };
        }