refactor!: move codecs to dedicated files and reduce side effects (#289)
Previously, codec classes were defined and registered in the same file as the class they targeted. This prevented efficient tree-shaking: as soon as the target class was imported (e.g. `StyleSheet`), the corresponding codec was also imported (`StyleSheetCodec`). As a result, a large amount of codec code (and dependent code) was present in applications incorporating maxGraph, even if they didn't use the codec function. As there are currently many problems with the codec function that make it almost unusable, this was a great waste. Codecs are no longer registered by default, which reduces side effects and coupling. It also greatly improves tree-shaking. For example, in the `ts-example` application included in this repository which doesn't use the "codec" feature, the size of the minified `maxGraph` chunk goes from `568.58 kB` to `467.90 kB`, which corresponds to a size reduction of `17.7%`! BREAKING CHANGE: Codecs supplied by `maxGraph` are no longer registered by default. They MUST be registered before performing an `encode` or `decode`. You can use one of the following functions to register codecs: - `registerAllCodecs` - `registerCoreCodecs` - `registerEditorCodecs`development
parent
871e1c37d5
commit
f9b2d7d19a
|
@ -1,5 +1,14 @@
|
|||
# `maxGraph` Change Log
|
||||
|
||||
# UNRELEASED
|
||||
|
||||
**Breaking Changes**
|
||||
- Codecs supplied by `maxGraph` are no longer registered by default. They **MUST** be registered before performing an `encode` or `decode`.
|
||||
You can use one of the following functions to register codecs:
|
||||
- `registerAllCodecs`
|
||||
- `registerCoreCodecs`
|
||||
- `registerEditorCodecs`
|
||||
|
||||
## 0.5.0
|
||||
|
||||
Release date: `2023-12-07`
|
||||
|
|
|
@ -16,7 +16,14 @@ limitations under the License.
|
|||
|
||||
import { describe, expect, test } from '@jest/globals';
|
||||
import { createGraphWithoutContainer } from '../utils';
|
||||
import { Cell, Codec, Geometry, GraphDataModel, Point } from '../../src';
|
||||
import {
|
||||
Cell,
|
||||
Codec,
|
||||
Geometry,
|
||||
GraphDataModel,
|
||||
Point,
|
||||
registerCoreCodecs,
|
||||
} from '../../src';
|
||||
import { getPrettyXml, parseXml } from '../../src/util/xmlUtils';
|
||||
|
||||
type ModelExportOptions = {
|
||||
|
@ -34,7 +41,9 @@ type ModelExportOptions = {
|
|||
*/
|
||||
class ModelXmlSerializer {
|
||||
// Include 'XML' in the class name as there were past discussions about supporting other format (JSON for example {@link https://github.com/maxGraph/maxGraph/discussions/60}).
|
||||
constructor(private dataModel: GraphDataModel) {}
|
||||
constructor(private dataModel: GraphDataModel) {
|
||||
registerCoreCodecs();
|
||||
}
|
||||
|
||||
import(xml: string): void {
|
||||
const doc = parseXml(xml);
|
||||
|
|
|
@ -43,7 +43,7 @@ import CellAttributeChange from '../view/undoable_changes/CellAttributeChange';
|
|||
import PrintPreview from '../view/other/PrintPreview';
|
||||
import mxClipboard from '../util/Clipboard';
|
||||
import MaxLog from '../gui/MaxLog';
|
||||
import { addLinkToHead, isNode } from '../util/domUtils';
|
||||
import { isNode } from '../util/domUtils';
|
||||
import { getViewXml, getXml } from '../util/xmlUtils';
|
||||
import { load, post, submit } from '../util/MaxXmlRequest';
|
||||
import PopupMenuHandler from '../view/handler/PopupMenuHandler';
|
||||
|
@ -54,9 +54,6 @@ import { CellStateStyle, MouseListenerSet } from '../types';
|
|||
import ConnectionHandler from '../view/handler/ConnectionHandler';
|
||||
import { show } from '../util/printUtils';
|
||||
import PanningHandler from '../view/handler/PanningHandler';
|
||||
import ObjectCodec from '../serialization/ObjectCodec';
|
||||
import CodecRegistry from '../serialization/CodecRegistry';
|
||||
import { getChildNodes } from '../util/domUtils';
|
||||
|
||||
/**
|
||||
* Installs the required language resources at class
|
||||
|
@ -2688,216 +2685,4 @@ export class Editor extends EventSource {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Codec for <Editor>s. This class is created and registered
|
||||
* dynamically at load time and used implicitly via <Codec>
|
||||
* and the <CodecRegistry>.
|
||||
*
|
||||
* Transient Fields:
|
||||
*
|
||||
* - modified
|
||||
* - lastSnapshot
|
||||
* - ignoredChanges
|
||||
* - undoManager
|
||||
* - graphContainer
|
||||
* - toolbarContainer
|
||||
*/
|
||||
export class EditorCodec extends ObjectCodec {
|
||||
constructor() {
|
||||
const __dummy: any = undefined;
|
||||
super(new Editor(__dummy), [
|
||||
'modified',
|
||||
'lastSnapshot',
|
||||
'ignoredChanges',
|
||||
'undoManager',
|
||||
'graphContainer',
|
||||
'toolbarContainer',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes the ui-part of the configuration node by reading
|
||||
* a sequence of the following child nodes and attributes
|
||||
* and passes the control to the default decoding mechanism:
|
||||
*
|
||||
* Child Nodes:
|
||||
*
|
||||
* stylesheet - Adds a CSS stylesheet to the document.
|
||||
* resource - Adds the basename of a resource bundle.
|
||||
* add - Creates or configures a known UI element.
|
||||
*
|
||||
* These elements may appear in any order given that the
|
||||
* graph UI element is added before the toolbar element
|
||||
* (see Known Keys).
|
||||
*
|
||||
* Attributes:
|
||||
*
|
||||
* as - Key for the UI element (see below).
|
||||
* element - ID for the element in the document.
|
||||
* style - CSS style to be used for the element or window.
|
||||
* x - X coordinate for the new window.
|
||||
* y - Y coordinate for the new window.
|
||||
* width - Width for the new window.
|
||||
* height - Optional height for the new window.
|
||||
* name - Name of the stylesheet (absolute/relative URL).
|
||||
* basename - Basename of the resource bundle (see {@link Resources}).
|
||||
*
|
||||
* The x, y, width and height attributes are used to create a new
|
||||
* <MaxWindow> if the element attribute is not specified in an add
|
||||
* node. The name and basename are only used in the stylesheet and
|
||||
* resource nodes, respectively.
|
||||
*
|
||||
* Known Keys:
|
||||
*
|
||||
* graph - Main graph element (see <Editor.setGraphContainer>).
|
||||
* title - Title element (see <Editor.setTitleContainer>).
|
||||
* toolbar - Toolbar element (see <Editor.setToolbarContainer>).
|
||||
* status - Status bar element (see <Editor.setStatusContainer>).
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```javascript
|
||||
* <ui>
|
||||
* <stylesheet name="css/process.css"/>
|
||||
* <resource basename="resources/app"/>
|
||||
* <add as="graph" element="graph"
|
||||
* style="left:70px;right:20px;top:20px;bottom:40px"/>
|
||||
* <add as="status" element="status"/>
|
||||
* <add as="toolbar" x="10" y="20" width="54"/>
|
||||
* </ui>
|
||||
* ```
|
||||
*/
|
||||
afterDecode(dec: Codec, node: Element, obj: any): any {
|
||||
// Assigns the specified templates for edges
|
||||
const defaultEdge = node.getAttribute('defaultEdge');
|
||||
|
||||
if (defaultEdge != null) {
|
||||
node.removeAttribute('defaultEdge');
|
||||
obj.defaultEdge = obj.templates[defaultEdge];
|
||||
}
|
||||
|
||||
// Assigns the specified templates for groups
|
||||
const defaultGroup = node.getAttribute('defaultGroup');
|
||||
|
||||
if (defaultGroup != null) {
|
||||
node.removeAttribute('defaultGroup');
|
||||
obj.defaultGroup = obj.templates[defaultGroup];
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides decode child to handle special child nodes.
|
||||
*/
|
||||
decodeChild(dec: Codec, child: Element, obj: any) {
|
||||
if (child.nodeName === 'Array') {
|
||||
const role = child.getAttribute('as');
|
||||
|
||||
if (role === 'templates') {
|
||||
this.decodeTemplates(dec, child, obj);
|
||||
return;
|
||||
}
|
||||
} else if (child.nodeName === 'ui') {
|
||||
this.decodeUi(dec, child, obj);
|
||||
return;
|
||||
}
|
||||
super.decodeChild.apply(this, [dec, child, obj]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes the ui elements from the given node.
|
||||
*/
|
||||
decodeUi(dec: Codec, node: Element, editor: Editor) {
|
||||
let tmp = <Element>node.firstChild;
|
||||
while (tmp != null) {
|
||||
if (tmp.nodeName === 'add') {
|
||||
const as = <string>tmp.getAttribute('as');
|
||||
const elt = tmp.getAttribute('element');
|
||||
const style = tmp.getAttribute('style');
|
||||
let element = null;
|
||||
|
||||
if (elt != null) {
|
||||
element = document.getElementById(elt);
|
||||
|
||||
if (element != null && style != null) {
|
||||
element.style.cssText += `;${style}`;
|
||||
}
|
||||
} else {
|
||||
const x = parseInt(<string>tmp.getAttribute('x'));
|
||||
const y = parseInt(<string>tmp.getAttribute('y'));
|
||||
const width = tmp.getAttribute('width') || null;
|
||||
const height = tmp.getAttribute('height') || null;
|
||||
|
||||
// Creates a new window around the element
|
||||
element = document.createElement('div');
|
||||
if (style != null) {
|
||||
element.style.cssText = style;
|
||||
}
|
||||
|
||||
const wnd = new MaxWindow(
|
||||
Translations.get(as) || as,
|
||||
element,
|
||||
x,
|
||||
y,
|
||||
width ? parseInt(width) : null,
|
||||
height ? parseInt(height) : null,
|
||||
false,
|
||||
true
|
||||
);
|
||||
wnd.setVisible(true);
|
||||
}
|
||||
|
||||
// TODO: Make more generic
|
||||
if (as === 'graph') {
|
||||
editor.setGraphContainer(element);
|
||||
} else if (as === 'toolbar') {
|
||||
editor.setToolbarContainer(element);
|
||||
} else if (as === 'title') {
|
||||
editor.setTitleContainer(element);
|
||||
} else if (as === 'status') {
|
||||
editor.setStatusContainer(element);
|
||||
} else if (as === 'map') {
|
||||
throw new Error('Unimplemented');
|
||||
//editor.setMapContainer(element);
|
||||
}
|
||||
} else if (tmp.nodeName === 'resource') {
|
||||
Translations.add(<string>tmp.getAttribute('basename'));
|
||||
} else if (tmp.nodeName === 'stylesheet') {
|
||||
addLinkToHead('stylesheet', <string>tmp.getAttribute('name'));
|
||||
}
|
||||
|
||||
tmp = <Element>tmp.nextSibling;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes the cells from the given node as templates.
|
||||
*/
|
||||
decodeTemplates(dec: Codec, node: Element, editor: Editor) {
|
||||
if (editor.templates == null) {
|
||||
editor.templates = [];
|
||||
}
|
||||
|
||||
const children = <Element[]>getChildNodes(node);
|
||||
for (let j = 0; j < children.length; j++) {
|
||||
const name = <string>children[j].getAttribute('as');
|
||||
let child = <Element | null>children[j].firstChild;
|
||||
|
||||
while (child != null && child.nodeType !== 1) {
|
||||
child = <Element | null>child.nextSibling;
|
||||
}
|
||||
|
||||
if (child != null) {
|
||||
// LATER: Only single cells means you need
|
||||
// to group multiple cells within another
|
||||
// cell. This should be changed to support
|
||||
// arrays of cells, or the wrapper must
|
||||
// be automatically handled in this class.
|
||||
editor.templates[name] = dec.decodeCell(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CodecRegistry.register(new EditorCodec());
|
||||
export default Editor;
|
||||
|
|
|
@ -20,9 +20,6 @@ import InternalEvent from '../view/event/InternalEvent';
|
|||
import EventObject from '../view/event/EventObject';
|
||||
import KeyHandler from '../view/handler/KeyHandler';
|
||||
import Editor from './Editor';
|
||||
import ObjectCodec from '../serialization/ObjectCodec';
|
||||
import CodecRegistry from '../serialization/CodecRegistry';
|
||||
import Codec from '../serialization/Codec';
|
||||
|
||||
/**
|
||||
* Binds keycodes to actionnames in an editor. This aggregates an internal {@link handler} and extends the implementation of {@link KeyHandler.escape} to not only cancel the editing, but also hide the properties dialog and fire an <Editor.escape> event via {@link editor}. An instance of this class is created by {@link Editor} and stored in {@link Editor.keyHandler}.
|
||||
|
@ -100,73 +97,4 @@ export class EditorKeyHandler {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom codec for configuring <EditorKeyHandler>s. This class is created
|
||||
* and registered dynamically at load time and used implicitly via
|
||||
* <Codec> and the <CodecRegistry>. This codec only reads configuration
|
||||
* data for existing key handlers, it does not encode or create key handlers.
|
||||
*/
|
||||
export class EditorKeyHandlerCodec extends ObjectCodec {
|
||||
constructor() {
|
||||
super(new EditorKeyHandler());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns null.
|
||||
*/
|
||||
encode(enc: Codec, obj: any) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a sequence of the following child nodes
|
||||
* and attributes:
|
||||
*
|
||||
* Child Nodes:
|
||||
*
|
||||
* add - Binds a keystroke to an actionname.
|
||||
*
|
||||
* Attributes:
|
||||
*
|
||||
* as - Keycode.
|
||||
* action - Actionname to execute in editor.
|
||||
* control - Optional boolean indicating if
|
||||
* the control key must be pressed.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```javascript
|
||||
* <EditorKeyHandler as="keyHandler">
|
||||
* <add as="88" control="true" action="cut"/>
|
||||
* <add as="67" control="true" action="copy"/>
|
||||
* <add as="86" control="true" action="paste"/>
|
||||
* </EditorKeyHandler>
|
||||
* ```
|
||||
*
|
||||
* The keycodes are for the x, c and v keys.
|
||||
*
|
||||
* See also: <EditorKeyHandler.bindAction>,
|
||||
* http://www.js-examples.com/page/tutorials__key_codes.html
|
||||
*/
|
||||
decode(dec: Codec, _node: Element, into: any) {
|
||||
if (into != null) {
|
||||
const { editor } = into;
|
||||
let node: Element | null = <Element | null>_node.firstChild;
|
||||
|
||||
while (node != null) {
|
||||
if (!this.processInclude(dec, node, into) && node.nodeName === 'add') {
|
||||
const as = node.getAttribute('as');
|
||||
const action = node.getAttribute('action');
|
||||
const control = node.getAttribute('control');
|
||||
|
||||
into.bindAction(as, action, control);
|
||||
}
|
||||
node = <Element | null>node.nextSibling;
|
||||
}
|
||||
}
|
||||
return into;
|
||||
}
|
||||
}
|
||||
|
||||
CodecRegistry.register(new EditorKeyHandlerCodec());
|
||||
export default EditorKeyHandler;
|
||||
|
|
|
@ -21,9 +21,6 @@ import MaxPopupMenu from '../gui/MaxPopupMenu';
|
|||
import { getTextContent } from '../util/domUtils';
|
||||
import Translations from '../util/Translations';
|
||||
import Editor from './Editor';
|
||||
import CodecRegistry from '../serialization/CodecRegistry';
|
||||
import ObjectCodec from '../serialization/ObjectCodec';
|
||||
import Codec from '../serialization/Codec';
|
||||
|
||||
import { PopupMenuItem } from '../types';
|
||||
|
||||
|
@ -324,42 +321,4 @@ export class EditorPopupMenu {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom codec for configuring <EditorPopupMenu>s. This class is created
|
||||
* and registered dynamically at load time and used implicitly via
|
||||
* <Codec> and the <CodecRegistry>. This codec only reads configuration
|
||||
* data for existing popup menus, it does not encode or create menus. Note
|
||||
* that this codec only passes the configuration node to the popup menu,
|
||||
* which uses the config to dynamically create menus. See
|
||||
* <EditorPopupMenu.createMenu>.
|
||||
*/
|
||||
export class EditorPopupMenuCodec extends ObjectCodec {
|
||||
constructor() {
|
||||
super(new EditorPopupMenu());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns null.
|
||||
*/
|
||||
encode(enc: Codec, obj: Element): Element | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses the given node as the config for <EditorPopupMenu>.
|
||||
*/
|
||||
decode(dec: Codec, node: Element, into: any) {
|
||||
const inc = node.getElementsByTagName('include')[0];
|
||||
|
||||
if (inc != null) {
|
||||
this.processInclude(dec, inc, into);
|
||||
} else if (into != null) {
|
||||
into.config = node;
|
||||
}
|
||||
|
||||
return into;
|
||||
}
|
||||
}
|
||||
|
||||
CodecRegistry.register(new EditorPopupMenuCodec());
|
||||
export default EditorPopupMenu;
|
||||
|
|
|
@ -27,13 +27,6 @@ import Editor from './Editor';
|
|||
import Cell from '../view/cell/Cell';
|
||||
import { Graph } from '../view/Graph';
|
||||
import EventObject from '../view/event/EventObject';
|
||||
import ObjectCodec from '../serialization/ObjectCodec';
|
||||
import CodecRegistry from '../serialization/CodecRegistry';
|
||||
import { getChildNodes, getTextContent } from '../util/domUtils';
|
||||
import { NODETYPE } from '../util/Constants';
|
||||
import Translations from '../util/Translations';
|
||||
import MaxLog from '../gui/MaxLog';
|
||||
import Codec from '../serialization/Codec';
|
||||
import type { DropHandler } from '../view/other/DragSource';
|
||||
|
||||
/**
|
||||
|
@ -483,262 +476,3 @@ export class EditorToolbar {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom codec for configuring <EditorToolbar>s. This class is created
|
||||
* and registered dynamically at load time and used implicitly via
|
||||
* <Codec> and the <CodecRegistry>. This codec only reads configuration
|
||||
* data for existing toolbars handlers, it does not encode or create toolbars.
|
||||
*/
|
||||
export class EditorToolbarCodec extends ObjectCodec {
|
||||
constructor() {
|
||||
super(new EditorToolbar());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns null.
|
||||
*/
|
||||
encode(enc: any, obj: any) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a sequence of the following child nodes
|
||||
* and attributes:
|
||||
*
|
||||
* Child Nodes:
|
||||
*
|
||||
* add - Adds a new item to the toolbar. See below for attributes.
|
||||
* separator - Adds a vertical separator. No attributes.
|
||||
* hr - Adds a horizontal separator. No attributes.
|
||||
* br - Adds a linefeed. No attributes.
|
||||
*
|
||||
* Attributes:
|
||||
*
|
||||
* as - Resource key for the label.
|
||||
* action - Name of the action to execute in enclosing editor.
|
||||
* mode - Modename (see below).
|
||||
* template - Template name for cell insertion.
|
||||
* style - Optional style to override the template style.
|
||||
* icon - Icon (relative/absolute URL).
|
||||
* pressedIcon - Optional icon for pressed state (relative/absolute URL).
|
||||
* id - Optional ID to be used for the created DOM element.
|
||||
* toggle - Optional 0 or 1 to disable toggling of the element. Default is
|
||||
* 1 (true).
|
||||
*
|
||||
* The action, mode and template attributes are mutually exclusive. The
|
||||
* style can only be used with the template attribute. The add node may
|
||||
* contain another sequence of add nodes with as and action attributes
|
||||
* to create a combo box in the toolbar. If the icon is specified then
|
||||
* a list of the child node is expected to have its template attribute
|
||||
* set and the action is ignored instead.
|
||||
*
|
||||
* Nodes with a specified template may define a function to be used for
|
||||
* inserting the cloned template into the graph. Here is an example of such
|
||||
* a node:
|
||||
*
|
||||
* ```javascript
|
||||
* <add as="Swimlane" template="swimlane" icon="images/swimlane.gif"><![CDATA[
|
||||
* function (editor, cell, evt, targetCell)
|
||||
* {
|
||||
* let pt = mxUtils.convertPoint(
|
||||
* editor.graph.container, mxEvent.getClientX(evt),
|
||||
* mxEvent.getClientY(evt));
|
||||
* return editor.addVertex(targetCell, cell, pt.x, pt.y);
|
||||
* }
|
||||
* ]]></add>
|
||||
* ```
|
||||
*
|
||||
* In the above function, editor is the enclosing <Editor> instance, cell
|
||||
* is the clone of the template, evt is the mouse event that represents the
|
||||
* drop and targetCell is the cell under the mousepointer where the drop
|
||||
* occurred. The targetCell is retrieved using {@link Graph#getCellAt}.
|
||||
*
|
||||
* Futhermore, nodes with the mode attribute may define a function to
|
||||
* be executed upon selection of the respective toolbar icon. In the
|
||||
* example below, the default edge style is set when this specific
|
||||
* connect-mode is activated:
|
||||
*
|
||||
* ```javascript
|
||||
* <add as="connect" mode="connect"><![CDATA[
|
||||
* function (editor)
|
||||
* {
|
||||
* if (editor.defaultEdge != null)
|
||||
* {
|
||||
* editor.defaultEdge.style = 'straightEdge';
|
||||
* }
|
||||
* }
|
||||
* ]]></add>
|
||||
* ```
|
||||
*
|
||||
* Both functions require <DefaultToolbarCodec.allowEval> to be set to true.
|
||||
*
|
||||
* Modes:
|
||||
*
|
||||
* select - Left mouse button used for rubberband- & cell-selection.
|
||||
* connect - Allows connecting vertices by inserting new edges.
|
||||
* pan - Disables selection and switches to panning on the left button.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* To add items to the toolbar:
|
||||
*
|
||||
* ```javascript
|
||||
* <EditorToolbar as="toolbar">
|
||||
* <add as="save" action="save" icon="images/save.gif"/>
|
||||
* <br/><hr/>
|
||||
* <add as="select" mode="select" icon="images/select.gif"/>
|
||||
* <add as="connect" mode="connect" icon="images/connect.gif"/>
|
||||
* </EditorToolbar>
|
||||
* ```
|
||||
*/
|
||||
decode(dec: Codec, _node: Element, into: any) {
|
||||
if (into != null) {
|
||||
const editor: Editor = into.editor;
|
||||
let node: Element | null = <Element | null>_node.firstChild;
|
||||
|
||||
while (node != null) {
|
||||
if (node.nodeType === NODETYPE.ELEMENT) {
|
||||
if (!this.processInclude(dec, node, into)) {
|
||||
if (node.nodeName === 'separator') {
|
||||
into.addSeparator();
|
||||
} else if (node.nodeName === 'br') {
|
||||
into.toolbar.addBreak();
|
||||
} else if (node.nodeName === 'hr') {
|
||||
into.toolbar.addLine();
|
||||
} else if (node.nodeName === 'add') {
|
||||
let as = <string>node.getAttribute('as');
|
||||
as = Translations.get(as) || as;
|
||||
const icon = node.getAttribute('icon');
|
||||
const pressedIcon = node.getAttribute('pressedIcon');
|
||||
const action = node.getAttribute('action');
|
||||
const mode = node.getAttribute('mode');
|
||||
const template = node.getAttribute('template');
|
||||
const toggle = node.getAttribute('toggle') != '0';
|
||||
const text = getTextContent(<Text>(<unknown>node));
|
||||
let elt = null;
|
||||
let funct: any;
|
||||
|
||||
if (action != null) {
|
||||
elt = into.addItem(as, icon, action, pressedIcon);
|
||||
} else if (mode != null) {
|
||||
funct = EditorToolbarCodec.allowEval ? eval(text) : null;
|
||||
elt = into.addMode(as, icon, mode, pressedIcon, funct);
|
||||
} else if (template != null || (text != null && text.length > 0)) {
|
||||
let cell = template ? editor.templates[template] : null;
|
||||
const style = node.getAttribute('style');
|
||||
|
||||
if (cell != null && style != null) {
|
||||
cell = editor.graph.cloneCell(cell);
|
||||
cell.setStyle(style);
|
||||
}
|
||||
|
||||
let insertFunction = null;
|
||||
|
||||
if (text != null && text.length > 0 && EditorToolbarCodec.allowEval) {
|
||||
insertFunction = eval(text);
|
||||
}
|
||||
|
||||
elt = into.addPrototype(
|
||||
as,
|
||||
icon,
|
||||
cell,
|
||||
pressedIcon,
|
||||
insertFunction,
|
||||
toggle
|
||||
);
|
||||
} else {
|
||||
const children = getChildNodes(node);
|
||||
|
||||
if (children.length > 0) {
|
||||
if (icon == null) {
|
||||
const combo = into.addActionCombo(as);
|
||||
|
||||
for (let i = 0; i < children.length; i += 1) {
|
||||
const child = <Element>children[i];
|
||||
|
||||
if (child.nodeName === 'separator') {
|
||||
into.addOption(combo, '---');
|
||||
} else if (child.nodeName === 'add') {
|
||||
const lab = child.getAttribute('as');
|
||||
const act = child.getAttribute('action');
|
||||
into.addActionOption(combo, lab, act);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const select: HTMLSelectElement = into.addCombo();
|
||||
|
||||
const create = () => {
|
||||
const template = editor.templates[select.value];
|
||||
|
||||
if (template != null) {
|
||||
const clone = template.clone();
|
||||
// @ts-ignore
|
||||
const style = select.options[select.selectedIndex].cellStyle;
|
||||
|
||||
if (style != null) {
|
||||
clone.setStyle(style);
|
||||
}
|
||||
|
||||
return clone;
|
||||
}
|
||||
MaxLog.warn(`Template ${template} not found`);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const img = into.addPrototype(as, icon, create, null, null, toggle);
|
||||
|
||||
// Selects the toolbar icon if a selection change
|
||||
// is made in the corresponding combobox.
|
||||
InternalEvent.addListener(select, 'change', () => {
|
||||
into.toolbar.selectMode(img, (evt: MouseEvent) => {
|
||||
const pt = convertPoint(
|
||||
editor.graph.container,
|
||||
getClientX(evt),
|
||||
getClientY(evt)
|
||||
);
|
||||
|
||||
return editor.addVertex(null, funct(), pt.x, pt.y);
|
||||
});
|
||||
|
||||
into.toolbar.noReset = false;
|
||||
});
|
||||
|
||||
// Adds the entries to the combobox
|
||||
for (let i = 0; i < children.length; i += 1) {
|
||||
const child = <Element>children[i];
|
||||
|
||||
if (child.nodeName === 'separator') {
|
||||
into.addOption(select, '---');
|
||||
} else if (child.nodeName === 'add') {
|
||||
const lab = child.getAttribute('as');
|
||||
const tmp = child.getAttribute('template');
|
||||
const option = into.addOption(select, lab, tmp || template);
|
||||
option.cellStyle = child.getAttribute('style');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Assigns an ID to the created element to access it later.
|
||||
if (elt != null) {
|
||||
const id = node.getAttribute('id');
|
||||
|
||||
if (id != null && id.length > 0) {
|
||||
elt.setAttribute('id', id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
node = <Element | null>node.nextSibling;
|
||||
}
|
||||
}
|
||||
return into;
|
||||
}
|
||||
}
|
||||
|
||||
CodecRegistry.register(new EditorToolbarCodec());
|
||||
|
|
|
@ -41,8 +41,8 @@ import './view/mixins/OrderMixin';
|
|||
|
||||
export { Graph } from './view/Graph';
|
||||
|
||||
export { GraphDataModel, ModelCodec } from './view/GraphDataModel';
|
||||
export { GraphView, GraphViewCodec } from './view/GraphView';
|
||||
export { GraphDataModel } from './view/GraphDataModel';
|
||||
export { GraphView } from './view/GraphView';
|
||||
export { default as LayoutManager } from './view/layout/LayoutManager';
|
||||
export { default as Outline } from './view/other/Outline';
|
||||
export { default as PrintPreview } from './view/other/PrintPreview';
|
||||
|
@ -50,24 +50,21 @@ export { default as SwimlaneManager } from './view/layout/SwimlaneManager';
|
|||
export { default as Client } from './Client';
|
||||
|
||||
export { default as CellAttributeChange } from './view/undoable_changes/CellAttributeChange';
|
||||
export { ChildChange, ChildChangeCodec } from './view/undoable_changes/ChildChange';
|
||||
export { ChildChange } from './view/undoable_changes/ChildChange';
|
||||
export { default as CollapseChange } from './view/undoable_changes/CollapseChange';
|
||||
export { default as CurrentRootChange } from './view/undoable_changes/CurrentRootChange';
|
||||
export { default as GeometryChange } from './view/undoable_changes/GeometryChange';
|
||||
export { RootChange, RootChangeCodec } from './view/undoable_changes/RootChange';
|
||||
export { RootChange } from './view/undoable_changes/RootChange';
|
||||
export { default as SelectionChange } from './view/undoable_changes/SelectionChange';
|
||||
export { default as StyleChange } from './view/undoable_changes/StyleChange';
|
||||
export {
|
||||
TerminalChange,
|
||||
TerminalChangeCodec,
|
||||
} from './view/undoable_changes/TerminalChange';
|
||||
export { TerminalChange } from './view/undoable_changes/TerminalChange';
|
||||
export { default as ValueChange } from './view/undoable_changes/ValueChange';
|
||||
export { default as VisibleChange } from './view/undoable_changes/VisibleChange';
|
||||
|
||||
export { EditorKeyHandler, EditorKeyHandlerCodec } from './editor/EditorKeyHandler';
|
||||
export { EditorPopupMenu, EditorPopupMenuCodec } from './editor/EditorPopupMenu';
|
||||
export { EditorToolbar, EditorToolbarCodec } from './editor/EditorToolbar';
|
||||
export { Editor, EditorCodec } from './editor/Editor';
|
||||
export { EditorKeyHandler } from './editor/EditorKeyHandler';
|
||||
export { EditorPopupMenu } from './editor/EditorPopupMenu';
|
||||
export { EditorToolbar } from './editor/EditorToolbar';
|
||||
export { Editor } from './editor/Editor';
|
||||
|
||||
export { default as CellHighlight } from './view/cell/CellHighlight';
|
||||
export { default as CellMarker } from './view/cell/CellMarker';
|
||||
|
@ -116,8 +113,9 @@ export { default as SwimlaneOrdering } from './view/layout/hierarchical/Swimlane
|
|||
|
||||
export { default as Codec } from './serialization/Codec';
|
||||
export { default as CodecRegistry } from './serialization/CodecRegistry';
|
||||
export { default as GenericChangeCodec } from './view/undoable_changes/GenericChangeCodec';
|
||||
export { default as ObjectCodec } from './serialization/ObjectCodec';
|
||||
export * from './serialization/codecs';
|
||||
export * from './serialization/register';
|
||||
|
||||
export { default as ActorShape } from './view/geometry/ActorShape';
|
||||
export { default as LabelShape } from './view/geometry/node/LabelShape';
|
||||
|
@ -175,7 +173,7 @@ export { default as Rectangle } from './view/geometry/Rectangle';
|
|||
export { default as EdgeStyle } from './view/style/EdgeStyle';
|
||||
export { default as Perimeter } from './view/style/Perimeter';
|
||||
export { default as StyleRegistry } from './view/style/StyleRegistry';
|
||||
export { Stylesheet, StylesheetCodec } from './view/style/Stylesheet';
|
||||
export { Stylesheet } from './view/style/Stylesheet';
|
||||
|
||||
export * as DomHelpers from './util/domHelpers';
|
||||
|
||||
|
@ -208,7 +206,7 @@ export { default as Clipboard } from './util/Clipboard';
|
|||
export { default as UndoableEdit } from './view/undoable_changes/UndoableEdit';
|
||||
export { default as UndoManager } from './view/undoable_changes/UndoManager';
|
||||
|
||||
export { Cell, CellCodec } from './view/cell/Cell';
|
||||
export { Cell } from './view/cell/Cell';
|
||||
export { default as CellEditorHandler } from './view/handler/CellEditorHandler';
|
||||
export { default as CellOverlay } from './view/cell/CellOverlay';
|
||||
export { default as CellPath } from './view/cell/CellPath';
|
||||
|
|
|
@ -50,6 +50,10 @@ const createXmlDocument = () => {
|
|||
* const xml = mxUtils.getXml(result);
|
||||
* ```
|
||||
*
|
||||
* **WARN**: as of version 0.6.0, the codecs provided by maxGraph are no longer registered by default, they **MUST** be registered before
|
||||
* performing `encode` or `decode`. For instance, you can use the {@link registerAllCodecs} function (or other related functions)
|
||||
* to register the codecs.
|
||||
*
|
||||
* #### Example
|
||||
*
|
||||
* Using the code below, an XML document is decoded into an existing model. The
|
||||
|
|
|
@ -0,0 +1,171 @@
|
|||
/*
|
||||
Copyright 2023-present The maxGraph project Contributors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import CodecRegistry from '../CodecRegistry';
|
||||
import ObjectCodec from '../ObjectCodec';
|
||||
import Cell from '../../view/cell/Cell';
|
||||
import type Codec from '../Codec';
|
||||
import { NODETYPE } from '../../util/Constants';
|
||||
import { importNode } from '../../util/domUtils';
|
||||
import { removeWhitespace } from '../../util/StringUtils';
|
||||
|
||||
/**
|
||||
* Codec for {@link Cell}s.
|
||||
*
|
||||
* This class is created and registered dynamically at load time and used implicitly via {@link Codec} and the {@link CodecRegistry}.
|
||||
*
|
||||
* Transient Fields:
|
||||
*
|
||||
* - children
|
||||
* - edges
|
||||
* - overlays
|
||||
* - mxTransient
|
||||
*
|
||||
* Reference Fields:
|
||||
*
|
||||
* - parent
|
||||
* - source
|
||||
* - target
|
||||
*
|
||||
* Transient fields can be added using the following code: `CodecRegistry.getCodec(Cell).exclude.push('name_of_field');`
|
||||
*
|
||||
* To subclass {@link Cell}, replace the template and add an alias as follows:
|
||||
*
|
||||
* ```javascript
|
||||
* // Given 'CustomCell' extends 'Cell'
|
||||
* CodecRegistry.getCodec(Cell).template = new CustomCell();
|
||||
* CodecRegistry.addAlias('CustomCell', 'Cell');
|
||||
* ```
|
||||
*/
|
||||
export class CellCodec extends ObjectCodec {
|
||||
constructor() {
|
||||
super(
|
||||
new Cell(),
|
||||
['children', 'edges', 'overlays', 'mxTransient'],
|
||||
['parent', 'source', 'target']
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `true` since this is a cell codec.
|
||||
*/
|
||||
isCellCodec() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overridden to disable conversion of value to number.
|
||||
*/
|
||||
isNumericAttribute(dec: Codec, attr: Element, obj: any) {
|
||||
return attr.nodeName !== 'value' && super.isNumericAttribute(dec, attr, obj);
|
||||
}
|
||||
|
||||
/**
|
||||
* Excludes user objects that are XML nodes.
|
||||
*/
|
||||
isExcluded(obj: any, attr: string, value: Element, isWrite: boolean) {
|
||||
return (
|
||||
super.isExcluded(obj, attr, value, isWrite) ||
|
||||
(isWrite && attr === 'value' && value.nodeType === NODETYPE.ELEMENT)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes a {@link Cell} and wraps the XML up inside the XML of the user object (inversion).
|
||||
*/
|
||||
afterEncode(enc: Codec, obj: Cell, node: Element) {
|
||||
if (obj.value != null && obj.value.nodeType === NODETYPE.ELEMENT) {
|
||||
// Wraps the graphical annotation up in the user object (inversion)
|
||||
// by putting the result of the default encoding into a clone of the
|
||||
// user object (node type 1) and returning this cloned user object.
|
||||
const tmp = node;
|
||||
node = importNode(enc.document, obj.value, true);
|
||||
node.appendChild(tmp);
|
||||
|
||||
// Moves the id attribute to the outermost XML node, namely the
|
||||
// node which denotes the object boundaries in the file.
|
||||
const id = tmp.getAttribute('id');
|
||||
node.setAttribute('id', String(id));
|
||||
tmp.removeAttribute('id');
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes an {@link Cell} and uses the enclosing XML node as the user object for the cell (inversion).
|
||||
*/
|
||||
beforeDecode(dec: Codec, node: Element, obj: Cell): Element | null {
|
||||
let inner: Element | null = <Element>node.cloneNode(true);
|
||||
const classname = this.getName();
|
||||
|
||||
if (node.nodeName !== classname) {
|
||||
// Passes the inner graphical annotation node to the
|
||||
// object codec for further processing of the cell.
|
||||
const tmp = node.getElementsByTagName(classname)[0];
|
||||
|
||||
if (tmp != null && tmp.parentNode === node) {
|
||||
removeWhitespace(<HTMLElement>tmp, true);
|
||||
removeWhitespace(<HTMLElement>tmp, false);
|
||||
tmp.parentNode.removeChild(tmp);
|
||||
inner = tmp;
|
||||
} else {
|
||||
inner = null;
|
||||
}
|
||||
|
||||
// Creates the user object out of the XML node
|
||||
obj.value = node.cloneNode(true);
|
||||
const id = obj.value.getAttribute('id');
|
||||
|
||||
if (id != null) {
|
||||
obj.setId(id);
|
||||
obj.value.removeAttribute('id');
|
||||
}
|
||||
} else {
|
||||
// Uses ID from XML file as ID for cell in model
|
||||
obj.setId(<string>node.getAttribute('id'));
|
||||
}
|
||||
|
||||
// Preprocesses and removes all Id-references in order to use the
|
||||
// correct encoder (this) for the known references to cells (all).
|
||||
if (inner != null) {
|
||||
for (let i = 0; i < this.idrefs.length; i += 1) {
|
||||
const attr = this.idrefs[i];
|
||||
const ref = inner.getAttribute(attr);
|
||||
|
||||
if (ref != null) {
|
||||
inner.removeAttribute(attr);
|
||||
let object = dec.objects[ref] || dec.lookup(ref);
|
||||
|
||||
if (object == null) {
|
||||
// Needs to decode forward reference
|
||||
const element = dec.getElementById(ref);
|
||||
|
||||
if (element != null) {
|
||||
const decoder = CodecRegistry.codecs[element.nodeName] || this;
|
||||
object = decoder.decode(dec, element);
|
||||
}
|
||||
}
|
||||
|
||||
// @ts-ignore dynamic assignment was in original implementation
|
||||
obj[attr] = object;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return inner;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,156 @@
|
|||
/*
|
||||
Copyright 2023-present The maxGraph project Contributors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import ObjectCodec from '../ObjectCodec';
|
||||
import ChildChange from '../../view/undoable_changes/ChildChange';
|
||||
import type Codec from '../Codec';
|
||||
import { NODETYPE } from '../../util/Constants';
|
||||
|
||||
/**
|
||||
* Codec for {@link ChildChange}s.
|
||||
*
|
||||
* This class is created and registered dynamically at load time and used implicitly via {@link Codec} and the {@link CodecRegistry}.
|
||||
*
|
||||
* Transient Fields:
|
||||
*
|
||||
* - model
|
||||
* - previous
|
||||
* - previousIndex
|
||||
* - child
|
||||
*
|
||||
* Reference Fields:
|
||||
*
|
||||
* - parent
|
||||
*/
|
||||
export class ChildChangeCodec extends ObjectCodec {
|
||||
constructor() {
|
||||
const __dummy: any = undefined;
|
||||
super(
|
||||
new ChildChange(__dummy, __dummy, __dummy),
|
||||
['model', 'child', 'previousIndex'],
|
||||
['parent', 'previous']
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `true` for the child attribute if the child cell had a previous parent or if we're reading the
|
||||
* child as an attribute rather than a child node, in which case it's always a reference.
|
||||
*/
|
||||
isReference(obj: any, attr: string, value: any, isWrite: boolean) {
|
||||
if (attr === 'child' && (!isWrite || obj.model.contains(obj.previous))) {
|
||||
return true;
|
||||
}
|
||||
return this.idrefs.indexOf(attr) >= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Excludes references to parent or previous if not in the model.
|
||||
*/
|
||||
isExcluded(obj: any, attr: string, value: any, write: boolean) {
|
||||
return (
|
||||
super.isExcluded(obj, attr, value, write) ||
|
||||
(write &&
|
||||
value != null &&
|
||||
(attr === 'previous' || attr === 'parent') &&
|
||||
!obj.model.contains(value))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes the child recursively and adds the result to the given node.
|
||||
*/
|
||||
afterEncode(enc: Codec, obj: any, node: Element) {
|
||||
if (this.isReference(obj, 'child', obj.child, true)) {
|
||||
// Encodes as reference (id)
|
||||
node.setAttribute('child', enc.getId(obj.child));
|
||||
} else {
|
||||
// At this point, the encoder is no longer able to know which cells
|
||||
// are new, so we have to encode the complete cell hierarchy and
|
||||
// ignore the ones that are already there at decoding time. Note:
|
||||
// This can only be resolved by moving the notify event into the
|
||||
// execute of the edit.
|
||||
enc.encodeCell(obj.child, node);
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes any child nodes as using the respective codec from the registry.
|
||||
*/
|
||||
beforeDecode(dec: Codec, _node: Element, obj: any): any {
|
||||
if (_node.firstChild != null && _node.firstChild.nodeType === NODETYPE.ELEMENT) {
|
||||
// Makes sure the original node isn't modified
|
||||
const node = _node.cloneNode(true);
|
||||
|
||||
let tmp = <Element>node.firstChild;
|
||||
obj.child = dec.decodeCell(tmp, false);
|
||||
|
||||
let tmp2 = <Element>tmp.nextSibling;
|
||||
(<Element>tmp.parentNode).removeChild(tmp);
|
||||
tmp = tmp2;
|
||||
|
||||
while (tmp != null) {
|
||||
tmp2 = <Element>tmp.nextSibling;
|
||||
|
||||
if (tmp.nodeType === NODETYPE.ELEMENT) {
|
||||
// Ignores all existing cells because those do not need to
|
||||
// be re-inserted into the model. Since the encoded version
|
||||
// of these cells contains the new parent, this would leave
|
||||
// to an inconsistent state on the model (i.e. a parent
|
||||
// change without a call to parentForCellChanged).
|
||||
const id = <string>tmp.getAttribute('id');
|
||||
|
||||
if (dec.lookup(id) == null) {
|
||||
dec.decodeCell(tmp);
|
||||
}
|
||||
}
|
||||
|
||||
(<Element>tmp.parentNode).removeChild(tmp);
|
||||
tmp = tmp2;
|
||||
}
|
||||
|
||||
return node;
|
||||
} else {
|
||||
const childRef = <string>_node.getAttribute('child');
|
||||
obj.child = dec.getObject(childRef);
|
||||
return _node;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Restores object state in the child change.
|
||||
*/
|
||||
afterDecode(dec: Codec, node: Element, obj: any): any {
|
||||
// Cells are decoded here after a complete transaction so the previous
|
||||
// parent must be restored on the cell for the case where the cell was
|
||||
// added. This is needed for the local model to identify the cell as a
|
||||
// new cell and register the ID.
|
||||
if (obj.child != null) {
|
||||
if (
|
||||
obj.child.parent != null &&
|
||||
obj.previous != null &&
|
||||
obj.child.parent !== obj.previous
|
||||
) {
|
||||
obj.previous = obj.child.parent;
|
||||
}
|
||||
|
||||
obj.child.parent = obj.previous;
|
||||
obj.previous = obj.parent;
|
||||
obj.previousIndex = obj.index;
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
}
|
|
@ -16,15 +16,14 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import ObjectCodec from '../../serialization/ObjectCodec';
|
||||
import ObjectCodec from '../ObjectCodec';
|
||||
import { isNode } from '../../util/domUtils';
|
||||
import Codec from '../../serialization/Codec';
|
||||
import type Codec from '../Codec';
|
||||
|
||||
/**
|
||||
* Codec for {@link ValueChange}s, {@link StyleChange}s, {@link GeometryChange}s,
|
||||
* {@link CollapseChange}s and {@link VisibleChange}s. This class is created
|
||||
* and registered dynamically at load time and used implicitly
|
||||
* via <Codec> and the <CodecRegistry>.
|
||||
* Codec for {@link ValueChange}s, {@link StyleChange}s, {@link GeometryChange}s, {@link CollapseChange}s and {@link VisibleChange}s.
|
||||
*
|
||||
* This class is created and registered dynamically at load time and used implicitly via {@link Codec} and the {@link CodecRegistry}.
|
||||
*
|
||||
* Transient Fields:
|
||||
*
|
||||
|
@ -34,16 +33,13 @@ import Codec from '../../serialization/Codec';
|
|||
* Reference Fields:
|
||||
*
|
||||
* - cell
|
||||
*
|
||||
* Constructor: GenericChangeCodec
|
||||
*
|
||||
* Factory function that creates a <ObjectCodec> for
|
||||
* the specified change and fieldname.
|
||||
*
|
||||
* @param obj An instance of the change object.
|
||||
* @param variable The fieldname for the change data.
|
||||
*/
|
||||
class GenericChangeCodec extends ObjectCodec {
|
||||
export class GenericChangeCodec extends ObjectCodec {
|
||||
/**
|
||||
*
|
||||
* @param obj An instance of the change object.
|
||||
* @param variable The field name for the change data.
|
||||
*/
|
||||
constructor(obj: any, variable: string) {
|
||||
super(obj, ['model', 'previous'], ['cell']);
|
||||
this.variable = variable;
|
||||
|
@ -54,7 +50,7 @@ class GenericChangeCodec extends ObjectCodec {
|
|||
/**
|
||||
* Restores the state by assigning the previous value.
|
||||
*/
|
||||
afterDecode(dec: Codec, node: Element, obj: any): any {
|
||||
afterDecode(dec: Codec, _node: Element, obj: any): any {
|
||||
// Allows forward references in sessions. This is a workaround
|
||||
// for the sequence of edits in mxGraph.moveCells and cellsAdded.
|
||||
if (isNode(obj.cell)) {
|
||||
|
@ -65,5 +61,3 @@ class GenericChangeCodec extends ObjectCodec {
|
|||
return obj;
|
||||
}
|
||||
}
|
||||
|
||||
export default GenericChangeCodec;
|
|
@ -0,0 +1,169 @@
|
|||
/*
|
||||
Copyright 2023-present The maxGraph project Contributors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import ObjectCodec from '../ObjectCodec';
|
||||
import GraphView from '../../view/GraphView';
|
||||
import Cell from '../../view/cell/Cell';
|
||||
import StyleRegistry from '../../view/style/StyleRegistry';
|
||||
import Point from '../../view/geometry/Point';
|
||||
|
||||
/**
|
||||
* Custom encoder for {@link GraphView}s.
|
||||
*
|
||||
* This class is created and registered dynamically at load time and used implicitly via {@link Codec} and the {@link CodecRegistry}.
|
||||
*
|
||||
* This codec only writes views into an XML format that can be used to create an image for the graph, that is,
|
||||
* it contains absolute coordinates with computed perimeters, edge styles and cell styles.
|
||||
*/
|
||||
export class GraphViewCodec extends ObjectCodec {
|
||||
constructor() {
|
||||
const __dummy: any = undefined;
|
||||
super(new GraphView(__dummy));
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes the given {@link GraphView} using {@link encodeCell} starting at the model's root. This returns the
|
||||
* top-level graph node of the recursive encoding.
|
||||
*/
|
||||
encode(enc: any, view: GraphView) {
|
||||
return this.encodeCell(enc, view, <Cell>view.graph.getDataModel().getRoot());
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively encodes the specified cell.
|
||||
*
|
||||
* Uses layer as the default node name. If the cell's parent is null, then graph is used for the node name.
|
||||
* If {@link Cell.isEdge} returns `true` for the cell, then edge is used for the node name, else if {@link Cell.isVertex} returns `true` for the cell,
|
||||
* then vertex is used for the node name.
|
||||
*
|
||||
* {@link Graph.getLabel} is used to create the label attribute for the cell.
|
||||
* For graph nodes and vertices the bounds are encoded into x, y, width and height.
|
||||
* For edges the points are encoded into a points attribute as a space-separated list of comma-separated coordinate pairs (e.g. x0,y0 x1,y1 ... xn,yn).
|
||||
* All values from the cell style are added as attribute values to the node.
|
||||
*/
|
||||
encodeCell(enc: any, view: GraphView, cell: Cell) {
|
||||
let node;
|
||||
const model = view.graph.getDataModel();
|
||||
const state = view.getState(cell);
|
||||
const parent = cell.getParent();
|
||||
|
||||
if (parent == null || state != null) {
|
||||
const childCount = cell.getChildCount();
|
||||
const geo = cell.getGeometry();
|
||||
let name = null;
|
||||
|
||||
if (parent === model.getRoot()) {
|
||||
name = 'layer';
|
||||
} else if (parent == null) {
|
||||
name = 'graph';
|
||||
} else if (cell.isEdge()) {
|
||||
name = 'edge';
|
||||
} else if (childCount > 0 && geo != null) {
|
||||
name = 'group';
|
||||
} else if (cell.isVertex()) {
|
||||
name = 'vertex';
|
||||
}
|
||||
|
||||
if (name != null) {
|
||||
node = enc.document.createElement(name);
|
||||
const lab = view.graph.getLabel(cell);
|
||||
|
||||
if (lab != null) {
|
||||
node.setAttribute('label', view.graph.getLabel(cell));
|
||||
|
||||
if (view.graph.isHtmlLabel(cell)) {
|
||||
node.setAttribute('html', true);
|
||||
}
|
||||
}
|
||||
|
||||
if (parent == null) {
|
||||
const bounds = view.getGraphBounds();
|
||||
|
||||
if (bounds != null) {
|
||||
node.setAttribute('x', Math.round(bounds.x));
|
||||
node.setAttribute('y', Math.round(bounds.y));
|
||||
node.setAttribute('width', Math.round(bounds.width));
|
||||
node.setAttribute('height', Math.round(bounds.height));
|
||||
}
|
||||
|
||||
node.setAttribute('scale', view.scale);
|
||||
} else if (state != null && geo != null) {
|
||||
// Writes each key, value in the style pair to an attribute
|
||||
for (const i in state.style) {
|
||||
// @ts-ignore
|
||||
let value = state.style[i];
|
||||
|
||||
// Tries to turn objects and functions into strings
|
||||
if (typeof value === 'function' && typeof value === 'object') {
|
||||
value = StyleRegistry.getName(value);
|
||||
}
|
||||
|
||||
if (
|
||||
value != null &&
|
||||
typeof value !== 'function' &&
|
||||
typeof value !== 'object'
|
||||
) {
|
||||
node.setAttribute(i, value);
|
||||
}
|
||||
}
|
||||
|
||||
const abs = state.absolutePoints;
|
||||
|
||||
// Writes the list of points into one attribute
|
||||
if (abs != null && abs.length > 0) {
|
||||
let pts = `${Math.round((<Point>abs[0]).x)},${Math.round((<Point>abs[0]).y)}`;
|
||||
|
||||
for (let i = 1; i < abs.length; i += 1) {
|
||||
pts += ` ${Math.round((<Point>abs[i]).x)},${Math.round((<Point>abs[i]).y)}`;
|
||||
}
|
||||
|
||||
node.setAttribute('points', pts);
|
||||
}
|
||||
|
||||
// Writes the bounds into 4 attributes
|
||||
else {
|
||||
node.setAttribute('x', Math.round(state.x));
|
||||
node.setAttribute('y', Math.round(state.y));
|
||||
node.setAttribute('width', Math.round(state.width));
|
||||
node.setAttribute('height', Math.round(state.height));
|
||||
}
|
||||
|
||||
const offset = state.absoluteOffset;
|
||||
|
||||
// Writes the offset into 2 attributes
|
||||
if (offset != null) {
|
||||
if (offset.x !== 0) {
|
||||
node.setAttribute('dx', Math.round(offset.x));
|
||||
}
|
||||
|
||||
if (offset.y !== 0) {
|
||||
node.setAttribute('dy', Math.round(offset.y));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < childCount; i += 1) {
|
||||
const childNode = this.encodeCell(enc, view, cell.getChildAt(i));
|
||||
|
||||
if (childNode != null) {
|
||||
node.appendChild(childNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return node;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
Copyright 2023-present The maxGraph project Contributors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import ObjectCodec from '../ObjectCodec';
|
||||
import GraphDataModel from '../../view/GraphDataModel';
|
||||
import Cell from '../../view/cell/Cell';
|
||||
|
||||
/**
|
||||
* Codec for {@link GraphDataModel}s.
|
||||
*
|
||||
* This class is created and registered dynamically at load time and used implicitly via {@link Codec} and the {@link CodecRegistry}.
|
||||
*/
|
||||
export class ModelCodec extends ObjectCodec {
|
||||
constructor() {
|
||||
super(new GraphDataModel());
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes the given {@link GraphDataModel} by writing a (flat) XML sequence of cell nodes as produced by the <CellCodec>.
|
||||
* The sequence is wrapped-up in a node with the name root.
|
||||
*/
|
||||
encodeObject(enc: any, obj: Cell, node: Element) {
|
||||
const rootNode = enc.document.createElement('root');
|
||||
enc.encodeCell(obj.getRoot(), rootNode);
|
||||
node.appendChild(rootNode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides decode child to handle special child nodes.
|
||||
*/
|
||||
decodeChild(dec: any, child: Element, obj: Cell | GraphDataModel) {
|
||||
if (child.nodeName === 'root') {
|
||||
this.decodeRoot(dec, child, <GraphDataModel>obj);
|
||||
} else {
|
||||
this.decodeChild.apply(this, [dec, child, obj]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the cells into the graph model. All cells are children of the root element in the node.
|
||||
*/
|
||||
decodeRoot(dec: any, root: Element, model: GraphDataModel) {
|
||||
let rootCell = null;
|
||||
let tmp = root.firstChild;
|
||||
|
||||
while (tmp != null) {
|
||||
const cell = dec.decodeCell(tmp);
|
||||
|
||||
if (cell != null && cell.getParent() == null) {
|
||||
rootCell = cell;
|
||||
}
|
||||
tmp = tmp.nextSibling;
|
||||
}
|
||||
|
||||
// Sets the root on the model if one has been decoded
|
||||
if (rootCell != null) {
|
||||
model.setRoot(rootCell);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
Copyright 2023-present The maxGraph project Contributors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import ObjectCodec from '../ObjectCodec';
|
||||
import RootChange from '../../view/undoable_changes/RootChange';
|
||||
import type Codec from '../Codec';
|
||||
import { NODETYPE } from '../../util/Constants';
|
||||
|
||||
/**
|
||||
* Codec for {@link RootChange}s.
|
||||
*
|
||||
* This class is created and registered dynamically at load time and used implicitly via {@link Codec} and the {@link CodecRegistry}.
|
||||
*
|
||||
* Transient Fields:
|
||||
*
|
||||
* - model
|
||||
* - previous
|
||||
* - root
|
||||
*/
|
||||
export class RootChangeCodec extends ObjectCodec {
|
||||
constructor() {
|
||||
const __dummy: any = undefined;
|
||||
super(new RootChange(__dummy, __dummy), ['model', 'previous', 'root']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes the child recursively.
|
||||
*/
|
||||
afterEncode(enc: Codec, obj: any, node: Element) {
|
||||
enc.encodeCell(obj.root, node);
|
||||
return node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes the optional children as cells using the respective decoder.
|
||||
*/
|
||||
beforeDecode(dec: Codec, node: Element, obj: any): any {
|
||||
if (node.firstChild != null && node.firstChild.nodeType === NODETYPE.ELEMENT) {
|
||||
// Makes sure the original node isn't modified
|
||||
node = <Element>node.cloneNode(true);
|
||||
|
||||
let tmp = <Element>node.firstChild;
|
||||
obj.root = dec.decodeCell(tmp, false);
|
||||
|
||||
let tmp2 = <Element>tmp.nextSibling;
|
||||
(<Element>tmp.parentNode).removeChild(tmp);
|
||||
tmp = tmp2;
|
||||
|
||||
while (tmp != null) {
|
||||
tmp2 = <Element>tmp.nextSibling;
|
||||
dec.decodeCell(tmp);
|
||||
(<Element>tmp.parentNode).removeChild(tmp);
|
||||
tmp = tmp2;
|
||||
}
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restores the state by assigning the previous value.
|
||||
*/
|
||||
afterDecode(_dec: Codec, _node: Element, obj: any): any {
|
||||
obj.previous = obj.root;
|
||||
|
||||
return obj;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,193 @@
|
|||
/*
|
||||
Copyright 2023-present The maxGraph project Contributors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import ObjectCodec from '../ObjectCodec';
|
||||
import { Stylesheet } from '../../view/style/Stylesheet';
|
||||
import type Codec from '../Codec';
|
||||
import StyleRegistry from '../../view/style/StyleRegistry';
|
||||
import { clone } from '../../util/cloneUtils';
|
||||
import MaxLog from '../../gui/MaxLog';
|
||||
import { NODETYPE } from '../../util/Constants';
|
||||
import { isNumeric } from '../../util/mathUtils';
|
||||
import { getTextContent } from '../../util/domUtils';
|
||||
|
||||
/**
|
||||
* Codec for {@link Stylesheet}s.
|
||||
*
|
||||
* This class is created and registered dynamically at load time and used implicitly via {@link Codec} and the {@link CodecRegistry}.
|
||||
*/
|
||||
export class StylesheetCodec extends ObjectCodec {
|
||||
constructor() {
|
||||
super(new Stylesheet());
|
||||
}
|
||||
|
||||
/**
|
||||
* Static global switch that specifies if the use of eval is allowed for evaluating text content. Default is true.
|
||||
* Set this to `false` if stylesheets may contain user input.
|
||||
*/
|
||||
static allowEval = true;
|
||||
|
||||
/**
|
||||
* Encodes a stylesheet. See {@link decode} for a description of the format.
|
||||
*/
|
||||
encode(enc: Codec, obj: any): Element {
|
||||
const node = enc.document.createElement(this.getName());
|
||||
|
||||
for (const i in obj.styles) {
|
||||
const style = obj.styles[i];
|
||||
const styleNode = enc.document.createElement('add');
|
||||
|
||||
if (i != null) {
|
||||
styleNode.setAttribute('as', i);
|
||||
|
||||
for (const j in style) {
|
||||
const value = this.getStringValue(j, style[j]);
|
||||
|
||||
if (value != null) {
|
||||
const entry = enc.document.createElement('add');
|
||||
entry.setAttribute('value', value);
|
||||
entry.setAttribute('as', j);
|
||||
styleNode.appendChild(entry);
|
||||
}
|
||||
}
|
||||
|
||||
if (styleNode.childNodes.length > 0) {
|
||||
node.appendChild(styleNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the string for encoding the given value.
|
||||
*/
|
||||
getStringValue(key: string, value: any): string | null {
|
||||
const type = typeof value;
|
||||
|
||||
if (type === 'function') {
|
||||
value = StyleRegistry.getName(value);
|
||||
} else if (type === 'object') {
|
||||
value = null;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a sequence of the following child nodes and attributes:
|
||||
*
|
||||
* Child Nodes:
|
||||
*
|
||||
* add - Adds a new style.
|
||||
*
|
||||
* Attributes:
|
||||
*
|
||||
* as - Name of the style.
|
||||
* extend - Name of the style to inherit from.
|
||||
*
|
||||
* Each node contains another sequence of add and remove nodes with the following attributes:
|
||||
*
|
||||
* as - Name of the style (see {@link Constants}).
|
||||
* value - Value for the style.
|
||||
*
|
||||
* Instead of the value-attribute, one can put Javascript expressions into the node as follows if {@link allowEval} is `true`:
|
||||
* <add as="perimeter">mxPerimeter.RectanglePerimeter</add>
|
||||
*
|
||||
* A remove node will remove the entry with the name given in the as-attribute from the style.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```javascript
|
||||
* <mxStylesheet as="stylesheet">
|
||||
* <add as="text">
|
||||
* <add as="fontSize" value="12"/>
|
||||
* </add>
|
||||
* <add as="defaultVertex" extend="text">
|
||||
* <add as="shape" value="rectangle"/>
|
||||
* </add>
|
||||
* </mxStylesheet>
|
||||
* ```
|
||||
*/
|
||||
decode(dec: Codec, _node: Element, into: any): any {
|
||||
const obj = into || new this.template.constructor();
|
||||
const id = _node.getAttribute('id');
|
||||
|
||||
if (id != null) {
|
||||
dec.objects[id] = obj;
|
||||
}
|
||||
|
||||
let node: Element | ChildNode | null = _node.firstChild;
|
||||
|
||||
while (node != null) {
|
||||
if (!this.processInclude(dec, <Element>node, obj) && node.nodeName === 'add') {
|
||||
const as = (<Element>node).getAttribute('as');
|
||||
|
||||
if (as != null) {
|
||||
const extend = (<Element>node).getAttribute('extend');
|
||||
let style = extend != null ? clone(obj.styles[extend]) : null;
|
||||
|
||||
if (style == null) {
|
||||
if (extend != null) {
|
||||
MaxLog.warn(
|
||||
`StylesheetCodec.decode: stylesheet ${extend} not found to extend`
|
||||
);
|
||||
}
|
||||
|
||||
style = {};
|
||||
}
|
||||
|
||||
let entry = node.firstChild;
|
||||
|
||||
while (entry != null) {
|
||||
if (entry.nodeType === NODETYPE.ELEMENT) {
|
||||
const key = <string>(<Element>entry).getAttribute('as');
|
||||
|
||||
if (entry.nodeName === 'add') {
|
||||
const text = getTextContent(<Text>(<unknown>entry));
|
||||
let value = null;
|
||||
|
||||
if (text != null && text.length > 0 && StylesheetCodec.allowEval) {
|
||||
value = eval(text);
|
||||
} else {
|
||||
value = (<Element>entry).getAttribute('value');
|
||||
|
||||
if (isNumeric(value)) {
|
||||
value = parseFloat(<string>value);
|
||||
}
|
||||
}
|
||||
|
||||
if (value != null) {
|
||||
style[key] = value;
|
||||
}
|
||||
} else if (entry.nodeName === 'remove') {
|
||||
delete style[key];
|
||||
}
|
||||
}
|
||||
|
||||
entry = entry.nextSibling;
|
||||
}
|
||||
|
||||
obj.putCellStyle(as, style);
|
||||
}
|
||||
}
|
||||
|
||||
node = node.nextSibling;
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
Copyright 2023-present The maxGraph project Contributors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import ObjectCodec from '../ObjectCodec';
|
||||
import TerminalChange from '../../view/undoable_changes/TerminalChange';
|
||||
import type Codec from '../Codec';
|
||||
|
||||
/**
|
||||
* Codec for {@link TerminalChange}s.
|
||||
*
|
||||
* This class is created and registered dynamically at load time and used implicitly via {@link Codec} and the {@link CodecRegistry}.
|
||||
*
|
||||
* Transient Fields:
|
||||
*
|
||||
* - model
|
||||
* - previous
|
||||
*
|
||||
* Reference Fields:
|
||||
*
|
||||
* - cell
|
||||
* - terminal
|
||||
*/
|
||||
export class TerminalChangeCodec extends ObjectCodec {
|
||||
constructor() {
|
||||
const __dummy: any = undefined;
|
||||
super(
|
||||
new TerminalChange(__dummy, __dummy, __dummy, __dummy),
|
||||
['model', 'previous'],
|
||||
['cell', 'terminal']
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Restores the state by assigning the previous value.
|
||||
*/
|
||||
afterDecode(_dec: Codec, _node: Element, obj: any): any {
|
||||
obj.previous = obj.terminal;
|
||||
return obj;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,233 @@
|
|||
/*
|
||||
Copyright 2023-present The maxGraph project Contributors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import ObjectCodec from '../../ObjectCodec';
|
||||
import Editor from '../../../editor/Editor';
|
||||
import type Codec from '../../Codec';
|
||||
import MaxWindow from '../../../gui/MaxWindow';
|
||||
import Translations from '../../../util/Translations';
|
||||
import { addLinkToHead, getChildNodes } from '../../../util/domUtils';
|
||||
|
||||
/**
|
||||
* Codec for {@link Editor}s.
|
||||
*
|
||||
* This class is created and registered dynamically at load time and used implicitly via {@link Codec} and the {@link CodecRegistry}.
|
||||
*
|
||||
* Transient Fields:
|
||||
*
|
||||
* - modified
|
||||
* - lastSnapshot
|
||||
* - ignoredChanges
|
||||
* - undoManager
|
||||
* - graphContainer
|
||||
* - toolbarContainer
|
||||
*/
|
||||
export class EditorCodec extends ObjectCodec {
|
||||
constructor() {
|
||||
const __dummy: any = undefined;
|
||||
super(new Editor(__dummy), [
|
||||
'modified',
|
||||
'lastSnapshot',
|
||||
'ignoredChanges',
|
||||
'undoManager',
|
||||
'graphContainer',
|
||||
'toolbarContainer',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes the ui-part of the configuration node by reading
|
||||
* a sequence of the following child nodes and attributes
|
||||
* and passes the control to the default decoding mechanism:
|
||||
*
|
||||
* Child Nodes:
|
||||
*
|
||||
* stylesheet - Adds a CSS stylesheet to the document.
|
||||
* resource - Adds the basename of a resource bundle.
|
||||
* add - Creates or configures a known UI element.
|
||||
*
|
||||
* These elements may appear in any order given that the
|
||||
* graph UI element is added before the toolbar element
|
||||
* (see Known Keys).
|
||||
*
|
||||
* Attributes:
|
||||
*
|
||||
* as - Key for the UI element (see below).
|
||||
* element - ID for the element in the document.
|
||||
* style - CSS style to be used for the element or window.
|
||||
* x - X coordinate for the new window.
|
||||
* y - Y coordinate for the new window.
|
||||
* width - Width for the new window.
|
||||
* height - Optional height for the new window.
|
||||
* name - Name of the stylesheet (absolute/relative URL).
|
||||
* basename - Basename of the resource bundle (see {@link Resources}).
|
||||
*
|
||||
* The x, y, width and height attributes are used to create a new
|
||||
* <MaxWindow> if the element attribute is not specified in an add
|
||||
* node. The name and basename are only used in the stylesheet and
|
||||
* resource nodes, respectively.
|
||||
*
|
||||
* Known Keys:
|
||||
*
|
||||
* graph - Main graph element (see <Editor.setGraphContainer>).
|
||||
* title - Title element (see <Editor.setTitleContainer>).
|
||||
* toolbar - Toolbar element (see <Editor.setToolbarContainer>).
|
||||
* status - Status bar element (see <Editor.setStatusContainer>).
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```javascript
|
||||
* <ui>
|
||||
* <stylesheet name="css/process.css"/>
|
||||
* <resource basename="resources/app"/>
|
||||
* <add as="graph" element="graph"
|
||||
* style="left:70px;right:20px;top:20px;bottom:40px"/>
|
||||
* <add as="status" element="status"/>
|
||||
* <add as="toolbar" x="10" y="20" width="54"/>
|
||||
* </ui>
|
||||
* ```
|
||||
*/
|
||||
afterDecode(dec: Codec, node: Element, obj: any): any {
|
||||
// Assigns the specified templates for edges
|
||||
const defaultEdge = node.getAttribute('defaultEdge');
|
||||
|
||||
if (defaultEdge != null) {
|
||||
node.removeAttribute('defaultEdge');
|
||||
obj.defaultEdge = obj.templates[defaultEdge];
|
||||
}
|
||||
|
||||
// Assigns the specified templates for groups
|
||||
const defaultGroup = node.getAttribute('defaultGroup');
|
||||
|
||||
if (defaultGroup != null) {
|
||||
node.removeAttribute('defaultGroup');
|
||||
obj.defaultGroup = obj.templates[defaultGroup];
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides decode child to handle special child nodes.
|
||||
*/
|
||||
decodeChild(dec: Codec, child: Element, obj: any) {
|
||||
if (child.nodeName === 'Array') {
|
||||
const role = child.getAttribute('as');
|
||||
|
||||
if (role === 'templates') {
|
||||
this.decodeTemplates(dec, child, obj);
|
||||
return;
|
||||
}
|
||||
} else if (child.nodeName === 'ui') {
|
||||
this.decodeUi(dec, child, obj);
|
||||
return;
|
||||
}
|
||||
super.decodeChild.apply(this, [dec, child, obj]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes the ui elements from the given node.
|
||||
*/
|
||||
decodeUi(dec: Codec, node: Element, editor: Editor) {
|
||||
let tmp = <Element>node.firstChild;
|
||||
while (tmp != null) {
|
||||
if (tmp.nodeName === 'add') {
|
||||
const as = <string>tmp.getAttribute('as');
|
||||
const elt = tmp.getAttribute('element');
|
||||
const style = tmp.getAttribute('style');
|
||||
let element = null;
|
||||
|
||||
if (elt != null) {
|
||||
element = document.getElementById(elt);
|
||||
|
||||
if (element != null && style != null) {
|
||||
element.style.cssText += `;${style}`;
|
||||
}
|
||||
} else {
|
||||
const x = parseInt(<string>tmp.getAttribute('x'));
|
||||
const y = parseInt(<string>tmp.getAttribute('y'));
|
||||
const width = tmp.getAttribute('width') || null;
|
||||
const height = tmp.getAttribute('height') || null;
|
||||
|
||||
// Creates a new window around the element
|
||||
element = document.createElement('div');
|
||||
if (style != null) {
|
||||
element.style.cssText = style;
|
||||
}
|
||||
|
||||
const wnd = new MaxWindow(
|
||||
Translations.get(as) || as,
|
||||
element,
|
||||
x,
|
||||
y,
|
||||
width ? parseInt(width) : null,
|
||||
height ? parseInt(height) : null,
|
||||
false,
|
||||
true
|
||||
);
|
||||
wnd.setVisible(true);
|
||||
}
|
||||
|
||||
// TODO: Make more generic
|
||||
if (as === 'graph') {
|
||||
editor.setGraphContainer(element);
|
||||
} else if (as === 'toolbar') {
|
||||
editor.setToolbarContainer(element);
|
||||
} else if (as === 'title') {
|
||||
editor.setTitleContainer(element);
|
||||
} else if (as === 'status') {
|
||||
editor.setStatusContainer(element);
|
||||
} else if (as === 'map') {
|
||||
throw new Error('Unimplemented');
|
||||
//editor.setMapContainer(element);
|
||||
}
|
||||
} else if (tmp.nodeName === 'resource') {
|
||||
Translations.add(<string>tmp.getAttribute('basename'));
|
||||
} else if (tmp.nodeName === 'stylesheet') {
|
||||
addLinkToHead('stylesheet', <string>tmp.getAttribute('name'));
|
||||
}
|
||||
|
||||
tmp = <Element>tmp.nextSibling;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes the cells from the given node as templates.
|
||||
*/
|
||||
decodeTemplates(dec: Codec, node: Element, editor: Editor) {
|
||||
if (editor.templates == null) {
|
||||
editor.templates = [];
|
||||
}
|
||||
|
||||
const children = <Element[]>getChildNodes(node);
|
||||
for (let j = 0; j < children.length; j++) {
|
||||
const name = <string>children[j].getAttribute('as');
|
||||
let child = <Element | null>children[j].firstChild;
|
||||
|
||||
while (child != null && child.nodeType !== 1) {
|
||||
child = <Element | null>child.nextSibling;
|
||||
}
|
||||
|
||||
if (child != null) {
|
||||
// LATER: Only single cells means you need
|
||||
// to group multiple cells within another
|
||||
// cell. This should be changed to support
|
||||
// arrays of cells, or the wrapper must
|
||||
// be automatically handled in this class.
|
||||
editor.templates[name] = dec.decodeCell(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
Copyright 2023-present The maxGraph project Contributors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import ObjectCodec from '../../ObjectCodec';
|
||||
import type Codec from '../../Codec';
|
||||
import EditorKeyHandler from '../../../editor/EditorKeyHandler';
|
||||
|
||||
/**
|
||||
* Custom codec for configuring {@link EditorKeyHandler}s.
|
||||
*
|
||||
* This class is created and registered dynamically at load time and used implicitly via {@link Codec} and the {@link CodecRegistry}.
|
||||
*
|
||||
* This codec only reads configuration data for existing key handlers, it does not encode or create key handlers.
|
||||
*/
|
||||
export class EditorKeyHandlerCodec extends ObjectCodec {
|
||||
constructor() {
|
||||
super(new EditorKeyHandler());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `null`.
|
||||
*/
|
||||
encode(enc: Codec, obj: any) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a sequence of the following child nodes and attributes:
|
||||
*
|
||||
* Child Nodes:
|
||||
*
|
||||
* add - Binds a keystroke to an action name.
|
||||
*
|
||||
* Attributes:
|
||||
*
|
||||
* as - Keycode.
|
||||
* action - Action name to execute in editor.
|
||||
* control - Optional boolean indicating if
|
||||
* the control key must be pressed.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```javascript
|
||||
* <EditorKeyHandler as="keyHandler">
|
||||
* <add as="88" control="true" action="cut"/>
|
||||
* <add as="67" control="true" action="copy"/>
|
||||
* <add as="86" control="true" action="paste"/>
|
||||
* </EditorKeyHandler>
|
||||
* ```
|
||||
*
|
||||
* The keycodes are for the x, c and v keys.
|
||||
*
|
||||
* See also: <EditorKeyHandler.bindAction>, http://www.js-examples.com/page/tutorials__key_codes.html
|
||||
*/
|
||||
decode(dec: Codec, _node: Element, into: any) {
|
||||
if (into != null) {
|
||||
const { editor } = into;
|
||||
let node: Element | null = <Element | null>_node.firstChild;
|
||||
|
||||
while (node != null) {
|
||||
if (!this.processInclude(dec, node, into) && node.nodeName === 'add') {
|
||||
const as = node.getAttribute('as');
|
||||
const action = node.getAttribute('action');
|
||||
const control = node.getAttribute('control');
|
||||
|
||||
into.bindAction(as, action, control);
|
||||
}
|
||||
node = <Element | null>node.nextSibling;
|
||||
}
|
||||
}
|
||||
return into;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
Copyright 2023-present The maxGraph project Contributors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import ObjectCodec from '../../ObjectCodec';
|
||||
import type Codec from '../../Codec';
|
||||
import EditorPopupMenu from '../../../editor/EditorPopupMenu';
|
||||
|
||||
/**
|
||||
* Custom codec for configuring {@link EditorPopupMenu}s.
|
||||
*
|
||||
* This class is created and registered dynamically at load time and used implicitly via {@link Codec} and the {@link CodecRegistry}.
|
||||
*
|
||||
* This codec only reads configuration data for existing popup menus, it does not encode or create menus.
|
||||
* Note that this codec only passes the configuration node to the popup menu, which uses the config to dynamically create menus.
|
||||
*
|
||||
* @see {@link EditorPopupMenu.createMenu}.
|
||||
*/
|
||||
export class EditorPopupMenuCodec extends ObjectCodec {
|
||||
constructor() {
|
||||
super(new EditorPopupMenu());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns null.
|
||||
*/
|
||||
encode(_enc: Codec, _obj: Element): Element | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses the given node as the config for <EditorPopupMenu>.
|
||||
*/
|
||||
decode(dec: Codec, node: Element, into: any) {
|
||||
const inc = node.getElementsByTagName('include')[0];
|
||||
|
||||
if (inc != null) {
|
||||
this.processInclude(dec, inc, into);
|
||||
} else if (into != null) {
|
||||
into.config = node;
|
||||
}
|
||||
|
||||
return into;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,276 @@
|
|||
/*
|
||||
Copyright 2023-present The maxGraph project Contributors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import ObjectCodec from '../../ObjectCodec';
|
||||
import { EditorToolbar } from '../../../editor/EditorToolbar';
|
||||
import type Codec from '../../Codec';
|
||||
import type Editor from '../../../editor/Editor';
|
||||
import { NODETYPE } from '../../../util/Constants';
|
||||
import MaxLog from '../../../gui/MaxLog';
|
||||
import { convertPoint } from '../../../util/styleUtils';
|
||||
import { getClientX, getClientY } from '../../../util/EventUtils';
|
||||
import InternalEvent from '../../../view/event/InternalEvent';
|
||||
import { getChildNodes, getTextContent } from '../../../util/domUtils';
|
||||
import Translations from '../../../util/Translations';
|
||||
|
||||
/**
|
||||
* Custom codec for configuring {@link EditorToolbar}s.
|
||||
*
|
||||
* This class is created and registered dynamically at load time and used implicitly via {@link Codec} and the {@link CodecRegistry}.
|
||||
*
|
||||
* This codec only reads configuration data for existing toolbars handlers, it does not encode or create toolbars.
|
||||
*/
|
||||
export class EditorToolbarCodec extends ObjectCodec {
|
||||
constructor() {
|
||||
super(new EditorToolbar());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `null`.
|
||||
*/
|
||||
encode(_enc: any, _obj: any) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a sequence of the following child nodes and attributes:
|
||||
*
|
||||
* Child Nodes:
|
||||
*
|
||||
* add - Adds a new item to the toolbar. See below for attributes.
|
||||
* separator - Adds a vertical separator. No attributes.
|
||||
* hr - Adds a horizontal separator. No attributes.
|
||||
* br - Adds a linefeed. No attributes.
|
||||
*
|
||||
* Attributes:
|
||||
*
|
||||
* as - Resource key for the label.
|
||||
* action - Name of the action to execute in enclosing editor.
|
||||
* mode - Mode name (see below).
|
||||
* template - Template name for cell insertion.
|
||||
* style - Optional style to override the template style.
|
||||
* icon - Icon (relative/absolute URL).
|
||||
* pressedIcon - Optional icon for pressed state (relative/absolute URL).
|
||||
* id - Optional ID to be used for the created DOM element.
|
||||
* toggle - Optional 0 or 1 to disable toggling of the element. Default is 1 (true).
|
||||
*
|
||||
* The action, mode and template attributes are mutually exclusive. The style can only be used with the template attribute.
|
||||
* The add node may contain another sequence of add nodes with "as" and action attributes to create a combo box in the toolbar.
|
||||
* If the icon is specified then a list of the child node is expected to have its template attribute set and the action is ignored instead.
|
||||
*
|
||||
* Nodes with a specified template may define a function to be used for inserting the cloned template into the graph.
|
||||
* Here is an example of such a node:
|
||||
*
|
||||
* ```javascript
|
||||
* <add as="Swimlane" template="swimlane" icon="images/swimlane.gif"><![CDATA[
|
||||
* function (editor, cell, evt, targetCell)
|
||||
* {
|
||||
* let pt = mxUtils.convertPoint(
|
||||
* editor.graph.container, mxEvent.getClientX(evt),
|
||||
* mxEvent.getClientY(evt));
|
||||
* return editor.addVertex(targetCell, cell, pt.x, pt.y);
|
||||
* }
|
||||
* ]]></add>
|
||||
* ```
|
||||
*
|
||||
* In the above function, editor is the enclosing {@link Editor} instance, cell is the clone of the template, evt is the mouse event that represents the
|
||||
* drop and targetCell is the cell under the mouse pointer where the drop occurred. The targetCell is retrieved using {@link Graph#getCellAt}.
|
||||
*
|
||||
* Furthermore, nodes with the mode attribute may define a function to be executed upon selection of the respective toolbar icon. In the
|
||||
* example below, the default edge style is set when this specific
|
||||
* connect-mode is activated:
|
||||
*
|
||||
* ```javascript
|
||||
* <add as="connect" mode="connect"><![CDATA[
|
||||
* function (editor)
|
||||
* {
|
||||
* if (editor.defaultEdge != null)
|
||||
* {
|
||||
* editor.defaultEdge.style = 'straightEdge';
|
||||
* }
|
||||
* }
|
||||
* ]]></add>
|
||||
* ```
|
||||
*
|
||||
* Both functions require {@link allowEval} to be set to `true`.
|
||||
*
|
||||
* Modes:
|
||||
*
|
||||
* select - Left mouse button used for rubberband- & cell-selection.
|
||||
* connect - Allows connecting vertices by inserting new edges.
|
||||
* pan - Disables selection and switches to panning on the left button.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* To add items to the toolbar:
|
||||
*
|
||||
* ```javascript
|
||||
* <EditorToolbar as="toolbar">
|
||||
* <add as="save" action="save" icon="images/save.gif"/>
|
||||
* <br/><hr/>
|
||||
* <add as="select" mode="select" icon="images/select.gif"/>
|
||||
* <add as="connect" mode="connect" icon="images/connect.gif"/>
|
||||
* </EditorToolbar>
|
||||
* ```
|
||||
*/
|
||||
decode(dec: Codec, _node: Element, into: any) {
|
||||
if (into != null) {
|
||||
const editor: Editor = into.editor;
|
||||
let node: Element | null = <Element | null>_node.firstChild;
|
||||
|
||||
while (node != null) {
|
||||
if (node.nodeType === NODETYPE.ELEMENT) {
|
||||
if (!this.processInclude(dec, node, into)) {
|
||||
if (node.nodeName === 'separator') {
|
||||
into.addSeparator();
|
||||
} else if (node.nodeName === 'br') {
|
||||
into.toolbar.addBreak();
|
||||
} else if (node.nodeName === 'hr') {
|
||||
into.toolbar.addLine();
|
||||
} else if (node.nodeName === 'add') {
|
||||
let as = <string>node.getAttribute('as');
|
||||
as = Translations.get(as) || as;
|
||||
const icon = node.getAttribute('icon');
|
||||
const pressedIcon = node.getAttribute('pressedIcon');
|
||||
const action = node.getAttribute('action');
|
||||
const mode = node.getAttribute('mode');
|
||||
const template = node.getAttribute('template');
|
||||
const toggle = node.getAttribute('toggle') != '0';
|
||||
const text = getTextContent(<Text>(<unknown>node));
|
||||
let elt = null;
|
||||
let funct: any;
|
||||
|
||||
if (action != null) {
|
||||
elt = into.addItem(as, icon, action, pressedIcon);
|
||||
} else if (mode != null) {
|
||||
funct = EditorToolbarCodec.allowEval ? eval(text) : null;
|
||||
elt = into.addMode(as, icon, mode, pressedIcon, funct);
|
||||
} else if (template != null || (text != null && text.length > 0)) {
|
||||
let cell = template ? editor.templates[template] : null;
|
||||
const style = node.getAttribute('style');
|
||||
|
||||
if (cell != null && style != null) {
|
||||
cell = editor.graph.cloneCell(cell);
|
||||
cell.setStyle(style);
|
||||
}
|
||||
|
||||
let insertFunction = null;
|
||||
|
||||
if (text != null && text.length > 0 && EditorToolbarCodec.allowEval) {
|
||||
insertFunction = eval(text);
|
||||
}
|
||||
|
||||
elt = into.addPrototype(
|
||||
as,
|
||||
icon,
|
||||
cell,
|
||||
pressedIcon,
|
||||
insertFunction,
|
||||
toggle
|
||||
);
|
||||
} else {
|
||||
const children = getChildNodes(node);
|
||||
|
||||
if (children.length > 0) {
|
||||
if (icon == null) {
|
||||
const combo = into.addActionCombo(as);
|
||||
|
||||
for (let i = 0; i < children.length; i += 1) {
|
||||
const child = <Element>children[i];
|
||||
|
||||
if (child.nodeName === 'separator') {
|
||||
into.addOption(combo, '---');
|
||||
} else if (child.nodeName === 'add') {
|
||||
const lab = child.getAttribute('as');
|
||||
const act = child.getAttribute('action');
|
||||
into.addActionOption(combo, lab, act);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const select: HTMLSelectElement = into.addCombo();
|
||||
|
||||
const create = () => {
|
||||
const template = editor.templates[select.value];
|
||||
|
||||
if (template != null) {
|
||||
const clone = template.clone();
|
||||
// @ts-ignore
|
||||
const style = select.options[select.selectedIndex].cellStyle;
|
||||
|
||||
if (style != null) {
|
||||
clone.setStyle(style);
|
||||
}
|
||||
|
||||
return clone;
|
||||
}
|
||||
MaxLog.warn(`Template ${template} not found`);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const img = into.addPrototype(as, icon, create, null, null, toggle);
|
||||
|
||||
// Selects the toolbar icon if a selection change
|
||||
// is made in the corresponding combobox.
|
||||
InternalEvent.addListener(select, 'change', () => {
|
||||
into.toolbar.selectMode(img, (evt: MouseEvent) => {
|
||||
const pt = convertPoint(
|
||||
editor.graph.container,
|
||||
getClientX(evt),
|
||||
getClientY(evt)
|
||||
);
|
||||
|
||||
return editor.addVertex(null, funct(), pt.x, pt.y);
|
||||
});
|
||||
|
||||
into.toolbar.noReset = false;
|
||||
});
|
||||
|
||||
// Adds the entries to the combobox
|
||||
for (let i = 0; i < children.length; i += 1) {
|
||||
const child = <Element>children[i];
|
||||
|
||||
if (child.nodeName === 'separator') {
|
||||
into.addOption(select, '---');
|
||||
} else if (child.nodeName === 'add') {
|
||||
const lab = child.getAttribute('as');
|
||||
const tmp = child.getAttribute('template');
|
||||
const option = into.addOption(select, lab, tmp || template);
|
||||
option.cellStyle = child.getAttribute('style');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Assigns an ID to the created element to access it later.
|
||||
if (elt != null) {
|
||||
const id = node.getAttribute('id');
|
||||
|
||||
if (id != null && id.length > 0) {
|
||||
elt.setAttribute('id', id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
node = <Element | null>node.nextSibling;
|
||||
}
|
||||
}
|
||||
return into;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
Copyright 2023-present The maxGraph project Contributors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
export * from './EditorCodec';
|
||||
export * from './EditorKeyHandlerCodec';
|
||||
export * from './EditorPopupMenuCodec';
|
||||
export * from './EditorToolbarCodec';
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
Copyright 2023-present The maxGraph project Contributors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
export * from './editor';
|
||||
export * from './CellCodec';
|
||||
export * from './ChildChangeCodec';
|
||||
export * from './GenericChangeCodec';
|
||||
export * from './GraphViewCodec';
|
||||
export * from './ModelCodec';
|
||||
export * from './RootChangeCodec';
|
||||
export * from './StylesheetCodec';
|
||||
export * from './TerminalChangeCodec';
|
|
@ -0,0 +1,113 @@
|
|||
/*
|
||||
Copyright 2023-present The maxGraph project Contributors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import CodecRegistry from './CodecRegistry';
|
||||
import {
|
||||
CellCodec,
|
||||
ChildChangeCodec,
|
||||
EditorCodec,
|
||||
EditorKeyHandlerCodec,
|
||||
EditorPopupMenuCodec,
|
||||
EditorToolbarCodec,
|
||||
GenericChangeCodec,
|
||||
GraphViewCodec,
|
||||
ModelCodec,
|
||||
RootChangeCodec,
|
||||
StylesheetCodec,
|
||||
TerminalChangeCodec,
|
||||
} from './codecs';
|
||||
import CellAttributeChange from '../view/undoable_changes/CellAttributeChange';
|
||||
import CollapseChange from '../view/undoable_changes/CollapseChange';
|
||||
import GeometryChange from '../view/undoable_changes/GeometryChange';
|
||||
import StyleChange from '../view/undoable_changes/StyleChange';
|
||||
import ValueChange from '../view/undoable_changes/ValueChange';
|
||||
import VisibleChange from '../view/undoable_changes/VisibleChange';
|
||||
|
||||
const registerGenericChangeCodecs = () => {
|
||||
const __dummy: any = undefined;
|
||||
CodecRegistry.register(
|
||||
new GenericChangeCodec(new CellAttributeChange(__dummy, __dummy, __dummy), 'value')
|
||||
);
|
||||
CodecRegistry.register(
|
||||
new GenericChangeCodec(new CollapseChange(__dummy, __dummy, __dummy), 'collapsed')
|
||||
);
|
||||
CodecRegistry.register(
|
||||
new GenericChangeCodec(new GeometryChange(__dummy, __dummy, __dummy), 'geometry')
|
||||
);
|
||||
CodecRegistry.register(
|
||||
new GenericChangeCodec(new StyleChange(__dummy, __dummy, __dummy), 'style')
|
||||
);
|
||||
CodecRegistry.register(
|
||||
new GenericChangeCodec(new ValueChange(__dummy, __dummy, __dummy), 'value')
|
||||
);
|
||||
CodecRegistry.register(
|
||||
new GenericChangeCodec(new VisibleChange(__dummy, __dummy, __dummy), 'visible')
|
||||
);
|
||||
};
|
||||
|
||||
let isCoreCodecsRegistered = false;
|
||||
|
||||
/**
|
||||
* Register core editor i.e. codecs that don't relate to editor.
|
||||
*
|
||||
* @param force if `true` register the codecs even if they were already registered. If false, only register them
|
||||
* if they have never been registered before.
|
||||
* @since 0.6.0
|
||||
*/
|
||||
export const registerCoreCodecs = (force = false) => {
|
||||
if (!isCoreCodecsRegistered || force) {
|
||||
CodecRegistry.register(new CellCodec());
|
||||
CodecRegistry.register(new ChildChangeCodec());
|
||||
CodecRegistry.register(new GraphViewCodec());
|
||||
CodecRegistry.register(new ModelCodec());
|
||||
CodecRegistry.register(new RootChangeCodec());
|
||||
CodecRegistry.register(new StylesheetCodec());
|
||||
CodecRegistry.register(new TerminalChangeCodec());
|
||||
registerGenericChangeCodecs();
|
||||
|
||||
isCoreCodecsRegistered = true;
|
||||
}
|
||||
};
|
||||
|
||||
let isEditorCodecsRegistered = false;
|
||||
/**
|
||||
* Register only editor codecs.
|
||||
* @param force if `true` register the codecs even if they were already registered. If false, only register them
|
||||
* if they have never been registered before.
|
||||
* @since 0.6.0
|
||||
*/
|
||||
export const registerEditorCodecs = (force = false) => {
|
||||
if (!isEditorCodecsRegistered || force) {
|
||||
CodecRegistry.register(new EditorCodec());
|
||||
CodecRegistry.register(new EditorKeyHandlerCodec());
|
||||
CodecRegistry.register(new EditorPopupMenuCodec());
|
||||
CodecRegistry.register(new EditorToolbarCodec());
|
||||
|
||||
isEditorCodecsRegistered = true;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Register all editors i.e. core codecs (as done by {@link registerCoreCodecs}) and editor codecs (as done by {@link registerEditorCodecs}).
|
||||
*
|
||||
* @param force if `true` register the codecs even if they were already registered. If false, only register them
|
||||
* if they have never been registered before.
|
||||
* @since 0.6.0
|
||||
*/
|
||||
export const registerAllCodecs = (force = false) => {
|
||||
registerCoreCodecs(force);
|
||||
registerEditorCodecs(force);
|
||||
};
|
|
@ -32,8 +32,6 @@ import TerminalChange from './undoable_changes/TerminalChange';
|
|||
import ValueChange from './undoable_changes/ValueChange';
|
||||
import VisibleChange from './undoable_changes/VisibleChange';
|
||||
import Geometry from './geometry/Geometry';
|
||||
import ObjectCodec from '../serialization/ObjectCodec';
|
||||
import CodecRegistry from '../serialization/CodecRegistry';
|
||||
import { cloneCells, filterCells } from '../util/cellArrayUtils';
|
||||
|
||||
import type { CellStyle, FilterFunction } from '../types';
|
||||
|
@ -1200,61 +1198,4 @@ export class GraphDataModel extends EventSource {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Codec for <Transactions>s. This class is created and registered
|
||||
* dynamically at load time and used implicitly via <Codec>
|
||||
* and the <CodecRegistry>.
|
||||
*/
|
||||
export class ModelCodec extends ObjectCodec {
|
||||
constructor() {
|
||||
super(new GraphDataModel());
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes the given <Transactions> by writing a (flat) XML sequence of
|
||||
* cell nodes as produced by the <CellCodec>. The sequence is
|
||||
* wrapped-up in a node with the name root.
|
||||
*/
|
||||
encodeObject(enc: any, obj: Cell, node: Element) {
|
||||
const rootNode = enc.document.createElement('root');
|
||||
enc.encodeCell(obj.getRoot(), rootNode);
|
||||
node.appendChild(rootNode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides decode child to handle special child nodes.
|
||||
*/
|
||||
decodeChild(dec: any, child: Element, obj: Cell | GraphDataModel) {
|
||||
if (child.nodeName === 'root') {
|
||||
this.decodeRoot(dec, child, <GraphDataModel>obj);
|
||||
} else {
|
||||
this.decodeChild.apply(this, [dec, child, obj]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the cells into the graph model. All cells
|
||||
* are children of the root element in the node.
|
||||
*/
|
||||
decodeRoot(dec: any, root: Element, model: GraphDataModel) {
|
||||
let rootCell = null;
|
||||
let tmp = root.firstChild;
|
||||
|
||||
while (tmp != null) {
|
||||
const cell = dec.decodeCell(tmp);
|
||||
|
||||
if (cell != null && cell.getParent() == null) {
|
||||
rootCell = cell;
|
||||
}
|
||||
tmp = tmp.nextSibling;
|
||||
}
|
||||
|
||||
// Sets the root on the model if one has been decoded
|
||||
if (rootCell != null) {
|
||||
model.setRoot(rootCell);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CodecRegistry.register(new ModelCodec());
|
||||
export default GraphDataModel;
|
||||
|
|
|
@ -47,9 +47,6 @@ import StyleRegistry from './style/StyleRegistry';
|
|||
import TooltipHandler from './handler/TooltipHandler';
|
||||
import { MouseEventListener } from '../types';
|
||||
|
||||
import ObjectCodec from '../serialization/ObjectCodec';
|
||||
import CodecRegistry from '../serialization/CodecRegistry';
|
||||
|
||||
/**
|
||||
* @class GraphView
|
||||
* @extends {EventSource}
|
||||
|
@ -57,7 +54,7 @@ import CodecRegistry from '../serialization/CodecRegistry';
|
|||
* Extends {@link EventSource} to implement a view for a graph. This class is in
|
||||
* charge of computing the absolute coordinates for the relative child
|
||||
* geometries, the points for perimeters and edge styles and keeping them
|
||||
* cached in {@link mxCellStates} for faster retrieval. The states are updated
|
||||
* cached in {@link CellState}s for faster retrieval. The states are updated
|
||||
* whenever the model or the view state (translate, scale) changes. The scale
|
||||
* and translate are honoured in the bounds.
|
||||
*
|
||||
|
@ -2393,160 +2390,4 @@ export class GraphView extends EventSource {
|
|||
moveHandler: MouseEventListener | null = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom encoder for {@link GraphView}s. This class is created
|
||||
* and registered dynamically at load time and used implicitly via
|
||||
* <Codec> and the <CodecRegistry>. This codec only writes views
|
||||
* into a XML format that can be used to create an image for
|
||||
* the graph, that is, it contains absolute coordinates with
|
||||
* computed perimeters, edge styles and cell styles.
|
||||
*/
|
||||
export class GraphViewCodec extends ObjectCodec {
|
||||
constructor() {
|
||||
const __dummy: any = undefined;
|
||||
super(new GraphView(__dummy));
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes the given {@link GraphView} using <encodeCell>
|
||||
* starting at the model's root. This returns the
|
||||
* top-level graph node of the recursive encoding.
|
||||
*/
|
||||
encode(enc: any, view: GraphView) {
|
||||
return this.encodeCell(enc, view, <Cell>view.graph.getDataModel().getRoot());
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively encodes the specifed cell. Uses layer
|
||||
* as the default nodename. If the cell's parent is
|
||||
* null, then graph is used for the nodename. If
|
||||
* <Transactions.isEdge> returns true for the cell,
|
||||
* then edge is used for the nodename, else if
|
||||
* <Transactions.isVertex> returns true for the cell,
|
||||
* then vertex is used for the nodename.
|
||||
*
|
||||
* {@link Graph#getLabel} is used to create the label
|
||||
* attribute for the cell. For graph nodes and vertices
|
||||
* the bounds are encoded into x, y, width and height.
|
||||
* For edges the points are encoded into a points
|
||||
* attribute as a space-separated list of comma-separated
|
||||
* coordinate pairs (eg. x0,y0 x1,y1 ... xn,yn). All
|
||||
* values from the cell style are added as attribute
|
||||
* values to the node.
|
||||
*/
|
||||
encodeCell(enc: any, view: GraphView, cell: Cell) {
|
||||
let node;
|
||||
const model = view.graph.getDataModel();
|
||||
const state = view.getState(cell);
|
||||
const parent = cell.getParent();
|
||||
|
||||
if (parent == null || state != null) {
|
||||
const childCount = cell.getChildCount();
|
||||
const geo = cell.getGeometry();
|
||||
let name = null;
|
||||
|
||||
if (parent === model.getRoot()) {
|
||||
name = 'layer';
|
||||
} else if (parent == null) {
|
||||
name = 'graph';
|
||||
} else if (cell.isEdge()) {
|
||||
name = 'edge';
|
||||
} else if (childCount > 0 && geo != null) {
|
||||
name = 'group';
|
||||
} else if (cell.isVertex()) {
|
||||
name = 'vertex';
|
||||
}
|
||||
|
||||
if (name != null) {
|
||||
node = enc.document.createElement(name);
|
||||
const lab = view.graph.getLabel(cell);
|
||||
|
||||
if (lab != null) {
|
||||
node.setAttribute('label', view.graph.getLabel(cell));
|
||||
|
||||
if (view.graph.isHtmlLabel(cell)) {
|
||||
node.setAttribute('html', true);
|
||||
}
|
||||
}
|
||||
|
||||
if (parent == null) {
|
||||
const bounds = view.getGraphBounds();
|
||||
|
||||
if (bounds != null) {
|
||||
node.setAttribute('x', Math.round(bounds.x));
|
||||
node.setAttribute('y', Math.round(bounds.y));
|
||||
node.setAttribute('width', Math.round(bounds.width));
|
||||
node.setAttribute('height', Math.round(bounds.height));
|
||||
}
|
||||
|
||||
node.setAttribute('scale', view.scale);
|
||||
} else if (state != null && geo != null) {
|
||||
// Writes each key, value in the style pair to an attribute
|
||||
for (const i in state.style) {
|
||||
// @ts-ignore
|
||||
let value = state.style[i];
|
||||
|
||||
// Tries to turn objects and functions into strings
|
||||
if (typeof value === 'function' && typeof value === 'object') {
|
||||
value = StyleRegistry.getName(value);
|
||||
}
|
||||
|
||||
if (
|
||||
value != null &&
|
||||
typeof value !== 'function' &&
|
||||
typeof value !== 'object'
|
||||
) {
|
||||
node.setAttribute(i, value);
|
||||
}
|
||||
}
|
||||
|
||||
const abs = state.absolutePoints;
|
||||
|
||||
// Writes the list of points into one attribute
|
||||
if (abs != null && abs.length > 0) {
|
||||
let pts = `${Math.round((<Point>abs[0]).x)},${Math.round((<Point>abs[0]).y)}`;
|
||||
|
||||
for (let i = 1; i < abs.length; i += 1) {
|
||||
pts += ` ${Math.round((<Point>abs[i]).x)},${Math.round((<Point>abs[i]).y)}`;
|
||||
}
|
||||
|
||||
node.setAttribute('points', pts);
|
||||
}
|
||||
|
||||
// Writes the bounds into 4 attributes
|
||||
else {
|
||||
node.setAttribute('x', Math.round(state.x));
|
||||
node.setAttribute('y', Math.round(state.y));
|
||||
node.setAttribute('width', Math.round(state.width));
|
||||
node.setAttribute('height', Math.round(state.height));
|
||||
}
|
||||
|
||||
const offset = state.absoluteOffset;
|
||||
|
||||
// Writes the offset into 2 attributes
|
||||
if (offset != null) {
|
||||
if (offset.x !== 0) {
|
||||
node.setAttribute('dx', Math.round(offset.x));
|
||||
}
|
||||
|
||||
if (offset.y !== 0) {
|
||||
node.setAttribute('dy', Math.round(offset.y));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < childCount; i += 1) {
|
||||
const childNode = this.encodeCell(enc, view, cell.getChildAt(i));
|
||||
|
||||
if (childNode != null) {
|
||||
node.appendChild(childNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
CodecRegistry.register(new GraphViewCodec());
|
||||
export default GraphView;
|
||||
|
|
|
@ -23,11 +23,6 @@ import { clone } from '../../util/cloneUtils';
|
|||
import Point from '../geometry/Point';
|
||||
import CellPath from './CellPath';
|
||||
import { isNotNullish } from '../../util/Utils';
|
||||
import ObjectCodec from '../../serialization/ObjectCodec';
|
||||
import CodecRegistry from '../../serialization/CodecRegistry';
|
||||
import { removeWhitespace } from '../../util/StringUtils';
|
||||
import { importNode } from '../../util/domUtils';
|
||||
import Codec from '../../serialization/Codec';
|
||||
|
||||
import type { CellStyle, FilterFunction, IdentityObject } from '../../types';
|
||||
|
||||
|
@ -884,164 +879,4 @@ export class Cell implements IdentityObject {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Codec for <Cell>s. This class is created and registered
|
||||
* dynamically at load time and used implicitly via <Codec>
|
||||
* and the <CodecRegistry>.
|
||||
*
|
||||
* Transient Fields:
|
||||
*
|
||||
* - children
|
||||
* - edges
|
||||
* - overlays
|
||||
* - mxTransient
|
||||
*
|
||||
* Reference Fields:
|
||||
*
|
||||
* - parent
|
||||
* - source
|
||||
* - target
|
||||
*
|
||||
* Transient fields can be added using the following code:
|
||||
*
|
||||
* CodecRegistry.getCodec(mxCell).exclude.push('name_of_field');
|
||||
*
|
||||
* To subclass <Cell>, replace the template and add an alias as
|
||||
* follows.
|
||||
*
|
||||
* ```javascript
|
||||
* function CustomCell(value, geometry, style)
|
||||
* {
|
||||
* mxCell.apply(this, arguments);
|
||||
* }
|
||||
*
|
||||
* mxUtils.extend(CustomCell, mxCell);
|
||||
*
|
||||
* CodecRegistry.getCodec(mxCell).template = new CustomCell();
|
||||
* CodecRegistry.addAlias('CustomCell', 'mxCell');
|
||||
* ```
|
||||
*/
|
||||
export class CellCodec extends ObjectCodec {
|
||||
constructor() {
|
||||
super(
|
||||
new Cell(),
|
||||
['children', 'edges', 'overlays', 'mxTransient'],
|
||||
['parent', 'source', 'target']
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true since this is a cell codec.
|
||||
*/
|
||||
isCellCodec() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overidden to disable conversion of value to number.
|
||||
*/
|
||||
isNumericAttribute(dec: Codec, attr: Element, obj: any) {
|
||||
return attr.nodeName !== 'value' && super.isNumericAttribute(dec, attr, obj);
|
||||
}
|
||||
|
||||
/**
|
||||
* Excludes user objects that are XML nodes.
|
||||
*/
|
||||
isExcluded(obj: any, attr: string, value: Element, isWrite: boolean) {
|
||||
return (
|
||||
super.isExcluded(obj, attr, value, isWrite) ||
|
||||
(isWrite && attr === 'value' && value.nodeType === NODETYPE.ELEMENT)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes an <Cell> and wraps the XML up inside the
|
||||
* XML of the user object (inversion).
|
||||
*/
|
||||
afterEncode(enc: Codec, obj: Cell, node: Element) {
|
||||
if (obj.value != null && obj.value.nodeType === NODETYPE.ELEMENT) {
|
||||
// Wraps the graphical annotation up in the user object (inversion)
|
||||
// by putting the result of the default encoding into a clone of the
|
||||
// user object (node type 1) and returning this cloned user object.
|
||||
const tmp = node;
|
||||
node = importNode(enc.document, obj.value, true);
|
||||
node.appendChild(tmp);
|
||||
|
||||
// Moves the id attribute to the outermost XML node, namely the
|
||||
// node which denotes the object boundaries in the file.
|
||||
const id = tmp.getAttribute('id');
|
||||
node.setAttribute('id', String(id));
|
||||
tmp.removeAttribute('id');
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes an <Cell> and uses the enclosing XML node as
|
||||
* the user object for the cell (inversion).
|
||||
*/
|
||||
beforeDecode(dec: Codec, node: Element, obj: Cell): Element | null {
|
||||
let inner: Element | null = <Element>node.cloneNode(true);
|
||||
const classname = this.getName();
|
||||
|
||||
if (node.nodeName !== classname) {
|
||||
// Passes the inner graphical annotation node to the
|
||||
// object codec for further processing of the cell.
|
||||
const tmp = node.getElementsByTagName(classname)[0];
|
||||
|
||||
if (tmp != null && tmp.parentNode === node) {
|
||||
removeWhitespace(<HTMLElement>tmp, true);
|
||||
removeWhitespace(<HTMLElement>tmp, false);
|
||||
tmp.parentNode.removeChild(tmp);
|
||||
inner = tmp;
|
||||
} else {
|
||||
inner = null;
|
||||
}
|
||||
|
||||
// Creates the user object out of the XML node
|
||||
obj.value = node.cloneNode(true);
|
||||
const id = obj.value.getAttribute('id');
|
||||
|
||||
if (id != null) {
|
||||
obj.setId(id);
|
||||
obj.value.removeAttribute('id');
|
||||
}
|
||||
} else {
|
||||
// Uses ID from XML file as ID for cell in model
|
||||
obj.setId(<string>node.getAttribute('id'));
|
||||
}
|
||||
|
||||
// Preprocesses and removes all Id-references in order to use the
|
||||
// correct encoder (this) for the known references to cells (all).
|
||||
if (inner != null) {
|
||||
for (let i = 0; i < this.idrefs.length; i += 1) {
|
||||
const attr = this.idrefs[i];
|
||||
const ref = inner.getAttribute(attr);
|
||||
|
||||
if (ref != null) {
|
||||
inner.removeAttribute(attr);
|
||||
let object = dec.objects[ref] || dec.lookup(ref);
|
||||
|
||||
if (object == null) {
|
||||
// Needs to decode forward reference
|
||||
const element = dec.getElementById(ref);
|
||||
|
||||
if (element != null) {
|
||||
const decoder = CodecRegistry.codecs[element.nodeName] || this;
|
||||
object = decoder.decode(dec, element);
|
||||
}
|
||||
}
|
||||
|
||||
// @ts-ignore dynamic assignment was in original implementation
|
||||
obj[attr] = object;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return inner;
|
||||
}
|
||||
}
|
||||
|
||||
CodecRegistry.register(new CellCodec());
|
||||
export default Cell;
|
||||
|
|
|
@ -19,15 +19,6 @@ limitations under the License.
|
|||
import { ALIGN, ARROW, SHAPE } from '../../util/Constants';
|
||||
import Perimeter from './Perimeter';
|
||||
import { clone } from '../../util/cloneUtils';
|
||||
import { isNumeric } from '../../util/mathUtils';
|
||||
import CodecRegistry from '../../serialization/CodecRegistry';
|
||||
import { NODETYPE } from '../../util/Constants';
|
||||
import MaxLog from '../../gui/MaxLog';
|
||||
import StyleRegistry from './StyleRegistry';
|
||||
import ObjectCodec from '../../serialization/ObjectCodec';
|
||||
import { getTextContent } from '../../util/domUtils';
|
||||
import Codec from '../../serialization/Codec';
|
||||
|
||||
import type { CellStateStyle, CellStyle } from '../../types';
|
||||
|
||||
/**
|
||||
|
@ -207,179 +198,3 @@ export class Stylesheet {
|
|||
return style;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Codec for {@link Stylesheet}s. This class is created and registered
|
||||
* dynamically at load time and used implicitly via <Codec>
|
||||
* and the <CodecRegistry>.
|
||||
*/
|
||||
export class StylesheetCodec extends ObjectCodec {
|
||||
constructor() {
|
||||
super(new Stylesheet());
|
||||
}
|
||||
|
||||
/**
|
||||
* Static global switch that specifies if the use of eval is allowed for
|
||||
* evaluating text content. Default is true. Set this to false if stylesheets
|
||||
* may contain user input.
|
||||
*/
|
||||
static allowEval = true;
|
||||
|
||||
/**
|
||||
* Encodes a stylesheet. See <decode> for a description of the
|
||||
* format.
|
||||
*/
|
||||
encode(enc: Codec, obj: any): Element {
|
||||
const node = enc.document.createElement(this.getName());
|
||||
|
||||
for (const i in obj.styles) {
|
||||
const style = obj.styles[i];
|
||||
const styleNode = enc.document.createElement('add');
|
||||
|
||||
if (i != null) {
|
||||
styleNode.setAttribute('as', i);
|
||||
|
||||
for (const j in style) {
|
||||
const value = this.getStringValue(j, style[j]);
|
||||
|
||||
if (value != null) {
|
||||
const entry = enc.document.createElement('add');
|
||||
entry.setAttribute('value', value);
|
||||
entry.setAttribute('as', j);
|
||||
styleNode.appendChild(entry);
|
||||
}
|
||||
}
|
||||
|
||||
if (styleNode.childNodes.length > 0) {
|
||||
node.appendChild(styleNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the string for encoding the given value.
|
||||
*/
|
||||
getStringValue(key: string, value: any): string | null {
|
||||
const type = typeof value;
|
||||
|
||||
if (type === 'function') {
|
||||
value = StyleRegistry.getName(value);
|
||||
} else if (type === 'object') {
|
||||
value = null;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a sequence of the following child nodes
|
||||
* and attributes:
|
||||
*
|
||||
* Child Nodes:
|
||||
*
|
||||
* add - Adds a new style.
|
||||
*
|
||||
* Attributes:
|
||||
*
|
||||
* as - Name of the style.
|
||||
* extend - Name of the style to inherit from.
|
||||
*
|
||||
* Each node contains another sequence of add and remove nodes with the following
|
||||
* attributes:
|
||||
*
|
||||
* as - Name of the style (see {@link Constants}).
|
||||
* value - Value for the style.
|
||||
*
|
||||
* Instead of the value-attribute, one can put Javascript expressions into
|
||||
* the node as follows if <StylesheetCodec.allowEval> is true:
|
||||
* <add as="perimeter">mxPerimeter.RectanglePerimeter</add>
|
||||
*
|
||||
* A remove node will remove the entry with the name given in the as-attribute
|
||||
* from the style.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```javascript
|
||||
* <mxStylesheet as="stylesheet">
|
||||
* <add as="text">
|
||||
* <add as="fontSize" value="12"/>
|
||||
* </add>
|
||||
* <add as="defaultVertex" extend="text">
|
||||
* <add as="shape" value="rectangle"/>
|
||||
* </add>
|
||||
* </mxStylesheet>
|
||||
* ```
|
||||
*/
|
||||
decode(dec: Codec, _node: Element, into: any): any {
|
||||
const obj = into || new this.template.constructor();
|
||||
const id = _node.getAttribute('id');
|
||||
|
||||
if (id != null) {
|
||||
dec.objects[id] = obj;
|
||||
}
|
||||
|
||||
let node: Element | ChildNode | null = _node.firstChild;
|
||||
|
||||
while (node != null) {
|
||||
if (!this.processInclude(dec, <Element>node, obj) && node.nodeName === 'add') {
|
||||
const as = (<Element>node).getAttribute('as');
|
||||
|
||||
if (as != null) {
|
||||
const extend = (<Element>node).getAttribute('extend');
|
||||
let style = extend != null ? clone(obj.styles[extend]) : null;
|
||||
|
||||
if (style == null) {
|
||||
if (extend != null) {
|
||||
MaxLog.warn(
|
||||
`StylesheetCodec.decode: stylesheet ${extend} not found to extend`
|
||||
);
|
||||
}
|
||||
|
||||
style = {};
|
||||
}
|
||||
|
||||
let entry = node.firstChild;
|
||||
|
||||
while (entry != null) {
|
||||
if (entry.nodeType === NODETYPE.ELEMENT) {
|
||||
const key = <string>(<Element>entry).getAttribute('as');
|
||||
|
||||
if (entry.nodeName === 'add') {
|
||||
const text = getTextContent(<Text>(<unknown>entry));
|
||||
let value = null;
|
||||
|
||||
if (text != null && text.length > 0 && StylesheetCodec.allowEval) {
|
||||
value = eval(text);
|
||||
} else {
|
||||
value = (<Element>entry).getAttribute('value');
|
||||
|
||||
if (isNumeric(value)) {
|
||||
value = parseFloat(<string>value);
|
||||
}
|
||||
}
|
||||
|
||||
if (value != null) {
|
||||
style[key] = value;
|
||||
}
|
||||
} else if (entry.nodeName === 'remove') {
|
||||
delete style[key];
|
||||
}
|
||||
}
|
||||
|
||||
entry = entry.nextSibling;
|
||||
}
|
||||
|
||||
obj.putCellStyle(as, style);
|
||||
}
|
||||
}
|
||||
|
||||
node = node.nextSibling;
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
|
||||
CodecRegistry.register(new StylesheetCodec());
|
||||
|
|
|
@ -16,8 +16,6 @@ limitations under the License.
|
|||
|
||||
import { isNullish } from '../../util/Utils';
|
||||
import Cell from '../cell/Cell';
|
||||
import CodecRegistry from '../../serialization/CodecRegistry';
|
||||
import GenericChangeCodec from './GenericChangeCodec';
|
||||
|
||||
import type { UndoableChange } from '../../types';
|
||||
|
||||
|
@ -81,8 +79,4 @@ class CellAttributeChange implements UndoableChange {
|
|||
}
|
||||
}
|
||||
|
||||
const __dummy: any = undefined;
|
||||
CodecRegistry.register(
|
||||
new GenericChangeCodec(new CellAttributeChange(__dummy, __dummy, __dummy), 'value')
|
||||
);
|
||||
export default CellAttributeChange;
|
||||
|
|
|
@ -16,12 +16,8 @@ limitations under the License.
|
|||
|
||||
import GraphDataModel from '../GraphDataModel';
|
||||
import Cell from '../cell/Cell';
|
||||
import ObjectCodec from '../../serialization/ObjectCodec';
|
||||
import CodecRegistry from '../../serialization/CodecRegistry';
|
||||
import { NODETYPE } from '../../util/Constants';
|
||||
|
||||
import type { UndoableChange } from '../../types';
|
||||
import Codec from '../../serialization/Codec';
|
||||
|
||||
/**
|
||||
* Action to add or remove a child in a model.
|
||||
|
@ -114,145 +110,4 @@ export class ChildChange implements UndoableChange {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Codec for {@link ChildChange}s. This class is created and registered
|
||||
* dynamically at load time and used implicitly via <Codec> and
|
||||
* the <CodecRegistry>.
|
||||
*
|
||||
* Transient Fields:
|
||||
*
|
||||
* - model
|
||||
* - previous
|
||||
* - previousIndex
|
||||
* - child
|
||||
*
|
||||
* Reference Fields:
|
||||
*
|
||||
* - parent
|
||||
*/
|
||||
export class ChildChangeCodec extends ObjectCodec {
|
||||
constructor() {
|
||||
const __dummy: any = undefined;
|
||||
super(
|
||||
new ChildChange(__dummy, __dummy, __dummy),
|
||||
['model', 'child', 'previousIndex'],
|
||||
['parent', 'previous']
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true for the child attribute if the child
|
||||
* cell had a previous parent or if we're reading the
|
||||
* child as an attribute rather than a child node, in
|
||||
* which case it's always a reference.
|
||||
*/
|
||||
isReference(obj: any, attr: string, value: any, isWrite: boolean) {
|
||||
if (attr === 'child' && (!isWrite || obj.model.contains(obj.previous))) {
|
||||
return true;
|
||||
}
|
||||
return this.idrefs.indexOf(attr) >= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Excludes references to parent or previous if not in the model.
|
||||
*/
|
||||
isExcluded(obj: any, attr: string, value: any, write: boolean) {
|
||||
return (
|
||||
super.isExcluded(obj, attr, value, write) ||
|
||||
(write &&
|
||||
value != null &&
|
||||
(attr === 'previous' || attr === 'parent') &&
|
||||
!obj.model.contains(value))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes the child recusively and adds the result
|
||||
* to the given node.
|
||||
*/
|
||||
afterEncode(enc: Codec, obj: any, node: Element) {
|
||||
if (this.isReference(obj, 'child', obj.child, true)) {
|
||||
// Encodes as reference (id)
|
||||
node.setAttribute('child', enc.getId(obj.child));
|
||||
} else {
|
||||
// At this point, the encoder is no longer able to know which cells
|
||||
// are new, so we have to encode the complete cell hierarchy and
|
||||
// ignore the ones that are already there at decoding time. Note:
|
||||
// This can only be resolved by moving the notify event into the
|
||||
// execute of the edit.
|
||||
enc.encodeCell(obj.child, node);
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes the any child nodes as using the respective
|
||||
* codec from the registry.
|
||||
*/
|
||||
beforeDecode(dec: Codec, _node: Element, obj: any): any {
|
||||
if (_node.firstChild != null && _node.firstChild.nodeType === NODETYPE.ELEMENT) {
|
||||
// Makes sure the original node isn't modified
|
||||
const node = _node.cloneNode(true);
|
||||
|
||||
let tmp = <Element>node.firstChild;
|
||||
obj.child = dec.decodeCell(tmp, false);
|
||||
|
||||
let tmp2 = <Element>tmp.nextSibling;
|
||||
(<Element>tmp.parentNode).removeChild(tmp);
|
||||
tmp = tmp2;
|
||||
|
||||
while (tmp != null) {
|
||||
tmp2 = <Element>tmp.nextSibling;
|
||||
|
||||
if (tmp.nodeType === NODETYPE.ELEMENT) {
|
||||
// Ignores all existing cells because those do not need to
|
||||
// be re-inserted into the model. Since the encoded version
|
||||
// of these cells contains the new parent, this would leave
|
||||
// to an inconsistent state on the model (ie. a parent
|
||||
// change without a call to parentForCellChanged).
|
||||
const id = <string>tmp.getAttribute('id');
|
||||
|
||||
if (dec.lookup(id) == null) {
|
||||
dec.decodeCell(tmp);
|
||||
}
|
||||
}
|
||||
|
||||
(<Element>tmp.parentNode).removeChild(tmp);
|
||||
tmp = tmp2;
|
||||
}
|
||||
|
||||
return node;
|
||||
} else {
|
||||
const childRef = <string>_node.getAttribute('child');
|
||||
obj.child = dec.getObject(childRef);
|
||||
return _node;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Restores object state in the child change.
|
||||
*/
|
||||
afterDecode(dec: Codec, node: Element, obj: any): any {
|
||||
// Cells are decoded here after a complete transaction so the previous
|
||||
// parent must be restored on the cell for the case where the cell was
|
||||
// added. This is needed for the local model to identify the cell as a
|
||||
// new cell and register the ID.
|
||||
if (obj.child != null) {
|
||||
if (
|
||||
obj.child.parent != null &&
|
||||
obj.previous != null &&
|
||||
obj.child.parent !== obj.previous
|
||||
) {
|
||||
obj.previous = obj.child.parent;
|
||||
}
|
||||
|
||||
obj.child.parent = obj.previous;
|
||||
obj.previous = obj.parent;
|
||||
obj.previousIndex = obj.index;
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
|
||||
CodecRegistry.register(new ChildChangeCodec());
|
||||
export default ChildChange;
|
||||
|
|
|
@ -16,8 +16,6 @@ limitations under the License.
|
|||
|
||||
import Cell from '../cell/Cell';
|
||||
import GraphDataModel from '../GraphDataModel';
|
||||
import CodecRegistry from '../../serialization/CodecRegistry';
|
||||
import GenericChangeCodec from './GenericChangeCodec';
|
||||
|
||||
import type { UndoableChange } from '../../types';
|
||||
|
||||
|
@ -52,8 +50,4 @@ class CollapseChange implements UndoableChange {
|
|||
}
|
||||
}
|
||||
|
||||
const __dummy: any = undefined;
|
||||
CodecRegistry.register(
|
||||
new GenericChangeCodec(new CollapseChange(__dummy, __dummy, __dummy), 'collapsed')
|
||||
);
|
||||
export default CollapseChange;
|
||||
|
|
|
@ -17,8 +17,6 @@ limitations under the License.
|
|||
import Geometry from '../geometry/Geometry';
|
||||
import Cell from '../cell/Cell';
|
||||
import GraphDataModel from '../GraphDataModel';
|
||||
import CodecRegistry from '../../serialization/CodecRegistry';
|
||||
import GenericChangeCodec from './GenericChangeCodec';
|
||||
|
||||
import type { UndoableChange } from '../../types';
|
||||
|
||||
|
@ -53,8 +51,4 @@ class GeometryChange implements UndoableChange {
|
|||
}
|
||||
}
|
||||
|
||||
const __dummy: any = undefined;
|
||||
CodecRegistry.register(
|
||||
new GenericChangeCodec(new GeometryChange(__dummy, __dummy, __dummy), 'geometry')
|
||||
);
|
||||
export default GeometryChange;
|
||||
|
|
|
@ -16,12 +16,8 @@ limitations under the License.
|
|||
|
||||
import Cell from '../cell/Cell';
|
||||
import GraphDataModel from '../GraphDataModel';
|
||||
import CodecRegistry from '../../serialization/CodecRegistry';
|
||||
import { NODETYPE } from '../../util/Constants';
|
||||
import ObjectCodec from '../../serialization/ObjectCodec';
|
||||
|
||||
import type { UndoableChange } from '../../types';
|
||||
import Codec from '../../serialization/Codec';
|
||||
|
||||
/**
|
||||
* Action to change the root in a model.
|
||||
|
@ -54,66 +50,4 @@ export class RootChange implements UndoableChange {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Codec for {@link RootChange}s. This class is created and registered
|
||||
* dynamically at load time and used implicitly via <Codec> and
|
||||
* the <CodecRegistry>.
|
||||
*
|
||||
* Transient Fields:
|
||||
*
|
||||
* - model
|
||||
* - previous
|
||||
* - root
|
||||
*/
|
||||
export class RootChangeCodec extends ObjectCodec {
|
||||
constructor() {
|
||||
const __dummy: any = undefined;
|
||||
super(new RootChange(__dummy, __dummy), ['model', 'previous', 'root']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes the child recursively.
|
||||
*/
|
||||
afterEncode(enc: Codec, obj: any, node: Element) {
|
||||
enc.encodeCell(obj.root, node);
|
||||
return node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes the optional children as cells
|
||||
* using the respective decoder.
|
||||
*/
|
||||
beforeDecode(dec: Codec, node: Element, obj: any): any {
|
||||
if (node.firstChild != null && node.firstChild.nodeType === NODETYPE.ELEMENT) {
|
||||
// Makes sure the original node isn't modified
|
||||
node = <Element>node.cloneNode(true);
|
||||
|
||||
let tmp = <Element>node.firstChild;
|
||||
obj.root = dec.decodeCell(tmp, false);
|
||||
|
||||
let tmp2 = <Element>tmp.nextSibling;
|
||||
(<Element>tmp.parentNode).removeChild(tmp);
|
||||
tmp = tmp2;
|
||||
|
||||
while (tmp != null) {
|
||||
tmp2 = <Element>tmp.nextSibling;
|
||||
dec.decodeCell(tmp);
|
||||
(<Element>tmp.parentNode).removeChild(tmp);
|
||||
tmp = tmp2;
|
||||
}
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restores the state by assigning the previous value.
|
||||
*/
|
||||
afterDecode(dec: Codec, node: Element, obj: any): any {
|
||||
obj.previous = obj.root;
|
||||
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
|
||||
CodecRegistry.register(new RootChangeCodec());
|
||||
export default RootChange;
|
||||
|
|
|
@ -16,8 +16,6 @@ limitations under the License.
|
|||
|
||||
import Cell from '../cell/Cell';
|
||||
import GraphDataModel from '../GraphDataModel';
|
||||
import CodecRegistry from '../../serialization/CodecRegistry';
|
||||
import GenericChangeCodec from './GenericChangeCodec';
|
||||
|
||||
import type { CellStyle, UndoableChange } from '../../types';
|
||||
|
||||
|
@ -49,8 +47,4 @@ class StyleChange implements UndoableChange {
|
|||
}
|
||||
}
|
||||
|
||||
const __dummy: any = undefined;
|
||||
CodecRegistry.register(
|
||||
new GenericChangeCodec(new StyleChange(__dummy, __dummy, __dummy), 'style')
|
||||
);
|
||||
export default StyleChange;
|
||||
|
|
|
@ -16,19 +16,11 @@ limitations under the License.
|
|||
|
||||
import Cell from '../cell/Cell';
|
||||
import GraphDataModel from '../GraphDataModel';
|
||||
import ObjectCodec from '../../serialization/ObjectCodec';
|
||||
import CodecRegistry from '../../serialization/CodecRegistry';
|
||||
|
||||
import type { UndoableChange } from '../../types';
|
||||
import Codec from '../../serialization/Codec';
|
||||
|
||||
/**
|
||||
* Action to change a terminal in a model.
|
||||
*
|
||||
* Constructor: mxTerminalChange
|
||||
*
|
||||
* Constructs a change of a terminal in the
|
||||
* specified model.
|
||||
*/
|
||||
export class TerminalChange implements UndoableChange {
|
||||
model: GraphDataModel;
|
||||
|
@ -59,39 +51,4 @@ export class TerminalChange implements UndoableChange {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Codec for {@link TerminalChange}s. This class is created and registered
|
||||
* dynamically at load time and used implicitly via <Codec> and
|
||||
* the <CodecRegistry>.
|
||||
*
|
||||
* Transient Fields:
|
||||
*
|
||||
* - model
|
||||
* - previous
|
||||
*
|
||||
* Reference Fields:
|
||||
*
|
||||
* - cell
|
||||
* - terminal
|
||||
*/
|
||||
export class TerminalChangeCodec extends ObjectCodec {
|
||||
constructor() {
|
||||
const __dummy: any = undefined;
|
||||
super(
|
||||
new TerminalChange(__dummy, __dummy, __dummy, __dummy),
|
||||
['model', 'previous'],
|
||||
['cell', 'terminal']
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Restores the state by assigning the previous value.
|
||||
*/
|
||||
afterDecode(dec: Codec, node: Element, obj: any): any {
|
||||
obj.previous = obj.terminal;
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
|
||||
CodecRegistry.register(new TerminalChangeCodec());
|
||||
export default TerminalChange;
|
||||
|
|
|
@ -16,8 +16,6 @@ limitations under the License.
|
|||
|
||||
import Cell from '../cell/Cell';
|
||||
import GraphDataModel from '../GraphDataModel';
|
||||
import CodecRegistry from '../../serialization/CodecRegistry';
|
||||
import GenericChangeCodec from './GenericChangeCodec';
|
||||
|
||||
import type { UndoableChange } from '../../types';
|
||||
|
||||
|
@ -52,8 +50,4 @@ class ValueChange implements UndoableChange {
|
|||
}
|
||||
}
|
||||
|
||||
const __dummy: any = undefined;
|
||||
CodecRegistry.register(
|
||||
new GenericChangeCodec(new ValueChange(__dummy, __dummy, __dummy), 'value')
|
||||
);
|
||||
export default ValueChange;
|
||||
|
|
|
@ -16,8 +16,6 @@ limitations under the License.
|
|||
|
||||
import Cell from '../cell/Cell';
|
||||
import GraphDataModel from '../GraphDataModel';
|
||||
import CodecRegistry from '../../serialization/CodecRegistry';
|
||||
import GenericChangeCodec from './GenericChangeCodec';
|
||||
|
||||
import type { UndoableChange } from '../../types';
|
||||
|
||||
|
@ -52,8 +50,4 @@ class VisibleChange implements UndoableChange {
|
|||
}
|
||||
}
|
||||
|
||||
const __dummy: any = undefined;
|
||||
CodecRegistry.register(
|
||||
new GenericChangeCodec(new VisibleChange(__dummy, __dummy, __dummy), 'visible')
|
||||
);
|
||||
export default VisibleChange;
|
||||
|
|
|
@ -27,7 +27,7 @@ export default defineConfig(({ mode }) => {
|
|||
},
|
||||
},
|
||||
},
|
||||
chunkSizeWarningLimit: 562, // @maxgraph/core
|
||||
chunkSizeWarningLimit: 468, // @maxgraph/core
|
||||
},
|
||||
};
|
||||
});
|
||||
|
|
|
@ -535,6 +535,16 @@ This is documented in the [mxStylesheet documentation](https://github.com/jgraph
|
|||
This is currently not supported in maxGraph: https://github.com/maxGraph/maxGraph/issues/154 "Add a way to not use default style properties when calculating cell styles".
|
||||
|
||||
|
||||
### Codecs
|
||||
|
||||
:::warning
|
||||
|
||||
From version 0.6.0 of `maxGraph`, codecs supplied by maxGraph are no longer registered by default, they ** MUST** be registered before performing an `encode` or `decode`
|
||||
|
||||
For example, you can use the `registerCoreCodecs` function (or other related functions) to register codecs.
|
||||
|
||||
:::
|
||||
|
||||
## Conclusion
|
||||
|
||||
By following these guidelines and updating your codebase accordingly, you should be able to migrate your application from `mxGraph` to `maxGraph`.
|
||||
|
|
Loading…
Reference in New Issue