Build infrastructure and Angular 15 upgrade

This commit is contained in:
aboyko
2023-02-28 19:22:33 -05:00
parent aa27ebeb3f
commit edfcdb2b02
101 changed files with 8009 additions and 25625 deletions

View File

@@ -0,0 +1,278 @@
import { dia, g } from 'jointjs';
import { Observable } from 'rxjs';
import * as _joint from 'jointjs';
import $ from 'jquery';
export namespace Flo {
export const joint: any = _joint;
export enum DnDEventType {
DRAG,
DROP
}
export interface DnDEvent {
type: DnDEventType;
view: dia.CellView;
event: MouseEvent;
}
export interface PropertyMetadata {
readonly id: string;
readonly name: string;
readonly description?: string;
readonly defaultValue?: any;
readonly type?: string;
readonly [propName: string]: any;
}
export interface ExtraMetadata {
readonly titleProperty?: string;
readonly noEditableProps?: boolean;
readonly noPaletteEntry?: boolean;
readonly unselectable?: boolean;
readonly [propName: string]: any;
readonly allowAdditionalProperties?: boolean; //TODO: Verify it is still needed
}
export interface ElementMetadata {
readonly name: string;
readonly group: string;
readonly metadata?: ExtraMetadata;
readonly [propName: string]: any;
description?(): Promise<string>;
get(property: String): Promise<PropertyMetadata>;
properties(): Promise<Map<string, PropertyMetadata>>;
}
export interface ViewerDescriptor {
readonly graph?: dia.Graph;
readonly paper?: dia.Paper;
}
export interface MetamodelListener {
metadataError(data: any): void;
metadataAboutToChange(): void;
metadataChanged(): void;
}
export interface Metamodel {
textToGraph(flo: EditorContext, dsl: string): Promise<any>;
graphToText(flo: EditorContext): Promise<string>;
load(): Promise<Map<string, Map<string, ElementMetadata>>>;
groups(): Array<string>;
refresh?(): Promise<Map<string, Map<string, ElementMetadata>>>;
subscribe?(listener: MetamodelListener): void;
unsubscribe?(listener: MetamodelListener): void;
isValidPropertyValue?(element: dia.Element, property: string, value: any): boolean;
}
export interface CreationParams {
metadata?: ElementMetadata;
props?: Map<string, any>;
}
export interface ElementCreationParams extends CreationParams {
position?: dia.Point;
}
export interface LinkCreationParams extends CreationParams {
source: string;
target: string;
}
export interface EmbeddedChildCreationParams extends CreationParams {
parent: dia.Cell;
position?: dia.Point;
}
export interface DecorationCreationParams extends EmbeddedChildCreationParams {
kind: string;
messages: Array<string>;
}
export interface HandleCreationParams extends EmbeddedChildCreationParams {
kind: string;
}
export interface PaletteRenderer {
createGroupHeader(title: string, isOpen: boolean): dia.Element;
onOpen?(groupView: dia.CellView): Promise<any>;
onClose?(groupView: dia.CellView): Promise<any>;
}
export interface Renderer {
createNode?(viewerDescriptor: ViewerDescriptor, metadata: ElementMetadata, props?: Map<string, any>): dia.Element;
createLink?(source: LinkEnd, target: LinkEnd, metadata?: ElementMetadata, props?: Map<string, any>): dia.Link;
createHandle?(kind: string, parent: dia.Cell): dia.Element;
createDecoration?(kind: string, parent: dia.Cell): dia.Element;
getPaletteRenderer?(): PaletteRenderer;
initializeNewNode?(node: dia.Element, viewerDescriptor: ViewerDescriptor): void;
initializeNewLink?(link: dia.Link, viewerDescriptor: ViewerDescriptor): void;
initializeNewHandle?(handle: dia.Element, viewerDescriptor: ViewerDescriptor): void;
initializeNewDecoration?(decoration: dia.Element, viewerDescriptor: ViewerDescriptor): void;
getNodeView?(): dia.ElementView;
getLinkView?(): dia.LinkView;
layout?(paper: dia.Paper): Promise<any>;
handleLinkEvent?(context: EditorContext, event: string, link: dia.Link): void;
isSemanticProperty?(propertyPath: string, element: dia.Cell): boolean;
refreshVisuals?(cell: dia.Cell, propertyPath: string, paper: dia.Paper): void;
getLinkAnchorPoint?(linkView: dia.LinkView, view: dia.ElementView, port: SVGElement, reference: dia.Point): dia.Point;
markersChanged?(cell: dia.Cell, paper: dia.Paper): void;
}
export interface EditorContext {
readonly textToGraphConversionObservable: Observable<void>;
readonly graphToTextConversionObservable: Observable<void>;
readonly paletteReady: Observable<boolean>;
zoomPercent: number;
gridSize: number;
readOnlyCanvas: boolean;
selection: dia.CellView;
graphToTextSync: boolean;
noPalette: boolean;
setDsl(dsl: string): void;
updateGraph(): Promise<any>;
updateText(): Promise<any>;
performLayout(): Promise<void>;
clearGraph(): Promise<void>;
getGraph(): dia.Graph;
getPaper(): dia.Paper;
getMinZoom(): number;
getMaxZoom(): number;
getZoomStep(): number;
fitToPage(): void;
createNode(metadata: ElementMetadata, props?: Map<string, any>, position?: dia.Point): dia.Element;
createLink(source: LinkEnd, target: LinkEnd, metadata?: ElementMetadata, props?: Map<string, any>): dia.Link;
deleteSelectedNode(): void;
delete(cell: dia.Cell): void;
[propName: string]: any;
}
export interface LinkEndDescriptor {
view: dia.CellView;
cssClassSelector?: string;
}
export interface DnDDescriptor {
sourceComponent?: string;
range?: number;
source?: LinkEndDescriptor;
target?: LinkEndDescriptor;
}
export interface LinkEnd {
id: string | number;
selector?: string;
port?: string;
magnet?: string;
}
export enum Severity {
Error,
Warning
}
export interface Marker {
severity: Severity;
message: string;
range?: Range;
}
export interface Position {
ch: number;
line: number;
}
export interface Range {
start: Position;
end: Position;
}
export interface Editor {
interactive?: ((cellView: dia.CellView, event: string) => boolean) | boolean | dia.CellView.InteractivityOptions;
allowLinkVertexEdit?: boolean;
highlighting?: any;
createHandles?(context: EditorContext, createHandle: (owner: dia.CellView, kind: string, action: () => void, location: dia.Point) => void, owner: dia.CellView): void;
validatePort?(context: EditorContext, view: dia.CellView, magnet: SVGElement): boolean;
validateLink?(context: EditorContext, cellViewS: dia.CellView, portS: SVGElement, cellViewT: dia.CellView, portT: SVGElement, isSource: boolean, linkView: dia.LinkView): boolean;
calculateDragDescriptor?(context: EditorContext, draggedView: dia.CellView, targetUnderMouse: dia.CellView, coordinate: g.Point, sourceComponent: string): DnDDescriptor;
handleNodeDropping?(context: EditorContext, dragDescriptor: DnDDescriptor): void;
showDragFeedback?(context: EditorContext, dragDescriptor: DnDDescriptor): void;
hideDragFeedback?(context: EditorContext, dragDescriptor: DnDDescriptor): void;
validate?(graph: dia.Graph, dsl: string, flo: EditorContext): Promise<Map<string | number, Array<Marker>>>;
preDelete?(context: EditorContext, deletedElement: dia.Cell): boolean;
setDefaultContent?(editorContext: EditorContext, data: Map<string, Map<string, ElementMetadata>>): void;
}
export function findMagnetByClass(view: dia.CellView, className: string): SVGElement | undefined {
if (className && className.startsWith('.')) {
className = className.substr(1);
}
const element = view.$('[magnet]').toArray().find((magnet: any) => magnet.getAttribute('class').split(/\s+/).indexOf(className) >= 0);
if (element) {
return view.findMagnet($(element));
}
}
export function findMagnetByPort(view: dia.CellView, port: string): SVGElement | undefined {
const element = view.$('[magnet]').toArray().find((magnet: HTMLElement) => magnet.getAttribute('port') === port);
if (element) {
return view.findMagnet($(element));
}
}
/**
* Return the metadata for a particular palette entry in a particular group.
* @param name - name of the palette entry
* @param group - group in which the palette entry should exist (e.g. sinks)
* @return
*/
export function getMetadata(metamodel: Map<string, Map<string, ElementMetadata>>, name: string, group: string): ElementMetadata | undefined {
const groupObj = metamodel && group ? metamodel.get(group) : undefined;
if (name && groupObj && groupObj.get(name)) {
return metamodel!.get(group)!.get(name);
} else {
return {
name: name,
group: group,
unresolved: true,
get: (property: string) => new Promise<PropertyMetadata>(resolve => resolve(undefined)),
properties: () => Promise.resolve(new Map<string, PropertyMetadata>())
};
}
}
export function getScrollbarWidth() {
// Creating invisible container
const outer = document.createElement('div');
outer.style.visibility = 'hidden';
outer.style.overflow = 'scroll'; // forcing scrollbar to appear
// outer.style.msOverflowStyle = 'scrollbar'; // needed for WinJS apps
document.body.appendChild(outer);
// Creating inner element and placing it in the container
const inner = document.createElement('div');
outer.appendChild(inner);
// Calculating difference between container's full width and the child width
const scrollbarWidth = (outer.offsetWidth - inner.offsetWidth);
// Removing temporary elements from the DOM
outer.parentNode.removeChild(outer);
return scrollbarWidth;
}
export const SCROLLBAR_WIDTH = getScrollbarWidth();
}

