]> _ Git - fluidbook-toolbox.git/commitdiff
wip #6020 @4
authorVincent Vanwaelscappel <vincent@cubedesigners.com>
Tue, 13 Jun 2023 12:24:01 +0000 (14:24 +0200)
committerVincent Vanwaelscappel <vincent@cubedesigners.com>
Tue, 13 Jun 2023 12:24:01 +0000 (14:24 +0200)
.editorconfig
app/Fluidbook/Link/LinksData.php
app/SubForms/Link/Base.php
package-lock.json
package.json
resources/linkeditor/js/linkeditor.js
resources/linkeditor/js/linkeditor.links.js
resources/linkeditor/style/inc/_links.sass
resources/views/fluidbook_publication/link_editor.blade.php

index 6537ca4677ee97ddcb112f22d086b92721fd578c..2436a504935fa1194af4971ded5cedd4cf78ea21 100644 (file)
@@ -8,6 +8,11 @@ indent_style = space
 indent_size = 4
 trim_trailing_whitespace = true
 
+[*.sh]
+charset = utf-8
+end_of_line = lf
+insert_final_newline = true
+
 [*.md]
 trim_trailing_whitespace = false
 
index b119916b32e702267ef4dee1b9965dd2168f86e5..ea2d137a230957aaabd497ef9fddca2807f5f4b9 100644 (file)
@@ -51,6 +51,7 @@ class LinksData
             'group' => __('Groupe'),
             'zindex' => __('Profondeur'),
             'pdfjs' => __('Mode PDFJS'),
+            'polygon' => __('Tracé du polygone'),
         );
 
         $comments = array();
index 20b94614f277ff00f1ff737c2a3f523443973dcd..a5f7e769764c4e936d436e35c5f5d9c09543fa89 100644 (file)
@@ -256,6 +256,7 @@ class Base extends Form
             $this->addField('group_transform_end', FieldGroupEnd::class);
             $this->addField('zindex', Depth::class, __('Profondeur'));
         }
+        $this->addField('polygon', Textarea::class, __('Tracé libre'));
         $this->addField('group_disposition_end', FieldGroupEnd::class);
     }
 
