UNPKG

jointjs

Version:

JavaScript diagramming library

372 lines (323 loc) 10.4 kB
import { Element } from '../dia/Element.mjs'; import { ElementView } from '../dia/ElementView.mjs'; import { omit, assign, sanitizeHTML, merge, has, breakText, setByPath } from '../util/index.mjs'; import { env } from '../env/index.mjs'; export const Generic = Element.define('basic.Generic', { attrs: { '.': { fill: '#ffffff', stroke: 'none' } } }); export const Rect = Generic.define('basic.Rect', { attrs: { 'rect': { fill: '#ffffff', stroke: '#000000', width: 100, height: 60 }, 'text': { fill: '#000000', text: '', 'font-size': 14, 'ref-x': .5, 'ref-y': .5, 'text-anchor': 'middle', 'y-alignment': 'middle', 'font-family': 'Arial, helvetica, sans-serif' } } }, { markup: '<g class="rotatable"><g class="scalable"><rect/></g><text/></g>' }); export const TextView = ElementView.extend({ presentationAttributes: ElementView.addPresentationAttributes({ // The element view is not automatically re-scaled to fit the model size // when the attribute 'attrs' is changed. attrs: ['SCALE'] }), confirmUpdate: function() { var flags = ElementView.prototype.confirmUpdate.apply(this, arguments); if (this.hasFlag(flags, 'SCALE')) { this.resize(); flags = this.removeFlag(flags, 'SCALE'); } return flags; } }); export const Text = Generic.define('basic.Text', { attrs: { 'text': { 'font-size': 18, fill: '#000000' } } }, { markup: '<g class="rotatable"><g class="scalable"><text/></g></g>', }); export const Circle = Generic.define('basic.Circle', { size: { width: 60, height: 60 }, attrs: { 'circle': { fill: '#ffffff', stroke: '#000000', r: 30, cx: 30, cy: 30 }, 'text': { 'font-size': 14, text: '', 'text-anchor': 'middle', 'ref-x': .5, 'ref-y': .5, 'y-alignment': 'middle', fill: '#000000', 'font-family': 'Arial, helvetica, sans-serif' } } }, { markup: '<g class="rotatable"><g class="scalable"><circle/></g><text/></g>', }); export const Ellipse = Generic.define('basic.Ellipse', { size: { width: 60, height: 40 }, attrs: { 'ellipse': { fill: '#ffffff', stroke: '#000000', rx: 30, ry: 20, cx: 30, cy: 20 }, 'text': { 'font-size': 14, text: '', 'text-anchor': 'middle', 'ref-x': .5, 'ref-y': .5, 'y-alignment': 'middle', fill: '#000000', 'font-family': 'Arial, helvetica, sans-serif' } } }, { markup: '<g class="rotatable"><g class="scalable"><ellipse/></g><text/></g>', }); export const Polygon = Generic.define('basic.Polygon', { size: { width: 60, height: 40 }, attrs: { 'polygon': { fill: '#ffffff', stroke: '#000000' }, 'text': { 'font-size': 14, text: '', 'text-anchor': 'middle', 'ref-x': .5, 'ref-dy': 20, 'y-alignment': 'middle', fill: '#000000', 'font-family': 'Arial, helvetica, sans-serif' } } }, { markup: '<g class="rotatable"><g class="scalable"><polygon/></g><text/></g>', }); export const Polyline = Generic.define('basic.Polyline', { size: { width: 60, height: 40 }, attrs: { 'polyline': { fill: '#ffffff', stroke: '#000000' }, 'text': { 'font-size': 14, text: '', 'text-anchor': 'middle', 'ref-x': .5, 'ref-dy': 20, 'y-alignment': 'middle', fill: '#000000', 'font-family': 'Arial, helvetica, sans-serif' } } }, { markup: '<g class="rotatable"><g class="scalable"><polyline/></g><text/></g>', }); export const Image = Generic.define('basic.Image', { attrs: { 'text': { 'font-size': 14, text: '', 'text-anchor': 'middle', 'ref-x': .5, 'ref-dy': 20, 'y-alignment': 'middle', fill: '#000000', 'font-family': 'Arial, helvetica, sans-serif' } } }, { markup: '<g class="rotatable"><g class="scalable"><image/></g><text/></g>', }); export const Path = Generic.define('basic.Path', { size: { width: 60, height: 60 }, attrs: { 'path': { fill: '#ffffff', stroke: '#000000' }, 'text': { 'font-size': 14, text: '', 'text-anchor': 'middle', 'ref': 'path', 'ref-x': .5, 'ref-dy': 10, fill: '#000000', 'font-family': 'Arial, helvetica, sans-serif' } } }, { markup: '<g class="rotatable"><g class="scalable"><path/></g><text/></g>', }); export const Rhombus = Path.define('basic.Rhombus', { attrs: { 'path': { d: 'M 30 0 L 60 30 30 60 0 30 z' }, 'text': { 'ref-y': .5, 'ref-dy': null, 'y-alignment': 'middle' } } }); const svgForeignObjectSupported = env.test('svgforeignobject'); export const TextBlock = Generic.define('basic.TextBlock', { // see joint.css for more element styles attrs: { rect: { fill: '#ffffff', stroke: '#000000', width: 80, height: 100 }, text: { fill: '#000000', 'font-size': 14, 'font-family': 'Arial, helvetica, sans-serif' }, '.content': { text: '', 'ref-x': .5, 'ref-y': .5, 'y-alignment': 'middle', 'x-alignment': 'middle' } }, content: '' }, { markup: [ '<g class="rotatable">', '<g class="scalable"><rect/></g>', svgForeignObjectSupported ? '<foreignObject class="fobj"><body xmlns="http://www.w3.org/1999/xhtml"><div class="content"/></body></foreignObject>' : '<text class="content"/>', '</g>' ].join(''), initialize: function() { this.listenTo(this, 'change:size', this.updateSize); this.listenTo(this, 'change:content', this.updateContent); this.updateSize(this, this.get('size')); this.updateContent(this, this.get('content')); Generic.prototype.initialize.apply(this, arguments); }, updateSize: function(cell, size) { // Selector `foreignObject' doesn't work across all browsers, we're using class selector instead. // We have to clone size as we don't want attributes.div.style to be same object as attributes.size. this.attr({ '.fobj': assign({}, size), div: { style: assign({}, size) } }); }, updateContent: function(cell, content) { if (svgForeignObjectSupported) { // Content element is a <div> element. this.attr({ '.content': { html: sanitizeHTML(content) } }); } else { // Content element is a <text> element. // SVG elements don't have innerHTML attribute. this.attr({ '.content': { text: content } }); } }, // Here for backwards compatibility: setForeignObjectSize: function() { this.updateSize.apply(this, arguments); }, // Here for backwards compatibility: setDivContent: function() { this.updateContent.apply(this, arguments); } }); // TextBlockView implements the fallback for IE when no foreignObject exists and // the text needs to be manually broken. export const TextBlockView = ElementView.extend({ presentationAttributes: svgForeignObjectSupported ? ElementView.prototype.presentationAttributes : ElementView.addPresentationAttributes({ content: ['CONTENT'], size: ['CONTENT'] }), initFlag: ['RENDER', 'CONTENT'], confirmUpdate: function() { var flags = ElementView.prototype.confirmUpdate.apply(this, arguments); if (this.hasFlag(flags, 'CONTENT')) { this.updateContent(this.model); flags = this.removeFlag(flags, 'CONTENT'); } return flags; }, update: function(_, renderingOnlyAttrs) { var model = this.model; if (!svgForeignObjectSupported) { // Update everything but the content first. var noTextAttrs = omit(renderingOnlyAttrs || model.get('attrs'), '.content'); ElementView.prototype.update.call(this, model, noTextAttrs); if (!renderingOnlyAttrs || has(renderingOnlyAttrs, '.content')) { // Update the content itself. this.updateContent(model, renderingOnlyAttrs); } } else { ElementView.prototype.update.call(this, model, renderingOnlyAttrs); } }, updateContent: function(cell, renderingOnlyAttrs) { // Create copy of the text attributes var textAttrs = merge({}, (renderingOnlyAttrs || cell.get('attrs'))['.content']); textAttrs = omit(textAttrs, 'text'); // Break the content to fit the element size taking into account the attributes // set on the model. var text = breakText(cell.get('content'), cell.get('size'), textAttrs, { // measuring sandbox svg document svgDocument: this.paper.svg }); // Create a new attrs with same structure as the model attrs { text: { *textAttributes* }} var attrs = setByPath({}, '.content', textAttrs, '/'); // Replace text attribute with the one we just processed. attrs['.content'].text = text; // Update the view using renderingOnlyAttributes parameter. ElementView.prototype.update.call(this, cell, attrs); } });