From f75580611bb75e0b28a0dab2038c3b481c7d314e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B0=D0=BD=D0=B4=D1=80?= Date: Fri, 18 Jan 2019 09:55:08 +0400 Subject: [PATCH] CAD Placemark Extension --- editor/extensions/ext-locale/placemark/en.js | 40 ++ editor/extensions/ext-placemark.js | 486 +++++++++++++++++++ editor/extensions/placemark-icons.xml | 138 ++++++ editor/extensions/placemark.png | Bin 0 -> 2349 bytes 4 files changed, 664 insertions(+) create mode 100644 editor/extensions/ext-locale/placemark/en.js create mode 100644 editor/extensions/ext-placemark.js create mode 100644 editor/extensions/placemark-icons.xml create mode 100644 editor/extensions/placemark.png diff --git a/editor/extensions/ext-locale/placemark/en.js b/editor/extensions/ext-locale/placemark/en.js new file mode 100644 index 00000000..eb216588 --- /dev/null +++ b/editor/extensions/ext-locale/placemark/en.js @@ -0,0 +1,40 @@ +export default { + name: 'star', + langList: [ + {id: 'nomarker', title: 'No Marker'}, + {id: 'leftarrow', title: 'Left Arrow'}, + {id: 'rightarrow', title: 'Right Arrow'}, + {id: 'forwardslash', title: 'Forward Slash'}, + {id: 'reverseslash', title: 'Reverse Slash'}, + {id: 'verticalslash', title: 'Vertical Slash'}, + {id: 'box', title: 'Box'}, + {id: 'star', title: 'Star'}, + {id: 'xmark', title: 'X'}, + {id: 'triangle', title: 'Triangle'}, + {id: 'mcircle', title: 'Circle'}, + {id: 'leftarrow_o', title: 'Open Left Arrow'}, + {id: 'rightarrow_o', title: 'Open Right Arrow'}, + {id: 'box_o', title: 'Open Box'}, + {id: 'star_o', title: 'Open Star'}, + {id: 'triangle_o', title: 'Open Triangle'}, + {id: 'mcircle_o', title: 'Open Circle'} + ], + buttons: [ + { + title: 'Placemark Tool' + } + ], + contextTools: [ + { + title: 'Select Place marker type' + }, + { + title: 'Text on separated with ; ', + label: 'Text' + }, + { + title: 'Font for text', + label: '' + } + ] +}; diff --git a/editor/extensions/ext-placemark.js b/editor/extensions/ext-placemark.js new file mode 100644 index 00000000..ac537637 --- /dev/null +++ b/editor/extensions/ext-placemark.js @@ -0,0 +1,486 @@ +/** + * ext-placemark.js + * + * + * @copyright 2010 CloudCanvas, Inc. All rights reserved + * + */ +export default { + name: 'placemark', + async init (S) { + const svgEditor = this; + const svgCanvas = svgEditor.canvas; + const addElem = svgCanvas.addSVGElementFromJson; + const {$, importLocale} = S; // {svgcontent}, + let + selElems, + // editingitex = false, + // svgdoc = S.svgroot.parentNode.ownerDocument, + started, + newPM; + // edg = 0, + // newFOG, newFOGParent, newDef, newImageName, newMaskID, + // undoCommand = 'Not image', + // modeChangeG, ccZoom, wEl, hEl, wOffset, hOffset, ccRgbEl, brushW, brushH; + const strings = await importLocale(); + const markerTypes = { + nomarker: {}, + forwardslash: + {element: 'path', attr: {d: 'M30,100 L70,0'}}, + reverseslash: + {element: 'path', attr: {d: 'M30,0 L70,100'}}, + verticalslash: + {element: 'path', attr: {d: 'M50,0 L50,100'}}, + xmark: + {element: 'path', attr: {d: 'M20,80 L80,20 M80,80 L20,20'}}, + leftarrow: + {element: 'path', attr: {d: 'M0,50 L100,90 L70,50 L100,10 Z'}}, + rightarrow: + {element: 'path', attr: {d: 'M100,50 L0,90 L30,50 L0,10 Z'}}, + box: + {element: 'path', attr: {d: 'M20,20 L20,80 L80,80 L80,20 Z'}}, + star: + {element: 'path', attr: {d: 'M10,30 L90,30 L20,90 L50,10 L80,90 Z'}}, + mcircle: + {element: 'circle', attr: {r: 30, cx: 50, cy: 50}}, + triangle: + {element: 'path', attr: {d: 'M10,80 L50,20 L80,80 Z'}}, + }; + + // duplicate shapes to support unfilled (open) marker types with an _o suffix + ['leftarrow', 'rightarrow', 'box', 'star', 'mcircle', 'triangle'].forEach((v) => { + markerTypes[v + '_o'] = markerTypes[v]; + }); + + + /** + * + * @param {boolean} on + * @returns {undefined} + */ + function showPanel (on) { + $('#placemark_panel').toggle(on); + } + + /** + * @param {Element} elem - A graphic element will have an attribute like marker-start + * @param {"marker-start"|"marker-mid"|"marker-end"} attr + * @returns {Element} The marker element that is linked to the graphic element + */ + function getLinked (elem, attr) { + if (!elem) { return null; } + const str = elem.getAttribute(attr); + if (!str) { return null; } + const m = str.match(/\(#(.*)\)/); + if (!m || m.length !== 2) { + return null; + } + return svgCanvas.getElem(m[1]); + } + + /** + * + * @param {string} attr + * @param {string|Float} val + * @returns {undefined} + */ + function setAttr (attr, val) { + svgCanvas.changeSelectedAttribute(attr, val); + svgCanvas.call('changed', selElems); + } + /** + * @param {string} id + * @param {""|"\\nomarker"|"nomarker"|"leftarrow"|"rightarrow"|"textmarker"|"textmarker_top"|"textmarker_bottom"|"forwardslash"|"reverseslash"|"verticalslash"|"box"|"star"|"xmark"|"triangle"|"mcircle"} val + * @returns {undefined} + */ + function addMarker (id, val) { + let marker = svgCanvas.getElem(id); + if (marker) { return undefined; } + //console.log(id); + if (val === '' || val === 'nomarker') { return undefined; } + const color = svgCanvas.getColor('stroke'); + // NOTE: Safari didn't like a negative value in viewBox + // so we use a standardized 0 0 100 100 + // with 50 50 being mapped to the marker position + const scale = 2;//parseFloat($('#marker_size').val()); + const strokeWidth = 10; + let refX = 50; + let refY = 50; + let viewBox = '0 0 100 100'; + let markerWidth = 5*scale; + let markerHeight = 5*scale; + let seType = val; + + if (!markerTypes[seType]) { return undefined; } // an unknown type! + //positional markers(arrows) at end of line + if (seType.includes('left'))refX=0; + if (seType.includes('right'))refX=100; + + // create a generic marker + marker = addElem({ + element: 'marker', + attr: { + id, + markerUnits: 'strokeWidth', + orient: 'auto', + style: 'pointer-events:none', + class: seType + } + }); + + const mel = addElem(markerTypes[seType]); + const fillcolor = (seType.substr(-2) === '_o') + ? 'none' + : color; + + mel.setAttribute('fill', fillcolor); + mel.setAttribute('stroke', color); + mel.setAttribute('stroke-width', strokeWidth); + marker.append(mel); + + marker.setAttribute('viewBox', viewBox); + marker.setAttribute('markerWidth', markerWidth); + marker.setAttribute('markerHeight', markerHeight); + marker.setAttribute('refX', refX); + marker.setAttribute('refY', refY); + svgCanvas.findDefs().append(marker); + + return marker; + } + /** + * + * @returns {undefined} + */ + function setMarker (el, val) { + const markerName = 'marker-start'; + const marker = getLinked(el, markerName); + if (marker) { $(marker).remove(); } + el.removeAttribute(markerName); + if (val === 'nomarker') { + svgCanvas.call('changed', [el]); + return; + } + // Set marker on element + const id = 'placemark_marker_' + el.id; + addMarker(id, val); + el.setAttribute(markerName, 'url(#' + id + ')'); + svgCanvas.call('changed', [el]); + } + + /** + * Called when the main system modifies an object. This routine changes + * the associated markers to be the same color. + * @param {Element} elem + * @returns {undefined} + */ + function colorChanged (elem) { + const color = elem.getAttribute('stroke'); + const marker = getLinked(elem, 'marker-start'); + if (!marker) { return; } + if (!marker.attributes.class) { return; } // not created by this extension + const ch = marker.lastElementChild; + if (!ch) { return; } + const curfill = ch.getAttribute('fill'); + const curstroke = ch.getAttribute('stroke'); + if (curfill && curfill !== 'none') { ch.setAttribute('fill', color); } + if (curstroke && curstroke !== 'none') { ch.setAttribute('stroke', color); } + } + + /** + * Called when the main system creates or modifies an object. + * Its primary purpose is to create new markers for cloned objects. + * @param {Element} el + * @returns {undefined} + */ + function updateReferences (el) { + const id = placemark_marker_ + el.id; + const markerName = 'marker-start'; + const marker = getLinked(el, markerName); + if (!marker || !marker.attributes.class) { return; } // not created by this extension + const url = el.getAttribute(markerName); + if (url) { + const len = el.id.length; + const linkid = url.substr(-len - 1, len); + if (el.id !== linkid) { + const val = $('#placemark_marker').attr('value') || 'leftarrow'; + addMarker(id, val); + svgCanvas.changeSelectedAttribute(markerName, 'url(#' + id + ')'); + if (el.tagName === 'line' && pos === 'mid') { el = convertline(el); } + svgCanvas.call('changed', selElems); + } + } + } + /** + * @param {Event} ev + * @returns {Promise} Resolves to `undefined` + */ + async function setArrowFromButton (ev) { + const parts = this.id.split('_'); + let val = parts[2]; + if (parts[3]) { val += '_' + parts[3]; } + $("#placemark_marker").attr("value",val); + } + + /** + * @param {"nomarker"|"leftarrow"|"rightarrow"|"textmarker"|"forwardslash"|"reverseslash"|"verticalslash"|"box"|"star"|"xmark"|"triangle"|"mcircle"} id + * @returns {undefined} + */ + function getTitle (id) { + const {langList} = strings; + const item = langList.find((itm) => { + return itm.id === id; + }); + return item ? item.title : id; + } + + /** + * Build the toolbar button array from the marker definitions. + * @returns {module:SVGEditor.Button[]} + */ + function addMarkerButtons (buttons) { + Object.keys(markerTypes).forEach(function (id) { + const title = getTitle(String(id)); + buttons.push({ + id: 'placemark_marker_' + id, + svgicon: id, + icon: svgEditor.curConfig.extIconsPath + 'markers-' + id + '.png', + title, + type: 'context', + events: {click: setArrowFromButton}, + panel: 'placemark_panel', + list: 'placemark_marker', + isDefault: id=='leftarrow' + }); + }); + return buttons; + } + + const buttons = [{ + id: 'tool_placemark', + icon: svgEditor.curConfig.extIconsPath + 'placemark.png', + type: 'mode', + position: 12, + events: { + click () { + showPanel(true); + svgCanvas.setMode('placemark'); + } + } + }]; + const contextTools = [ + { + type: 'button-select', + panel: 'placemark_panel', + id: 'placemark_marker', + colnum: 3, + events: {change: setArrowFromButton} + }, + { + type: 'input', + panel: 'placemark_panel', + id: 'placemarkText', + size: 20, + defval: '', + events: { + change () { + //setAttr('text', this.value); + } + } + }, { + type: 'input', + panel: 'placemark_panel', + id: 'placemarkFont', + size: 10, + defval: 'Arial 10', + events: { + change () { + //setAttr('untext', this.value); + } + } + } + ]; + + return { + name: strings.name, + svgicons: svgEditor.curConfig.extIconsPath + 'placemark-icons.xml', + buttons: addMarkerButtons(strings.buttons.map((button, i)=> Object.assign(buttons[i], button))), + context_tools: strings.contextTools.map((contextTool, i) => Object.assign(contextTools[i], contextTool)), + callback () { + $('#placemark_panel').hide(); + // const endChanges = function(){}; + }, + mouseDown (opts) { + const rgb = svgCanvas.getColor('fill'); + // const ccRgbEl = rgb.substring(1, rgb.length); + const sRgb = svgCanvas.getColor('stroke'); + // const ccSRgbEl = sRgb.substring(1, rgb.length); + const sWidth = svgCanvas.getStrokeWidth(); + + if (svgCanvas.getMode() === 'placemark') { + started = true; + const id = svgCanvas.getNextId(); + const items = $('#placemarkText').val().split(";"); + let font = $('#placemarkFont').val().split(" "); + const fontSize = parseInt(font.pop()); + font = font.join(" ") + let x0 = opts.start_x+10, y0 = opts.start_y+10,maxlen=0; + let children = [{ + element:'line', + attr:{ + id: id+'_pline_0', + fill:"none", + stroke: sRgb, + "stroke-width": sWidth, + "stroke-linecap":"round", + x1: opts.start_x, + y1: opts.start_y, + x2: x0, + y2: y0, + } + }]; + items.forEach((i,n)=>{ + maxlen=Math.max(maxlen,i.length) + children.push({ + element:'line', + attr:{ + id: id+'_tline_'+n, + fill:"none", + stroke: sRgb, + "stroke-width": sWidth, + "stroke-linecap":"round", + x1: x0, + y1: y0+(fontSize+6)*n, + x2: x0+i.length*fontSize*0.6+fontSize, + y2: y0+(fontSize+6)*n, + } + }); + children.push({ + element:'text', + attr:{ + id: id+'_txt_'+n, + fill: sRgb, + stroke: "none", + "stroke-width": 0, + x: x0+3, + y: y0-3+(fontSize+6)*n, + "font-family":font, + "font-size":fontSize, + "text-anchor":"start", + }, + children:[i] + }) + }); + if(items.length>0)children.push({ + element:'line', + attr:{ + id: id+'_vline_0', + fill:"none", + stroke: sRgb, + "stroke-width": sWidth, + "stroke-linecap":"round", + x1: x0, + y1: y0, + x2: x0, + y2: y0+(fontSize+6)*(items.length-1), + } + }); + newPM = svgCanvas.addSVGElementFromJson({ + element: 'g', + attr: { + id: id, + shape: 'placemark', + fontSize:fontSize, + maxlen:maxlen, + lines:items.length, + px:opts.start_x, + py:opts.start_y, + }, + children: children, + }); + setMarker(newPM.firstChild,$('#placemark_marker').attr('value') || 'leftarrow'); + return { + started: true + }; + } + return undefined; + }, + mouseMove (opts) { + if (!started) { + return undefined; + } + console.log() + if (svgCanvas.getMode() === 'placemark') { + const scale = 1/svgCanvas.getZoom(); + let x = opts.mouse_x*scale; + let y = opts.mouse_y*scale; + const {fontSize,maxlen,lines,px,py} = $(newPM).attr(['fontSize','maxlen','lines','px','py']); + $(newPM).children().each((n,i)=>{ + const type = i.id.split("_"); + const y0 = y+(fontSize+6)*type[3],x0 = x+maxlen*fontSize*0.6+fontSize; + const nx = (x + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + T + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + T + + + + + + + + T + + + + + diff --git a/editor/extensions/placemark.png b/editor/extensions/placemark.png new file mode 100644 index 0000000000000000000000000000000000000000..5b15c26ce624857b2ea878540467b75d296b4832 GIT binary patch literal 2349 zcmV+|3DWk7P)u8F_ne_N`7F&1zV^9^7( z;NzCc9K`}$z)3NG0+#^i0e?hIwNC?Szzrn*0yqeGs(qr5wnTZr4YczRa1Ze9|8vhFL~67MN8IzfZGIkA%O2)!12I0 z0d-@QAvGZ2W(9r%<_X~S3vdQ-H83@xeykFx0Rbm^1lTT+hpN48R-dm7!6R1n(7*t= zqk$_u>bZ(40&^>H67a*2ykhkn4Ge%=8F;@WZA}4A25xplWlCxyvKas;O?(b4U9w8n zGVVsnY$LlQa2g>8l&q3#fMbDwO6EKA?2^Fk4%}UmMl@X>0^C;;Ke5@+EP%!p;Q1wR zQ-+)4!%NN8%`dl$N#GU$r10c#Sqwe^j&zJBxd`u-9GqU&tX#+D_Gm@{Etb}HOw}R0 z#I-Jdg}P{iq~-j&g=+p^=nCLCkMf~KHQinwP+5zli>!Lb7`X%3O770hV+;tjgXssN+hSE)}@>ft>;QZh53g%gYYuT>g_n zP9Nnl81d(&0_Uqs<(^#TsHB+XIwi*~ePwHV`9^`0a?hdcFs6&k!21Bl>BP`XQQVtZL)Hspm?peHEC^QiGCd^(@rhxpoIm zNpVLSLm;r>6975fNs>b@cPo{O?!EcQjy4As{<2B-PL8`4Ek!5-XKATw;MANl$CN>4 zliUNj2}LLZ*8p&H1KKgrPOnY-1Gg1GE&|s8aB5O3rq_Wvt@d@kTp=+{omtd(;D*jQ zDe&?K7m*vFY-yY1lxrcAW$*e7TxmHc!N?S-7;5i|=;ee;LuSJAh z1lJTNgMGaR3ObOfRjZcOMHeTpnWN9Ht)`~8fe3alSo?G$whG8DW)BD zoh6Ty&_G*&lStY~)I~SigiTVkcP+JXNwJjfptJ{@qP=VBRGAb@=j?PTK(u!m#-UA9 zKysa%w0 z3TW>u;0EBFl(Ig&#G-C~xn1lEa0>q@iN=?9FdxH6)(hn&9(C|5)W@y>mv)azUMF?i z?FCk$P+nqF2fw^NO?%!I;I;)GE7YTz3+)0ZM>0vFyu_nQetIcn3PifU(thD)>O@z7 z)Afov*{TcQirTm*O#+hrtvg>{0~=Q*zimvI%YMOh>5r4T0$jjg>r@quHeF<%BpofY zZ^fPdJObAae6@XZrX@<}w{_Z~+G?ffHE_DjTgkDygiQ;MK7Hz}x4`KFZ(V4*n0fKP zH|gnp`OIf;fpguLqEEx<6ES_D-U8R>!~1&iB5)lYei66`T)Q+7r%DTtt~lU*2d=k~ zlfDC|?IQadd^)+5n#mnUTsusH0^d57tDOsx@+##5P3?M+x$h&+`}K(<&Rb>es1Si` z2M*p+bOpG+hrD_V+!E$m_rZ5WWwe*C_=CFM%B-_OS4Nz4qLc;QaV7Ac=^<#n%sHv^ zWp1Lam~+yE)=Ox!w~><)bkk}Uz1G(v?+Z%sJ`kwocz2@ZL_9 zeGf8^6mSz0Uu@KE=DKozO2$HAtV!uB@@;~;`Hd8CHbx4ADUI9C!B@Fe%vY1gd8`2I z!=1gDs~)k9R=<`D$gX1 TWGEqX00000NkvXXu0mjf9>ilt literal 0 HcmV?d00001