index 793ac3c7537dd5eb356ace85ebbd0379694fdbe8..91dcc5624dfc6afdfa9d3cb891994581f566cbee 100644 (file)
@@ -1,10 +1,11 @@
 {
-    "name": "FluidbookToolbox",
+    "name": "FluidbookToolboxDev",
     "lockfileVersion": 3,
     "requires": true,
     "packages": {
         "": {
             "dependencies": {
+                "@lucio/graham-scan": "^1.0.0",
                 "ace-builds": "^1.17.0",
                 "cash-dom": "^8.1.5",
                 "codemirror": "^6.0.1",
                 "@lezer/common": "^1.0.0"
             }
         },
+        "node_modules/@lucio/graham-scan": {
+            "version": "1.0.0",
+            "resolved": "https://registry.npmjs.org/@lucio/graham-scan/-/graham-scan-1.0.0.tgz",
+            "integrity": "sha512-BXUL3vWM27BkqKcR6dVh/GUxK1niaAJybDKFxegs/TUPkorBxK6CAlykZGz6ffprCfpAJTshn86/BDFTzheD5Q=="
+        },
         "node_modules/@mapbox/node-pre-gyp": {
             "version": "1.0.10",
             "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.10.tgz",
index f44c93facbb0e7618f2f9269958b0aeab51ceee9..bd93cc6beb9f35c26b1d91077fa6a68905d13ca3 100644 (file)
@@ -27,6 +27,7 @@
         "webfont": "^11.2.26"
     },
     "dependencies": {
+        "@lucio/graham-scan": "^1.0.0",
         "ace-builds": "^1.17.0",
         "cash-dom": "^8.1.5",
         "codemirror": "^6.0.1",
index dee4346609164394cb1726c7632b01132a0c350b..9584e91b6895ae2de86399fb5e80759db00a362d 100644 (file)
@@ -5,6 +5,8 @@ import Noty from "noty";
 import 'noty/lib/noty.css';
 import 'noty/lib/themes/mint.css';
 
+import GrahamScan from "@lucio/graham-scan";
+
 import LinkeditorLinks from './linkeditor.links';
 import LinkeditorLoader from './linkeditor.loader';
 import LinkeditorResize from './linkeditor.resize';
@@ -26,6 +28,8 @@ window.$ = window.jQuery = require('jquery');
 window.key = require('keymaster-reloaded');
 window.tippy = tippy;
 window.Noty = Noty;
+window.GrahamScan = GrahamScan;
+
 
 window.key.filter = function (event) {
     let tagName = (event.target || event.srcElement).tagName;
@@ -174,7 +178,7 @@ LinkEditor.prototype = {
 
 
         $(window).on('keydown', function (e) {
-            if (this.linkeditor.utils.isfocusOnFormItem()) {
+            if ($this.utils.isfocusOnFormItem()) {
                 return true;
             }
             if (e.keyCode == 16) {
@@ -186,6 +190,8 @@ LinkEditor.prototype = {
             } else if (e.keyCode == 18) {
                 $("#linkeditor-main").addClass('duplicate');
 
+            } else if (e.keyCode == 60) {
+                $("#linkeditor-main").addClass('polygon');
             }
             $this.rulers.moveRuler();
             return false;
@@ -198,10 +204,12 @@ LinkEditor.prototype = {
                 $this.zoom.resetZoomDrag();
             } else if (e.keyCode == 18) {
                 $("#linkeditor-main").removeClass('duplicate');
-
+            } else if (e.keyCode == 60) {
+                $("#linkeditor-main").removeClass('polygon');
+                $this.links.closePolygonShape();
             }
             $this.rulers.moveRuler();
-            if (this.linkeditor.utils.isfocusOnFormItem()) {
+            if ($this.utils.isfocusOnFormItem()) {
                 return true;
             }
             return false;
@@ -393,6 +401,7 @@ LinkEditor.prototype = {
         this.rulers.moveRuler();
         this.links.moveDragLink();
         this.links.moveResizeLink();
+        this.links.movePolygonPoint();
         this.zoom.updateMousePosition();
     },
 
index 64b45f923e0f73cb524f592eb67d0b7fb176557b..1a2e4b4c3876ce5f3a747f4f9f4a169638e6db45 100644 (file)
@@ -3,6 +3,7 @@ var LinkeditorLinks = function (linkeditor) {
 
     this.dragLinkPos = null;
     this.resizeLinkPos = null;
+    this.movePolygonPointPos = null;
 
     this.magnetValuesX = [];
     this.magnetValuesY = [];
@@ -13,7 +14,6 @@ var LinkeditorLinks = function (linkeditor) {
     this.contextMenuPosition = null;
 
     this.rectSelection = null;
-    this.shapeMode = 'rectangle';
 
     this.dropTypes = [4, 6, 7, 12, 15, 16, 17, 18, 25, 30, 31];
 
@@ -22,7 +22,6 @@ var LinkeditorLinks = function (linkeditor) {
 
 LinkeditorLinks.prototype = {
     init: function () {
-        this.updateShapeMode();
     },
 
     initEvents: function () {
@@ -36,7 +35,11 @@ LinkeditorLinks.prototype = {
             $this.deselectAllLinks();
             let link = $(this).closest('.link');
             $this.selectLink(link);
-            $this.startResizeLink($(this).attr('class'));
+            if ($(this).hasClass('poly')) {
+                $this.startMovePolygonPoint($(this).data('point'));
+            } else {
+                $this.startResizeLink($(this).attr('class'));
+            }
         });
 
         $(document).on('mousedown', '.link', function (e) {
@@ -410,6 +413,7 @@ LinkeditorLinks.prototype = {
         });
 
         this.linkeditor.hasChanged();
+        this.updatePolygonLinks();
         this.updateLayers();
     },
 
@@ -532,7 +536,7 @@ LinkeditorLinks.prototype = {
     },
 
     createLinkDrag: function () {
-        var link = this.duplicateLinkDrag({width: 0, height: 0});
+        var link = this.duplicateLinkDrag({width: 0, height: 0, polygon: null});
         $(link).addClass('pendingCreate').addClass('new');
         this.deselectAllLinks();
         this.selectLink($(link));
@@ -757,6 +761,7 @@ LinkeditorLinks.prototype = {
     },
 
     selectLink: function (l) {
+        console.log($(l).find('.corners'));
         if ($(l).find('.corners').length === 0) {
             $(l).append('<div class="corners"><div class="nw"></div><div class="n"></div><div class="ne"></div><div class="e"></div><div class="se"></div><div class="s"></div><div class="sw"></div><div class="w"></div></div>')
         }
@@ -859,6 +864,7 @@ LinkeditorLinks.prototype = {
             }
             $this.addLink(link, false);
         });
+        this.updatePolygonLinks();
         this.updateLayers();
         this.linkeditor.undo.initState();
     },
@@ -966,6 +972,7 @@ LinkeditorLinks.prototype = {
 
         this.deselectAllLinks();
         this.selectLink($(link));
+        this.updatePolygonLinks();
         this.linkeditor.form.updateFormData();
 
         return $(link);
@@ -1116,6 +1123,7 @@ LinkeditorLinks.prototype = {
         });
         this.linkeditor.rulers.updateMagnetValues();
         this.updateLayers();
+        this.updatePolygonLinks(false);
     },
 
     updateSelectionData: function (props) {
@@ -1319,61 +1327,93 @@ LinkeditorLinks.prototype = {
     mouseUp: function () {
         this.endRectSelection();
         this.stopDragLink();
-        this.endPolygonLine();
         this.stopResizeLink();
+        this.stopMovePolygonPoint();
         this.cleanPendingCreateLink();
     },
 
     mouseDown: function () {
-        if (this.shapeMode === 'rectangle') {
-            this.createLinkDrag();
-        } else {
+        if ($('#linkeditor-main').hasClass('polygon')) {
             this.beginPolygonLine();
+        } else {
+            this.createLinkDrag();
         }
     },
 
-    toggleShape: function (shape) {
-        if (this.shapeMode === shape) {
-            return;
-        }
-        this.shapeMode = shape;
-        this.updateShapeMode();
-    },
-
-    updateShapeMode: function () {
-        $('[data-icon^="shape"]').removeClass('active');
-        $('[data-icon="shape-' + this.shapeMode + '"]').addClass('active');
-    },
-
     beginPolygonLine: function () {
         var link;
         var pos = this.linkeditor.globalToFluidbook(this.linkeditor.mx, this.linkeditor.my, this.linkeditor.single);
+        let polygon;
         if ($('.pendingPolygonCreate').length === 0) {
             link = this.duplicateLinkDrag({width: 0, height: 0, left: pos.x, top: pos.y});
             $(link).addClass('pendingPolygonCreate');
-            $(link).data('points', [pos]);
+            polygon = [pos];
         } else {
             link = $('.pendingPolygonCreate');
-            $(link).data('points').push(pos);
+            polygon = this.getOffsetPolygon(link);
+            polygon.push(pos);
         }
+
         this.selectLink($(link));
-        this.updatePolygonLink(link);
+        this.updatePolygonLink(link, polygon);
         this.linkeditor.hasChanged();
     },
 
-    updatePolygonLink: function (link) {
+    closePolygonShape: function () {
+        $('.pendingPolygonCreate').removeClass('pendingPolygonCreate');
+    },
+
+    getOffsetPolygon: function (link) {
+        let left = parseFloat($(link).attr('fb-left'));
+        let top = parseFloat($(link).attr('fb-top'));
+        try {
+            let points = JSON.parse($(link).attr('fb-polygon'));
+            let offset = [];
+            $.each(points, function (k, v) {
+                offset.push({x: parseFloat(v.x) + left, y: parseFloat(v.y) + top});
+            });
+            return offset;
+        } catch (e) {
+            return null;
+        }
+    },
+
+    updatePolygonLinks: function (updateData) {
+        if (updateData === undefined) {
+            updateData = true;
+        }
+        let $this = this;
+        $('.link[fb-polygon]').each(function () {
+            let polygon = $this.getOffsetPolygon($(this));
+            if (polygon !== null) {
+                $this.updatePolygonLink($(this), polygon, updateData);
+            } else {
+                $(this).find('svg,.corners').remove();
+                $(this).attr('fb-polygon', null);
+            }
+        });
+    },
+
+    updatePolygonLink: function (link, polygon, updateData) {
+        if (updateData === undefined) {
+            updateData = true;
+        }
+        let $this = this;
+
         let minx = Number.MAX_VALUE;
         let maxx = Number.MIN_VALUE;
         let miny = Number.MAX_VALUE;
         let maxy = Number.MIN_VALUE;
 
-        $.each($(link).data('points'), function (k, pos) {
+        $.each(polygon, function (k, pos) {
             minx = Math.min(minx, pos.x);
             miny = Math.min(miny, pos.y);
             maxx = Math.max(maxx, pos.x);
             maxy = Math.max(maxy, pos.y);
         });
 
+        let normalizedPolygon = [];
+
         let w = maxx - minx;
         let h = maxy - miny;
 
@@ -1382,19 +1422,101 @@ LinkeditorLinks.prototype = {
         $(link).attr('fb-left', minx);
         $(link).attr('fb-top', miny);
 
-        let svg = '<svg preserveAspectRatio="none" width="' + w + '" height="' + h + '" style="width: 100%;height:100%;"><polygon points="';
-
-        $.each($(link).data('points'), function (k, pos) {
-            svg += (pos.x - minx) + ',' + (pos.y - miny) + ' ';
+        let svg = '<svg  preserveAspectRatio="none" viewBox="0 0 ' + w + ' ' + h + '" width="' + w + '" height="' + h + '" style="width: 100%;height:100%;"><polygon points="';
+        let corners = $('<div class="corners"></div>');
+        let clippath = [];
+
+        $.each(polygon, function (k, pos) {
+            let point = {
+                x: $this.linkeditor.utils.roundDimension(pos.x - minx),
+                y: $this.linkeditor.utils.roundDimension(pos.y - miny)
+            };
+            let cx = (point.x / w) * 100;
+            let cy = (point.y / h) * 100;
+            svg += point.x + ',' + point.y + ' ';
+            normalizedPolygon.push(point);
+            $(corners).append('<div class="poly" data-point="' + k + '" style="left:calc(' + cx + '% - 4px);top:calc(' + cy + '% - 4px);"></div>');
+
+            let cs = 1.05;
+            let ccx = ((cx / 100) * 100 * cs) - ((cs * 100) - 100) / 2;
+            let ccy = ((cy / 100) * 100 * cs) - ((cs * 100) - 100) / 2;
+            clippath.push([ccx, ccy]);
         });
-        svg += '" style="fill:currentColor;stroke:currentColor;stroke-width:1;fill-opacity:0.25;"></svg>';
+
+
+        svg += '" fill="currentColor" stroke="currentColor" stroke-width="1.25" fill-opacity="0.25" vector-effect="non-scaling-stroke"></svg>';
+        $(link).css('clip-path', 'polygon(' + this.getConvexClipPath(clippath) + ')');
         $(link).html(svg);
+        $(link).append(corners);
+        let jsonPolygon = JSON.stringify(normalizedPolygon);
+        $(link).attr('fb-polygon', jsonPolygon);
+        if (updateData) {
+            this.updateLinkData($(link).attr('fb-uid'), {
+                left: minx,
+                top: miny,
+                width: w,
+                height: h,
+                polygon: jsonPolygon
+            });
+        }
+    },
 
 
+    getConvexClipPath: function (points) {
+        const grahamscan = new GrahamScan();
+        grahamscan.setPoints(points);
+        let hull = grahamscan.getHull();
+        let res = [];
+        $.each(hull, function (k, p) {
+            res.push(p[0] + '% ' + p[1] + '%');
+        });
+        return res.join(',');
+    },
+
+    startMovePolygonPoint: function (idx) {
+        let link = this.getFirstLinkInSelection();
+        let polygon = this.getOffsetPolygon(link);
+        this.movePolygonPointPos = {
+            x: this.linkeditor.mx,
+            y: this.linkeditor.my,
+            ox: polygon[idx].x,
+            oy: polygon[idx].y,
+            index: idx
+        };
+        this.setDragOrigValues();
     },
 
-    endPolygonLine: function () {
+    stopMovePolygonPoint: function () {
+        if (this.movePolygonPointPos === null) {
+            return;
+        }
+        var $this = this;
+        this.linkeditor.form.updateLinkForm();
+        this.movePolygonPointPos = null;
+        this.updatePolygonLinks();
+        this.linkeditor.hasChanged();
+    },
 
+    movePolygonPoint: function () {
+        if (this.movePolygonPointPos === null) {
+            return;
+        }
+        let $this = this;
+        let f = 1 / (this.linkeditor.fs * this.linkeditor.zoom.zoom);
+        let dx = (this.linkeditor.mx - this.movePolygonPointPos.x) * f;
+        let dy = (this.linkeditor.my - this.movePolygonPointPos.y) * f;
+
+
+        let link = this.getFirstLinkInSelection();
+        let polygon = this.getOffsetPolygon(link);
+        polygon[this.movePolygonPointPos.index].x = this.movePolygonPointPos.ox + dx;
+        polygon[this.movePolygonPointPos.index].y = this.movePolygonPointPos.oy + dy;
+
+        this.updatePolygonLink(link, polygon);
+        $(link).attr('fb-update', '1');
+
+        this.linkeditor.updateFBElements(false);
+        this.linkeditor.save.hasChanged();
     },
 };
 
index ff5cb9a121e8708af6c5e326c30db81c314393b2..52bed531b4af9f80b9d467c8d07982a542c566cd 100644 (file)
@@ -9,7 +9,7 @@
         border: currentColor solid 1px
         cursor: cell
 
-        #linkeditor-main.dropfile  &.dropfile
+        #linkeditor-main.dropfile &.dropfile
             border-style: dashed
 
         &.dropfile.dragging
             .corners
                 visibility: visible
 
+        &[fb-polygon^="["]
+            background-color: transparent !important
+            border-style: none
+
+            &.pendingPolygonCreate
+                clip-path: none !important
+
         .corners
             visibility: hidden
             position: absolute
@@ -83,5 +90,9 @@
                 &.n, &.s
                     left: calc(50% - 4px)
 
+                &.poly
+                    cursor: crosshair
+                    border-radius: 50%
+
     position: relative
     z-index: 500
index 4b45531ed61c6c74fc76eeefb1b36f6115046473..b14fc69f60101841f28de202fca18f7356980686 100644 (file)
                        data-tooltip="{{__('Rétablir la derière modification')}} (Ctrl+Maj+Z)"
                        data-key="ctrl+shift+z" data-key-skipintextfields></a>
                     <div class="separator"></div>
-                    <a href="#" data-action="links.toggleShape" data-icon="shape-rectangle"
-                       data-action-args="rectangle" data-tooltip="{{__('Liens rectangles')}}"></a>
-                    <a href="#" data-action="links.toggleShape" data-icon="shape-polygon"
-                       data-action-args="polygon" data-tooltip="{{__('Liens polygones')}}"></a>
-                    <div class="separator"></div>
                     <a href="#" data-action="zoom.reset" data-icon="zoom-reset"
                        data-tooltip="{{__('Réinitialiser le zoom')}} (Esc)" data-key="esc, ctrl+0, ctrl+numpad0"></a>
                     <div class="separator"></div>