View File

@@ -0,0 +1,390 @@
import { dia } from 'jointjs';
import { ValidatorFn, AsyncValidatorFn, AbstractControl, ValidationErrors } from '@angular/forms'
import { Flo } from './flo-common';
import { Subject, Observable } from 'rxjs'
import {debounceTime, switchMap} from 'rxjs/operators';
export namespace Properties {
export enum InputType {
TEXT,
NUMBER,
SELECT,
CHECKBOX,
PASSWORD,
EMAIL,
URL,
CODE
}
export interface Property {
readonly id: string;
readonly name: string;
readonly type?: string;
readonly description?: string;
readonly defaultValue?: any;
value?: any;
readonly valueOptions?: any[]
readonly [propName: string]: any;
}
export interface PropertyFilter {
accept: (property: Property) => boolean;
}
export interface SelectOption {
name: string;
value: any;
}
export interface ErrorData {
id: string;
message: string;
}
export interface Validation {
validator?: ValidatorFn|ValidatorFn[]|null,
asyncValidator?: AsyncValidatorFn|AsyncValidatorFn[]|null,
errorData?: Array<ErrorData>;
}
export interface ControlModel<T> {
readonly type: InputType;
readonly id: string;
value: T;
readonly defaultValue: T;
readonly name?: string;
readonly description?: string;
readonly property: Property
readonly validation?: Validation;
}
export interface CodeControlModel<T> extends ControlModel<T> {
readonly language: string;
}
export class GenericControlModel<T> implements ControlModel<T> {
constructor(private _property: Property, public type: InputType, public validation?: Validation) {}
get id() {
return this.property.id;
}
get name() {
return this.property.name;
}
get description() {
return this.property.description;
}
get defaultValue() {
return this.property.defaultValue;
}
get value(): T {
return this.getValue();
}
set value(value: T) {
this.setValue(value);
}
get property(): Property {
return this._property;
}
protected setValue(value: T) {
this.property.value = value;
}
protected getValue(): T {
return this.property.value;
}
}
export class CheckBoxControlModel extends GenericControlModel<boolean> {
constructor(_property: Property, validation?: Validation) {
super(_property, InputType.CHECKBOX, validation);
}
protected getValue() {
const res = super.getValue();
const type = typeof res;
switch (type) {
case 'boolean':
return res;
case 'string':
switch ((<any>res).trim().toLowerCase()) {
case 'true':
case '1':
return true;
case 'false':
case '0':
return false;
default:
return this.property.defaultValue
}
case 'number':
const num = <any> res;
if (num === 0) {
return false;
} else if (num === 1) {
return true;
} else {
return this.property.defaultValue
}
}
return this.property.defaultValue;
}
}
export abstract class AbstractCodeControlModel extends GenericControlModel<string> implements CodeControlModel<string> {
abstract language: string;
constructor(_property: Property, private encode?: (s: string) => string, private decode?: (s: string) => string, validation?: Validation) {
super(_property, InputType.CODE, validation);
}
set value(value: string) {
if (value && this.encode) {
super.setValue(this.encode(value));
} else {
super.setValue(value);
}
}
get value(): string {
let dsl = super.getValue();
if (dsl && this.decode) {
return this.decode(dsl);
} else {
return dsl;
}
}
}
export class GenericCodeControlModel extends AbstractCodeControlModel {
constructor(_property: Property, public language: string, encode?: (s: string) => string, decode?: (s: string) => string, validation?: Validation) {
super(_property, encode, decode, validation);
}
}
export class CodeControlModelWithDynamicLanguageProperty extends AbstractCodeControlModel {
private _langControlModel: Properties.ControlModel<any>;
constructor(_property: Properties.Property, private _languagePropertyName: string,
private _groupModel: Properties.PropertiesGroupModel,
encode?: (s: string) => string, decode?: (s: string) => string, validation?: Validation) {
super(_property, encode, decode, validation);
}
get language(): string {
const value = this.languageControlModel.value;
return value ? value : this.languageControlModel.defaultValue;
}
get languageControlModel(): Properties.ControlModel<any> {
if (!this._langControlModel) {
// Cast to Properties.ControlModel<any> from Properties.ControlModel<any> | undefined
// Should not be undefined!
this._langControlModel = <Properties.ControlModel<any>> this._groupModel.getControlsModels().find(c => c.id === this._languagePropertyName);
}
return this._langControlModel;
}
}
export class GenericListControlModel extends GenericControlModel<string> {
constructor(property: Property, validation?: Validation) {
super(property, InputType.TEXT, validation);
}
get value(): string {
return this.property.value ? this.property.value.toString().trim().split(/\s*,\s*/).join(', ') : '';
}
set value(value: string) {
this.property.value = value && value.trim() ? value.split(/\s*,\s*/).join(',') : undefined;
}
}
export class SelectControlModel extends GenericControlModel<any> {
constructor(_property: Property, type: InputType, public options: Array<SelectOption>, validation?: Validation) {
super(_property, type, validation);
if (_property.defaultValue === undefined) {
options.unshift({
name: 'SELECT',
value: _property.defaultValue
})
}
}
}
export interface PropertiesSource {
getProperties(): Promise<Property[]>;
applyChanges(properties: Property[]): void;
}
export class DefaultCellPropertiesSource implements PropertiesSource {
protected cell: dia.Cell;
constructor(cell: dia.Cell) {
this.cell = cell;
}
getProperties(): Promise<Array<Property>> {
let metadata: Flo.ElementMetadata = this.cell.get('metadata');
return Promise.resolve(metadata.properties().then(propsMetadata => Array.from(propsMetadata.values()).map(m => this.createProperty(m))));
}
protected createProperty(metadata: Flo.PropertyMetadata): Property {
return {
id: metadata.id,
name: metadata.name,
type: metadata.type,
defaultValue: metadata.defaultValue,
attr: `props/${metadata.name}`,
value: this.cell.attr(`props/${metadata.name}`),
description: metadata.description,
valueOptions: metadata.options
}
}
applyChanges(properties: Property[]) {
this.cell.trigger('batch:start', { batchName: 'update properties' });
properties.forEach(property => {
if ((typeof property.value === 'boolean' && !property.defaultValue && !property.value) ||
(property.value === property.defaultValue || property.value === '' || property.value === undefined || property.value === null)) {
let currentValue = this.cell.attr(property.attr);
if (currentValue !== undefined && currentValue !== null) {
// Remove attr doesn't fire appropriate event. Set default value first as a workaround to schedule DSL resync
// this.cell.attr(property.attr, property.defaultValue === undefined ? null : property.defaultValue);
this.cell.attr(property.attr, null);
this.cell.removeAttr(property.attr);
}
} else {
this.cell.attr(property.attr, property.value);
}
});
this.cell.trigger('batch:stop', { batchName: 'update properties' });
}
}
export class PropertiesGroupModel {
protected propertiesSource: PropertiesSource;
protected controlModels: Array<ControlModel<any>>;
protected loading = true;
protected _loadedSubject: Subject<boolean>;
constructor(propertiesSource: PropertiesSource) {
this.propertiesSource = propertiesSource;
}
load() {
this.loading = true;
this._loadedSubject = new Subject<boolean>();
this.propertiesSource.getProperties().then(properties => {
this.controlModels = properties.map(p => this.createControlModel(p));
this.loading = false;
this._loadedSubject.next(true);
this._loadedSubject.complete();
});
}
get isLoading(): boolean {
return this.loading;
}
get loadedSubject() {
return this._loadedSubject;
}
getControlsModels() {
return this.controlModels;
}
protected createControlModel(property: Property): ControlModel<any> {
return new GenericControlModel(property, InputType.TEXT);
}
public applyChanges(): void {
if (this.loading) {
return;
}
let properties = this.controlModels.map(cm => cm.property);
this.propertiesSource.applyChanges(properties);
}
}
const UNIQUE_RESOURCE_ERROR = {uniqueResource: true};
export namespace Validators {
export function uniqueResource(service: (value: any) => Observable<boolean>, debounceDuration: number): AsyncValidatorFn {
return (control: AbstractControl): Observable<ValidationErrors> => {
return new Observable<ValidationErrors>(obs => {
if (control.valueChanges) {
return control.valueChanges.pipe(
debounceTime(debounceDuration),
switchMap(() => service(control.value)),
).subscribe((res) => {
if (res) {
obs.next(undefined);
} else {
obs.next(UNIQUE_RESOURCE_ERROR);
}
obs.complete();
}, () => {
obs.next(UNIQUE_RESOURCE_ERROR);
obs.complete();
});
} else {
service(control.value).subscribe((res) => {
if (res) {
obs.next(undefined);
} else {
obs.next(UNIQUE_RESOURCE_ERROR);
}
obs.complete();
}, () => {
obs.next(UNIQUE_RESOURCE_ERROR);
obs.complete();
})
}
});
}
}
export function noneOf(excluded: Array<any>): ValidatorFn {
return (control: AbstractControl): {[key: string]: any} => {
return excluded.find(e => e === control.value) ? {'noneOf': {value: control.value}} : {};
};
}
}
}

