From: Vincent Vanwaelscappel Date: Tue, 13 Jun 2023 12:24:01 +0000 (+0200) Subject: wip #6020 @4 X-Git-Url: http://git.cubedesigners.com/?a=commitdiff_plain;h=81a254808008bb747ca7955f25514cd275a55a69;p=fluidbook-toolbox.git wip #6020 @4 --- diff --git a/.editorconfig b/.editorconfig index 6537ca467..2436a5049 100644 --- a/.editorconfig +++ b/.editorconfig @@ -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 diff --git a/app/Fluidbook/Link/LinksData.php b/app/Fluidbook/Link/LinksData.php index b119916b3..ea2d137a2 100644 --- a/app/Fluidbook/Link/LinksData.php +++ b/app/Fluidbook/Link/LinksData.php @@ -51,6 +51,7 @@ class LinksData 'group' => __('Groupe'), 'zindex' => __('Profondeur'), 'pdfjs' => __('Mode PDFJS'), + 'polygon' => __('Tracé du polygone'), ); $comments = array(); diff --git a/app/SubForms/Link/Base.php b/app/SubForms/Link/Base.php index 20b94614f..a5f7e7697 100644 --- a/app/SubForms/Link/Base.php +++ b/app/SubForms/Link/Base.php @@ -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); } diff --git a/package-lock.json b/package-lock.json index 793ac3c75..91dcc5624 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", @@ -1835,6 +1836,11 @@ "@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", diff --git a/package.json b/package.json index f44c93fac..bd93cc6be 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/resources/linkeditor/js/linkeditor.js b/resources/linkeditor/js/linkeditor.js index dee434660..9584e91b6 100644 --- a/resources/linkeditor/js/linkeditor.js +++ b/resources/linkeditor/js/linkeditor.js @@ -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(); }, diff --git a/resources/linkeditor/js/linkeditor.links.js b/resources/linkeditor/js/linkeditor.links.js index 64b45f923..1a2e4b4c3 100644 --- a/resources/linkeditor/js/linkeditor.links.js +++ b/resources/linkeditor/js/linkeditor.links.js @@ -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('
') } @@ -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 = ''); + 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('
'); + + 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 += '" fill="currentColor" stroke="currentColor" stroke-width="1.25" fill-opacity="0.25" vector-effect="non-scaling-stroke">'; + $(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(); }, }; diff --git a/resources/linkeditor/style/inc/_links.sass b/resources/linkeditor/style/inc/_links.sass index ff5cb9a12..52bed531b 100644 --- a/resources/linkeditor/style/inc/_links.sass +++ b/resources/linkeditor/style/inc/_links.sass @@ -9,7 +9,7 @@ border: currentColor solid 1px cursor: cell - #linkeditor-main.dropfile &.dropfile + #linkeditor-main.dropfile &.dropfile border-style: dashed &.dropfile.dragging @@ -26,6 +26,13 @@ .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 diff --git a/resources/views/fluidbook_publication/link_editor.blade.php b/resources/views/fluidbook_publication/link_editor.blade.php index 4b45531ed..b14fc69f6 100644 --- a/resources/views/fluidbook_publication/link_editor.blade.php +++ b/resources/views/fluidbook_publication/link_editor.blade.php @@ -168,11 +168,6 @@ data-tooltip="{{__('Rétablir la derière modification')}} (Ctrl+Maj+Z)" data-key="ctrl+shift+z" data-key-skipintextfields>
- - -