Build infrastructure and Angular 15 upgrade
This commit is contained in:
278
projects/flo/shared/flo-common.ts
Normal file
278
projects/flo/shared/flo-common.ts
Normal 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();
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
390
projects/flo/shared/flo-properties.ts
Normal file
390
projects/flo/shared/flo-properties.ts
Normal 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}} : {};
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
28
projects/flo/shared/logger.ts
Normal file
28
projects/flo/shared/logger.ts
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
805
projects/flo/shared/shapes.ts
Normal file
805
projects/flo/shared/shapes.ts
Normal 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();
|
||||
|
||||
Reference in New Issue
Block a user