View File

@@ -0,0 +1,28 @@
import { isDevMode } from '@angular/core';
export class Logger {
constructor() {
}
static log(value: any, ...rest: any[]) {
if (isDevMode()) {
console.log(value, ...rest);
}
}
static debug(value: any, ...rest: any[]) {
if (isDevMode()) {
console.debug(value, ...rest);
}
}
static error(value: any, ...rest: any[]) {
console.error(value, ...rest);
}
static warn(value: any, ...rest: any[]) {
console.warn(value, ...rest);
}
}

View File

@@ -0,0 +1,805 @@
import { dia } from 'jointjs';
import { Flo } from './flo-common';
import template from 'lodash/template';
import isFunction from 'lodash/isFunction';
import $ from 'jquery';
const joint: any = Flo.joint;
const isChrome: boolean = /Chrome/.test(navigator.userAgent) && /Google Inc/.test(navigator.vendor);
const isFF: boolean = navigator.userAgent.indexOf('Firefox') > 0;
const IMAGE_W = 120;
const IMAGE_H = 35;
const ERROR_MARKER_SIZE: dia.Size = {width: 16, height: 16};
const HANDLE_SIZE: dia.Size = {width: 10, height: 10};
const HANDLE_ICON_MAP: Map<string, string> = new Map<string, string>();
const REMOVE = 'remove';
HANDLE_ICON_MAP.set(REMOVE, 'icons/delete.svg');
const DECORATION_ICON_MAP: Map<string, string> = new Map<string, string>();
const ERROR = 'error';
DECORATION_ICON_MAP.set(ERROR, 'icons/error.svg');
export function loadShapes() {
joint.shapes.flo = {};
joint.shapes.flo.NODE_TYPE = 'sinspctr.IntNode';
joint.shapes.flo.LINK_TYPE = 'sinspctr.Link';
joint.shapes.flo.DECORATION_TYPE = 'decoration';
joint.shapes.flo.HANDLE_TYPE = 'handle';
// joint.util.cloneDeep = (obj: any) => {
// return _.cloneDeepWith(obj, (o) => {
// if (_.isObject(o) && !_.isPlainObject(o)) {
// return o;
// }
// });
// };
joint.util.filter.redscale = (args: Shapes.FilterOptions) => {
let amount = Number.isFinite(args.amount) ? args.amount : 1;
return template(
'<filter><feColorMatrix type="matrix" values="${a} ${b} ${c} 0 ${d} ${e} ${f} ${g} 0 0 ${h} ${i} ${k} 0 0 0 0 0 1 0"/></filter>',
<any>{
a: 1 - 0.96 * amount,
b: 0.95 * amount,
c: 0.01 * amount,
d: 0.3 * amount,
e: 0.2 * amount,
f: 1 - 0.9 * amount,
g: 0.7 * amount,
h: 0.05 * amount,
i: 0.05 * amount,
k: 1 - 0.1 * amount
}
);
};
joint.util.filter.orangescale = (args: Shapes.FilterOptions) => {
let amount = Number.isFinite(args.amount) ? args.amount : 1;
return template(
'<filter><feColorMatrix type="matrix" values="${a} ${b} ${c} 0 ${d} ${e} ${f} ${g} 0 ${h} ${i} ${k} ${l} 0 0 0 0 0 1 0"/></filter>',
<any>{
a: 1.0 + 0.5 * amount,
b: 1.4 * amount,
c: 0.2 * amount,
d: 0.3 * amount,
e: 0.3 * amount,
f: 1 + 0.05 * amount,
g: 0.2 * amount,
h: 0.15 * amount,
i: 0.3 * amount,
k: 0.3 * amount,
l: 1 - 0.6 * amount
}
);
};
joint.shapes.flo.Node = joint.shapes.basic.Generic.extend({
markup:
'<g class="shape"><image class="image" /></g>' +
'<rect class="border-white"/>' +
'<rect class="border"/>' +
'<rect class="box"/>' +
'<text class="label"/>' +
'<text class="label2"></text>' +
'<rect class="input-port" />' +
'<rect class="output-port"/>' +
'<rect class="output-port-cover"/>',
defaults: joint.util.deepSupplement({
type: joint.shapes.flo.NODE_TYPE,
position: {x: 0, y: 0},
size: { width: IMAGE_W, height: IMAGE_H },
attrs: {
'.': { magnet: false },
// rounded edges around image
'.border': {
refWidth: 1,
refHeight: 1,
rx: 3,
ry: 3,
'fill-opacity': 0, // see through
stroke: '#eeeeee',
'stroke-width': 0
},
'.box': {
refWidth: 1,
refHeight: 1,
rx: 3,
ry: 3,
//'fill-opacity': 0, // see through
stroke: '#6db33f',
fill: '#eeeeee',
'stroke-width': 1
},
'.input-port': {
idp: 'input',
port: 'input',
height: 8, width: 8,
magnet: true,
fill: '#eeeeee',
transform: 'translate(' + -4 + ',' + ((IMAGE_H / 2 ) - 4) + ')',
stroke: '#34302d',
'stroke-width': 1
},
'.output-port': {
id: 'output',
port: 'output',
height: 8, width: 8,
magnet: true,
fill: '#eeeeee',
transform: 'translate(' + (IMAGE_W - 4) + ',' + ((IMAGE_H / 2) - 4) + ')',
stroke: '#34302d',
'stroke-width': 1
},
'.label': {
'text-anchor': 'middle',
'ref-x': 0.5, // jointjs specific: relative position to ref'd element
// 'ref-y': -12, // jointjs specific: relative position to ref'd element
'ref-y': 0.3,
ref: '.border', // jointjs specific: element for ref-x, ref-y
fill: 'black',
'font-size': 14
},
'.label2': {
'text': '\u21d2',
'text-anchor': 'middle',
'ref-x': 0.15, // jointjs specific: relative position to ref'd element
'ref-y': 0.2, // jointjs specific: relative position to ref'd element
ref: '.border', // jointjs specific: element for ref-x, ref-y
// transform: 'translate(' + (IMAGE_W/2) + ',' + (IMAGE_H/2) + ')',
fill: 'black',
'font-size': 24
},
'.shape': {
},
'.image': {
refWidth: 1,
refHeight: 1,
}
}
}, joint.shapes.basic.Generic.prototype.defaults)
});
joint.shapes.flo.Link = joint.dia.Link.extend({
defaults: joint.util.deepSupplement({
type: joint.shapes.flo.LINK_TYPE,
attrs: {
'.connection': { stroke: '#34302d', 'stroke-width': 2 },
// Lots of alternatives that have been played with:
// '.smoooth': true
// '.marker-source': { stroke: '#9B59B6', fill: '#9B59B6', d: 'M24.316,5.318,9.833,13.682,9.833,5.5,5.5,5.5,5.5,25.5,9.833,25.5,9.833,17.318,24.316,25.682z' },
// '.marker-target': { stroke: '#F39C12', fill: '#F39C12', d: 'M14.615,4.928c0.487-0.986,1.284-0.986,1.771,0l2.249,4.554c0.486,0.986,1.775,1.923,2.864,2.081l5.024,0.73c1.089,0.158,1.335,0.916,0.547,1.684l-3.636,3.544c-0.788,0.769-1.28,2.283-1.095,3.368l0.859,5.004c0.186,1.085-0.459,1.553-1.433,1.041l-4.495-2.363c-0.974-0.512-2.567-0.512-3.541,0l-4.495,2.363c-0.974,0.512-1.618,0.044-1.432-1.041l0.858-5.004c0.186-1.085-0.307-2.6-1.094-3.368L3.93,13.977c-0.788-0.768-0.542-1.525,0.547-1.684l5.026-0.73c1.088-0.158,2.377-1.095,2.864-2.081L14.615,4.928z' },
// '.connection': { 'stroke':'black'},
// '.': { filter: { name: 'dropShadow', args: { dx: 1, dy: 1, blur: 2 } } },
// '.connection': { 'stroke-width': 10, 'stroke-linecap': 'round' },
// This means: moveto 10 0, lineto 0 5, lineto, 10 10 closepath(z)
// '.marker-target': { d: 'M 5 0 L 0 7 L 5 14 z', stroke: '#34302d','stroke-width': 1},
// '.marker-target': { d: 'M 14 2 L 9,2 L9,0 L 0,7 L 9,14 L 9,12 L 14,12 z', 'stroke-width': 1, fill: '#34302d', stroke: '#34302d'},
// '.marker-source': {d: 'M 5 0 L 5,10 L 0,10 L 0,0 z', 'stroke-width': 0, fill: '#34302d', stroke: '#34302d'},
// '.marker-target': { stroke: '#E74C3C', fill: '#E74C3C', d: 'M 10 0 L 0 5 L 10 10 z' },
'.marker-arrowheads': { display: 'none' },
'.tool-options': { display: 'none' }
},
// connector: { name: 'normalDimFix' }
}, joint.dia.Link.prototype.defaults)
});
joint.shapes.flo.LinkView = joint.dia.LinkView.extend({
options: joint.util.deepSupplement({
linkToolsOffset: 0.5,
shortLinkLength: 40
}, joint.dia.LinkView.prototype.options),
updateToolsPosition: function() {
// Overriden to support relative offset for tools placement.
// If offset is between 0 and 1 then percentage of the connection length will be used to offset the tools group
if (this.options.linkToolsOffset < 1 && this.options.linkToolsOffset > 0) {
let connectionLength = this.getConnectionLength();
const relativeOffset = this.options.linkToolsOffset
this.options.linkToolsOffset = connectionLength * relativeOffset;
const returnValue = joint.dia.LinkView.prototype.updateToolsPosition.apply(this, arguments);
this.options.linkToolsOffset = relativeOffset;
return returnValue;
} else {
return joint.dia.LinkView.prototype.updateToolsPosition.apply(this, arguments);
}
},
_beforeArrowheadMove: function() {
if (this.model.get('source').id) {
this._oldSource = this.model.get('source');
}
if (this.model.get('target').id) {
this._oldTarget = this.model.get('target');
}
joint.dia.LinkView.prototype._beforeArrowheadMove.apply(this, arguments);
},
_afterArrowheadMove: function() {
joint.dia.LinkView.prototype._afterArrowheadMove.apply(this, arguments);
if (!this.model.get('source').id) {
if (this._oldSource) {
this.model.set('source', this._oldSource);
} else {
this.model.remove();
}
}
if (!this.model.get('target').id) {
if (this._oldTarget) {
this.model.set('target', this._oldTarget);
} else {
this.model.remove();
}
}
delete this._oldSource;
delete this._oldTarget;
}
});
// TODO: must do cleanup for the `mainElementView'
joint.shapes.flo.ElementView = joint.dia.ElementView.extend({
// canShowTooltip: true,
beingDragged: false,
// _tempZorder: 0,
_tempOpacity: 1.0,
_hovering: false,
dragLinkStart: function(evt: any, magnet: any, x: number, y: number) {
this.model.startBatch('add-link');
const linkView = this.addLinkFromMagnet(magnet, x, y);
// backwards compatiblity events
joint.dia.CellView.prototype.pointerdown.apply(linkView, [evt, x, y]);
linkView.notify('link:pointerdown', evt, x, y);
/*** START MAIN DIFF ***/
const sourceOrTarget = $(magnet).attr('port') === 'input' ? 'source' : 'target';
linkView.eventData(evt, linkView.startArrowheadMove(sourceOrTarget, { whenNotAllowed: 'remove' }));
/*** END MAIN DIFF ***/
this.eventData(evt, { linkView: linkView });
},
addLinkFromMagnet: function(magnet: any, x: number, y: number) {
const paper = this.paper;
const graph = paper.model;
const link = paper.getDefaultLink(this, magnet);
let sourceEnd, targetEnd: any;
/*** START MAIN DIFF ***/
if ($(magnet).attr('port') === 'input') {
sourceEnd = { x: x, y: y };
targetEnd = this.getLinkEnd(magnet, x, y, link, 'target');
} else {
sourceEnd = this.getLinkEnd(magnet, x, y, link, 'source');
targetEnd = { x: x, y: y };
}
/*** END MAIN DIFF ***/
link.set({
source: sourceEnd,
target: targetEnd
}).addTo(graph, {
async: false,
ui: true
});
return link.findView(paper);
},
// pointerdown: function(evt: any, x: number, y: number) {
// // this.canShowTooltip = false;
// // this.hideTooltip();
// this.beingDragged = false;
// this._tempOpacity = this.model.attr('./opacity');
//
// this.model.trigger('batch:start');
//
// if ( // target is a valid magnet start linking
// evt.target.getAttribute('magnet') &&
// this.paper.options.validateMagnet.call(this.paper, this, evt.target)
// ) {
// let link = this.paper.getDefaultLink(this, evt.target);
// if ($(evt.target).attr('port') === 'input') {
// link.set({
// source: { x: x, y: y },
// target: {
// id: this.model.id,
// selector: this.getSelector(evt.target),
// port: evt.target.getAttribute('port')
// }
// });
// } else {
// link.set({
// source: {
// id: this.model.id,
// selector: this.getSelector(evt.target),
// port: evt.target.getAttribute('port')
// },
// target: { x: x, y: y }
// });
// }
// this.paper.model.addCell(link);
// this._linkView = this.paper.findViewByModel(link);
// if ($(evt.target).attr('port') === 'input') {
// this._linkView.startArrowheadMove('source');
// } else {
// this._linkView.startArrowheadMove('target');
// }
// this.paper.__creatingLinkFromPort = true;
// } else {
// this._dx = x;
// this._dy = y;
// joint.dia.CellView.prototype.pointerdown.apply(this, arguments);
// }
// },
drag: function(evt: MouseEvent, x: number, y: number) {
let interactive = isFunction(this.options.interactive) ? this.options.interactive(this, 'pointermove') :
this.options.interactive;
if (interactive !== false) {
this.paper.trigger('dragging-node-over-canvas', {type: Flo.DnDEventType.DRAG, view: this, event: evt});
}
joint.dia.ElementView.prototype.drag.apply(this, arguments);
},
dragEnd: function(evt: MouseEvent, x: number, y: number) { // jshint ignore:line
this.paper.trigger('dragging-node-over-canvas', {type: Flo.DnDEventType.DROP, view: this, event: evt});
joint.dia.ElementView.prototype.dragEnd.apply(this, arguments);
},
// events: {
// // Tooltips on the elements in the graph
// 'mouseenter': function(evt: MouseEvent) {
// if (this.canShowTooltip) {
// this.showTooltip(evt.pageX, evt.pageY);
// }
// if (!this._hovering && !this.paper.__creatingLinkFromPort) {
// this._hovering = true;
// if (isChrome || isFF) {
// this._tempZorder = this.model.get('z');
// this.model.toFront({deep: true});
// }
// }
// },
// 'mouseleave': function() {
// this.hideTooltip();
// if (this._hovering) {
// this._hovering = false;
// if (isChrome || isFF) {
// this.model.set('z', this._tempZorder);
// var z = this._tempZorder;
// this.model.getEmbeddedCells({breadthFirst: true}).forEach(function(cell: dia.Cell) {
// cell.set('z', ++z);
// });
// }
// }
// },
// 'mousemove': function(evt: MouseEvent) {
// this.moveTooltip(evt.pageX, evt.pageY);
// }
// },
// showTooltip: function(x: number, y: number) {
// var mousex = x + 10;
// var mousey = y + 10;
//
// var nodeTooltip: HTMLElement;
// if (this.model instanceof joint.dia.Element && this.model.get('metadata')) {
// nodeTooltip = document.createElement('div');
// $(nodeTooltip).addClass('node-tooltip');
//
// $(nodeTooltip).appendTo($('body')).fadeIn('fast');
// $(nodeTooltip).addClass('tooltip-description');
// var nodeTitle = document.createElement('div');
// $(nodeTooltip).append(nodeTitle);
// var nodeDescription = document.createElement('div');
// $(nodeTooltip).append(nodeDescription);
//
// var model = this.model;
//
// if (model.attr('metadata/name')) {
// var typeSpan = document.createElement('span');
// $(typeSpan).addClass('tooltip-title-type');
// $(nodeTitle).append(typeSpan);
// $(typeSpan).text(model.attr('metadata/name'));
// if (model.attr('metadata/group')) {
// var groupSpan = document.createElement('span');
// $(groupSpan).addClass('tooltip-title-group');
// $(nodeTitle).append(groupSpan);
// $(groupSpan).text('(' + model.attr('metadata/group') + ')');
// }
// }
//
// model.get('metadata').get('description').then(function(description: string) {
// $(nodeDescription).text(description);
// }, function(error: any) {
// if (error) {
// Logger.error(error);
// }
// });
//
// // defaultValue
// if (!model.attr('metadata/metadata/hide-tooltip-options')) {
// model.get('metadata')?.get('properties').then(function(metaProps: any) {
// var props = model.attr('props'); // array of {'name':,'value':}
// if (metaProps && props) {
// Object.keys(props).sort().forEach(function(propertyName) {
// if (metaProps[propertyName]) {
// var optionRow = document.createElement('div');
// var optionName = document.createElement('span');
// var optionDescription = document.createElement('span');
// $(optionName).addClass('node-tooltip-option-name');
// $(optionDescription).addClass('node-tooltip-option-description');
// $(optionName).text(metaProps[propertyName].name);
// $(optionDescription).text(props[propertyName]);//nodeOptionData[i].description);
// $(optionRow).append(optionName);
// $(optionRow).append(optionDescription);
// $(nodeTooltip).append(optionRow);
// }
// // This was the code to add every parameter in:
// // $(optionName).addClass('node-tooltip-option-name');
// // $(optionDescription).addClass('node-tooltip-option-description');
// // $(optionName).text(metaProps[propertyName].name);
// // $(optionDescription).text(metaProps[propertyName].description);
// // $(optionRow).append(optionName);
// // $(optionRow).append(optionDescription);
// // $(nodeTooltip).append(optionRow);
// });
// }
// }, function(error: any) {
// if (error) {
// Logger.error(error);
// }
// });
// }
//
// $('.node-tooltip').css({ top: mousey, left: mousex });
// } else if (this.model.get('type') === joint.shapes.flo.DECORATION_TYPE && this.model.attr('./kind') === 'error') {
// Logger.debug('mouse enter: ERROR box=' + JSON.stringify(this.model.getBBox()));
// nodeTooltip = document.createElement('div');
// var errors = this.model.attr('messages');
// if (errors && errors.length > 0) {
// $(nodeTooltip).addClass('error-tooltip');
// $(nodeTooltip).appendTo($('body')).fadeIn('fast');
// var header = document.createElement('p');
// $(header).text('Errors:');
// $(nodeTooltip).append(header);
// for (var i = 0;i < errors.length; i++) {
// var errorElement = document.createElement('li');
// $(errorElement).text(errors[i]);
// $(nodeTooltip).append(errorElement);
// }
// $('.error-tooltip').css({ top: mousey, left: mousex });
// }
// }
// },
// hideTooltip: function() {
// $('.node-tooltip').remove();
// $('.error-tooltip').remove();
// },
// moveTooltip: function(x: number, y: number) {
// $('.node-tooltip')
// .css({ top: y + 10, left: x + 10 });
// $('.error-tooltip')
// .css({ top: y + 10, left: x + 10 });
// }
});
joint.shapes.flo.ErrorDecoration = joint.shapes.basic.Generic.extend({
markup: '<g class="rotatable"><g class="scalable"><image/></g></g>',
defaults: joint.util.deepSupplement({
type: joint.shapes.flo.DECORATION_TYPE,
size: ERROR_MARKER_SIZE,
attrs: {
'image': ERROR_MARKER_SIZE
}
}, joint.shapes.basic.Generic.prototype.defaults)
});
joint.shapes.flo.PaletteGroupHeader = joint.shapes.basic.Generic.extend({
// The path is the open/close arrow, defaults to vertical (open)
markup: '<g class="scalable"><rect/></g><text/><g class="rotatable"><path d="m 10 10 l 5 8.7 l 5 -8.7 z"/></g>',
defaults: joint.util.deepSupplement({
type: 'palette.groupheader',
size: {width: 170, height: 30},
position: {x: 0, y: 0},
attrs: {
'rect': { fill: '#34302d', 'stroke-width': 1, stroke: '#6db33f', 'follow-scale': true, width: 80, height: 40 },
'text': {
text: '',
fill: '#eeeeee',
'ref-x': 0.5,
'ref-y': 7,
'x-alignment': 'middle',
'font-size': 18/*, 'font-weight': 'bold', 'font-variant': 'small-caps', 'text-transform': 'capitalize'*/
},
'path': { fill: 'white', 'stroke-width': 2, stroke: 'white'/*,transform:'rotate(90,15,15)'*/}
},
// custom properties
isOpen: true
}, joint.shapes.basic.Generic.prototype.defaults)
});
joint.shapes.flo.NoMatchesFound = joint.shapes.basic.Generic.extend({
// The path is the open/close arrow, defaults to vertical (open)
markup: '<g class="scalable"><rect class="no-matches-label-border"/></g><rect class="no-mathes-label-bg"/><text class="no-matches-label"/>',
defaults: joint.util.deepSupplement({
size: {width: 170, height: 30},
position: {x: 0, y: 0},
attrs: {
'.no-matches-label-border': {
refWidth: 1,
refHeight: 1,
refX: 0,
refY: 0,
},
'.no-macthes-label-bg': {
ref: '.no-matches-label',
refWidth: 10,
refHeight: 2,
'follow-scale': true
},
'.no-matches-label': {
text: 'No results found.',
ref: '.no-matches-label-border',
refY: 0.5,
refY2: 5,
yAlignment: 'middle',
},
},
}, joint.shapes.basic.Generic.prototype.defaults)
});
}
export namespace Constants {
export const REMOVE_HANDLE_TYPE = REMOVE;
export const PROPERTIES_HANDLE_TYPE = 'properties';
export const ERROR_DECORATION_KIND = ERROR;
export const PALETTE_CONTEXT = 'palette';
export const CANVAS_CONTEXT = 'canvas';
export const FEEDBACK_CONTEXT = 'feedback';
}
export namespace Shapes {
export interface CreationParams extends Flo.CreationParams {
renderer?: Flo.Renderer;
paper?: dia.Paper;
graph?: dia.Graph;
}
export interface ElementCreationParams extends CreationParams {
position?: dia.Point;
}
export interface LinkCreationParams extends CreationParams {
source: Flo.LinkEnd;
target: Flo.LinkEnd;
}
export interface EmbeddedChildCreationParams extends CreationParams {
parent: dia.Cell;
position?: dia.Point;
}
export interface DecorationCreationParams extends EmbeddedChildCreationParams {
kind: string;
messages: Array<string>;
}
export interface HandleCreationParams extends EmbeddedChildCreationParams {
kind: string;
}
export interface FilterOptions {
amount: number;
[propName: string]: any;
}
export class Factory {
/**
* Create a JointJS node that embeds extra metadata (properties).
*/
static createNode(params: ElementCreationParams): dia.Element {
let renderer = params.renderer;
let paper = params.paper;
let metadata = params.metadata;
let position = params.position;
let props = params.props;
let graph = params.graph || (params.paper ? params.paper.model : undefined);
let node: dia.Element;
if (!position) {
position = {x: 0, y: 0};
}
if (renderer && isFunction(renderer.createNode)) {
node = renderer.createNode({graph, paper}, metadata, props);
} else {
node = new joint.shapes.flo.Node();
if (metadata) {
node.attr('.label/text', metadata.name);
}
}
node.set('type', joint.shapes.flo.NODE_TYPE);
if (position) {
node.set('position', position);
}
if (props) {
Array.from(props.keys()).forEach(key => node.attr(`props/${key}`, props!.get(key)));
}
node.set('metadata', metadata);
if (graph) {
graph.addCell(node);
}
if (renderer && isFunction(renderer.initializeNewNode)) {
let descriptor: Flo.ViewerDescriptor = {
paper: paper,
graph: graph
};
renderer.initializeNewNode(node, descriptor);
}
return node;
}
static createLink(params: LinkCreationParams): dia.Link {
let renderer = params.renderer;
let paper = params.paper;
let metadata = params.metadata;
let source = params.source;
let target = params.target;
let props = params.props;
let graph = params.graph || (params.paper ? params.paper.model : undefined);
let link: dia.Link;
if (renderer && isFunction(renderer.createLink)) {
link = renderer.createLink(source, target, metadata, props);
} else {
link = new joint.shapes.flo.Link();
}
if (source) {
link.set('source', source);
}
if (target) {
link.set('target', target);
}
link.set('type', joint.shapes.flo.LINK_TYPE);
if (metadata) {
link.set('metadata', metadata);
}
if (props) {
Array.from(props.keys()).forEach(key => link.attr(`props/${key}`, props!.get(key)));
}
if (graph) {
graph.addCell(link);
}
if (renderer && isFunction(renderer.initializeNewLink)) {
let descriptor: Flo.ViewerDescriptor = {
paper: paper,
graph: graph
};
renderer.initializeNewLink(link, descriptor);
}
// prevent creation of link breaks
link.attr('.marker-vertices/display', 'none');
return link;
}
static createDecoration(params: DecorationCreationParams): dia.Element {
let renderer = params.renderer;
let paper = params.paper;
let parent = params.parent;
let kind = params.kind;
let messages = params.messages;
let location = params.position;
let graph = params.graph || (params.paper ? params.paper.model : undefined);
let decoration: dia.Element;
if (renderer && isFunction(renderer.createDecoration)) {
decoration = renderer.createDecoration(kind, parent);
}
if (decoration) {
decoration.set('type', joint.shapes.flo.DECORATION_TYPE);
if ((isChrome || isFF) && parent && typeof parent.get('z') === 'number') {
decoration.set('z', parent.get('z') + 1);
}
decoration.attr('./kind', kind);
decoration.attr('messages', messages);
if (graph) {
graph.addCell(decoration);
}
parent.embed(decoration);
if (renderer && isFunction(renderer.initializeNewDecoration)) {
let descriptor: Flo.ViewerDescriptor = {
paper: paper,
graph: graph
};
renderer.initializeNewDecoration(decoration, descriptor);
}
return decoration;
}
}
static createHandle(params: HandleCreationParams): dia.Element {
let renderer = params.renderer;
let paper = params.paper;
let parent = params.parent;
let kind = params.kind;
let location = params.position;
let graph = params.graph || (params.paper ? params.paper.model : undefined);
let handle: dia.Element;
if (!location) {
location = {x: 0, y: 0};
}
if (renderer && isFunction(renderer.createHandle)) {
handle = renderer.createHandle(kind, parent);
} else {
handle = new joint.shapes.flo.ErrorDecoration({
size: HANDLE_SIZE,
attrs: {
'image': {
'xlink:href': HANDLE_ICON_MAP.get(kind)
}
}
});
}
handle.set('type', joint.shapes.flo.HANDLE_TYPE);
handle.set('position', location);
if ((isChrome || isFF) && parent && typeof parent.get('z') === 'number') {
handle.set('z', parent.get('z') + 1);
}
handle.attr('./kind', kind);
if (graph) {
graph.addCell(handle);
}
parent.embed(handle);
if (renderer && isFunction(renderer.initializeNewHandle)) {
let descriptor: Flo.ViewerDescriptor = {
paper: paper,
graph: graph
};
renderer.initializeNewHandle(handle, descriptor);
}
return handle;
}
}
}
loadShapes();