mirror of
				https://github.com/mermaid-js/mermaid.git
				synced 2025-11-03 20:34:20 +01:00 
			
		
		
		
	refactor: Fix typings in utils.ts
This commit is contained in:
		@@ -4,17 +4,13 @@ import { log } from '../../logger.js';
 | 
			
		||||
import type { BaseDiagramConfig, QuadrantChartConfig } from '../../config.type.js';
 | 
			
		||||
import defaultConfig from '../../defaultConfig.js';
 | 
			
		||||
import { getThemeVariables } from '../../themes/theme-default.js';
 | 
			
		||||
import type { Point } from '../../types.js';
 | 
			
		||||
 | 
			
		||||
const defaultThemeVariables = getThemeVariables();
 | 
			
		||||
 | 
			
		||||
export type TextVerticalPos = 'left' | 'center' | 'right';
 | 
			
		||||
export type TextHorizontalPos = 'top' | 'middle' | 'bottom';
 | 
			
		||||
 | 
			
		||||
export interface Point {
 | 
			
		||||
  x: number;
 | 
			
		||||
  y: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface QuadrantPointInputType extends Point {
 | 
			
		||||
  text: string;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -136,7 +136,7 @@ const runThrowsErrors = async function (
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // generate the id of the diagram
 | 
			
		||||
  const idGenerator = new utils.initIdGenerator(conf.deterministicIds, conf.deterministicIDSeed);
 | 
			
		||||
  const idGenerator = new utils.InitIDGenerator(conf.deterministicIds, conf.deterministicIDSeed);
 | 
			
		||||
 | 
			
		||||
  let txt: string;
 | 
			
		||||
  const errors: DetailedError[] = [];
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										16
									
								
								packages/mermaid/src/types.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								packages/mermaid/src/types.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,16 @@
 | 
			
		||||
export interface Point {
 | 
			
		||||
  x: number;
 | 
			
		||||
  y: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface TextDimensionConfig {
 | 
			
		||||
  fontSize?: number;
 | 
			
		||||
  fontWeight?: number;
 | 
			
		||||
  fontFamily?: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface TextDimensions {
 | 
			
		||||
  width: number;
 | 
			
		||||
  height: number;
 | 
			
		||||
  lineHeight?: number;
 | 
			
		||||
}
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
import { vi } from 'vitest';
 | 
			
		||||
import utils, { cleanAndMerge, detectDirective } from './utils.js';
 | 
			
		||||
import utils, { calculatePoint, cleanAndMerge, detectDirective } from './utils.js';
 | 
			
		||||
import assignWithDepth from './assignWithDepth.js';
 | 
			
		||||
import { detectType } from './diagram-api/detectType.js';
 | 
			
		||||
import { addDiagrams } from './diagram-api/diagram-orchestration.js';
 | 
			
		||||
@@ -352,7 +352,7 @@ describe('when initializing the id generator', function () {
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('should return a random number generator based on Date', function () {
 | 
			
		||||
    const idGenerator = new utils.initIdGenerator(false);
 | 
			
		||||
    const idGenerator = new utils.InitIDGenerator(false);
 | 
			
		||||
    expect(typeof idGenerator.next).toEqual('function');
 | 
			
		||||
    const lastId = idGenerator.next();
 | 
			
		||||
    vi.advanceTimersByTime(1000);
 | 
			
		||||
@@ -360,7 +360,7 @@ describe('when initializing the id generator', function () {
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('should return a non random number generator', function () {
 | 
			
		||||
    const idGenerator = new utils.initIdGenerator(true);
 | 
			
		||||
    const idGenerator = new utils.InitIDGenerator(true);
 | 
			
		||||
    expect(typeof idGenerator.next).toEqual('function');
 | 
			
		||||
    const start = 0;
 | 
			
		||||
    const lastId = idGenerator.next();
 | 
			
		||||
@@ -369,7 +369,7 @@ describe('when initializing the id generator', function () {
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('should return a non random number generator based on seed', function () {
 | 
			
		||||
    const idGenerator = new utils.initIdGenerator(true, 'thisIsASeed');
 | 
			
		||||
    const idGenerator = new utils.InitIDGenerator(true, 'thisIsASeed');
 | 
			
		||||
    expect(typeof idGenerator.next).toEqual('function');
 | 
			
		||||
    const start = 11;
 | 
			
		||||
    const lastId = idGenerator.next();
 | 
			
		||||
@@ -490,3 +490,107 @@ describe('cleanAndMerge', () => {
 | 
			
		||||
    expect(inputDeep).toEqual({ a: { b: 1 } });
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
describe('calculatePoint', () => {
 | 
			
		||||
  it('should calculate a point on a straight line', () => {
 | 
			
		||||
    const points = [
 | 
			
		||||
      { x: 0, y: 0 },
 | 
			
		||||
      { x: 0, y: 10 },
 | 
			
		||||
      { x: 0, y: 20 },
 | 
			
		||||
    ];
 | 
			
		||||
    expect(calculatePoint(points, 0)).toEqual({ x: 0, y: 0 });
 | 
			
		||||
    expect(calculatePoint(points, 5)).toEqual({ x: 0, y: 5 });
 | 
			
		||||
    expect(calculatePoint(points, 10)).toEqual({ x: 0, y: 10 });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('should calculate a point on a straight line with slope', () => {
 | 
			
		||||
    const points = [
 | 
			
		||||
      { x: 0, y: 0 },
 | 
			
		||||
      { x: 10, y: 10 },
 | 
			
		||||
      { x: 20, y: 20 },
 | 
			
		||||
    ];
 | 
			
		||||
    expect(calculatePoint(points, 0)).toMatchInlineSnapshot(`
 | 
			
		||||
      {
 | 
			
		||||
        "x": 0,
 | 
			
		||||
        "y": 0,
 | 
			
		||||
      }
 | 
			
		||||
    `);
 | 
			
		||||
    expect(calculatePoint(points, 5)).toMatchInlineSnapshot(`
 | 
			
		||||
      {
 | 
			
		||||
        "x": 3.53553,
 | 
			
		||||
        "y": 3.53553,
 | 
			
		||||
      }
 | 
			
		||||
    `);
 | 
			
		||||
    expect(calculatePoint(points, 10)).toMatchInlineSnapshot(`
 | 
			
		||||
      {
 | 
			
		||||
        "x": 7.07107,
 | 
			
		||||
        "y": 7.07107,
 | 
			
		||||
      }
 | 
			
		||||
    `);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('should calculate a point on a straight line with negative slope', () => {
 | 
			
		||||
    const points = [
 | 
			
		||||
      { x: 20, y: 20 },
 | 
			
		||||
      { x: 10, y: 10 },
 | 
			
		||||
      { x: 15, y: 15 },
 | 
			
		||||
      { x: 0, y: 0 },
 | 
			
		||||
    ];
 | 
			
		||||
    expect(calculatePoint(points, 0)).toMatchInlineSnapshot(`
 | 
			
		||||
      {
 | 
			
		||||
        "x": 20,
 | 
			
		||||
        "y": 20,
 | 
			
		||||
      }
 | 
			
		||||
    `);
 | 
			
		||||
    expect(calculatePoint(points, 5)).toMatchInlineSnapshot(`
 | 
			
		||||
      {
 | 
			
		||||
        "x": 16.46447,
 | 
			
		||||
        "y": 16.46447,
 | 
			
		||||
      }
 | 
			
		||||
    `);
 | 
			
		||||
    expect(calculatePoint(points, 10)).toMatchInlineSnapshot(`
 | 
			
		||||
      {
 | 
			
		||||
        "x": 12.92893,
 | 
			
		||||
        "y": 12.92893,
 | 
			
		||||
      }
 | 
			
		||||
    `);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('should calculate a point on a curved line', () => {
 | 
			
		||||
    const points = [
 | 
			
		||||
      { x: 0, y: 0 },
 | 
			
		||||
      { x: 10, y: 10 },
 | 
			
		||||
      { x: 20, y: 0 },
 | 
			
		||||
    ];
 | 
			
		||||
    expect(calculatePoint(points, 0)).toMatchInlineSnapshot(`
 | 
			
		||||
      {
 | 
			
		||||
        "x": 0,
 | 
			
		||||
        "y": 0,
 | 
			
		||||
      }
 | 
			
		||||
    `);
 | 
			
		||||
    expect(calculatePoint(points, 15)).toMatchInlineSnapshot(`
 | 
			
		||||
      {
 | 
			
		||||
        "x": 10.6066,
 | 
			
		||||
        "y": 9.3934,
 | 
			
		||||
      }
 | 
			
		||||
    `);
 | 
			
		||||
    expect(calculatePoint(points, 20)).toMatchInlineSnapshot(`
 | 
			
		||||
      {
 | 
			
		||||
        "x": 14.14214,
 | 
			
		||||
        "y": 5.85786,
 | 
			
		||||
      }
 | 
			
		||||
    `);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('should throw an error if the new point cannot be found', () => {
 | 
			
		||||
    const points = [
 | 
			
		||||
      { x: 0, y: 0 },
 | 
			
		||||
      { x: 10, y: 10 },
 | 
			
		||||
      { x: 20, y: 20 },
 | 
			
		||||
    ];
 | 
			
		||||
    const distanceToTraverse = 30;
 | 
			
		||||
    expect(() => calculatePoint(points, distanceToTraverse)).toThrow(
 | 
			
		||||
      'Could not find a suitable point for the given distance'
 | 
			
		||||
    );
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,3 @@
 | 
			
		||||
// @ts-nocheck : TODO Fix ts errors
 | 
			
		||||
import { sanitizeUrl } from '@braintree/sanitize-url';
 | 
			
		||||
import type { CurveFactory } from 'd3';
 | 
			
		||||
import {
 | 
			
		||||
@@ -33,6 +32,8 @@ import type { MermaidConfig } from './config.type.js';
 | 
			
		||||
import memoize from 'lodash-es/memoize.js';
 | 
			
		||||
import merge from 'lodash-es/merge.js';
 | 
			
		||||
import { directiveRegex } from './diagram-api/regexes.js';
 | 
			
		||||
import type { D3Element } from './mermaidAPI.js';
 | 
			
		||||
import type { Point, TextDimensionConfig, TextDimensions } from './types.js';
 | 
			
		||||
 | 
			
		||||
export const ZERO_WIDTH_SPACE = '\u200b';
 | 
			
		||||
 | 
			
		||||
@@ -58,7 +59,7 @@ const d3CurveTypes = {
 | 
			
		||||
  curveStep: curveStep,
 | 
			
		||||
  curveStepAfter: curveStepAfter,
 | 
			
		||||
  curveStepBefore: curveStepBefore,
 | 
			
		||||
};
 | 
			
		||||
} as const;
 | 
			
		||||
 | 
			
		||||
const directiveWithoutOpen =
 | 
			
		||||
  /\s*(?:(\w+)(?=:):|(\w+))\s*(?:(\w+)|((?:(?!}%{2}).|\r?\n)*))?\s*(?:}%{2})?/gi;
 | 
			
		||||
@@ -101,14 +102,14 @@ export const detectInit = function (
 | 
			
		||||
  config?: MermaidConfig
 | 
			
		||||
): MermaidConfig | undefined {
 | 
			
		||||
  const inits = detectDirective(text, /(?:init\b)|(?:initialize\b)/);
 | 
			
		||||
  let results = {};
 | 
			
		||||
  let results: MermaidConfig & { config?: unknown } = {};
 | 
			
		||||
 | 
			
		||||
  if (Array.isArray(inits)) {
 | 
			
		||||
    const args = inits.map((init) => init.args);
 | 
			
		||||
    sanitizeDirective(args);
 | 
			
		||||
    results = assignWithDepth(results, [...args]);
 | 
			
		||||
  } else {
 | 
			
		||||
    results = inits.args;
 | 
			
		||||
    results = inits.args as MermaidConfig;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (!results) {
 | 
			
		||||
@@ -116,19 +117,24 @@ export const detectInit = function (
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  let type = detectType(text, config);
 | 
			
		||||
  ['config'].forEach((prop) => {
 | 
			
		||||
    if (results[prop] !== undefined) {
 | 
			
		||||
      if (type === 'flowchart-v2') {
 | 
			
		||||
        type = 'flowchart';
 | 
			
		||||
      }
 | 
			
		||||
      results[type] = results[prop];
 | 
			
		||||
      delete results[prop];
 | 
			
		||||
 | 
			
		||||
  // Move the `config` value to appropriate diagram type value
 | 
			
		||||
  const prop = 'config';
 | 
			
		||||
  if (results[prop] !== undefined) {
 | 
			
		||||
    if (type === 'flowchart-v2') {
 | 
			
		||||
      type = 'flowchart';
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
    results[type as keyof MermaidConfig] = results[prop];
 | 
			
		||||
    delete results[prop];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return results;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
interface Directive {
 | 
			
		||||
  type?: string;
 | 
			
		||||
  args?: unknown;
 | 
			
		||||
}
 | 
			
		||||
/**
 | 
			
		||||
 * Detects the directive from the text.
 | 
			
		||||
 *
 | 
			
		||||
@@ -154,8 +160,8 @@ export const detectInit = function (
 | 
			
		||||
 */
 | 
			
		||||
export const detectDirective = function (
 | 
			
		||||
  text: string,
 | 
			
		||||
  type: string | RegExp = null
 | 
			
		||||
): { type?: string; args?: any } | { type?: string; args?: any }[] {
 | 
			
		||||
  type: string | RegExp | null = null
 | 
			
		||||
): Directive | Directive[] {
 | 
			
		||||
  try {
 | 
			
		||||
    const commentWithoutDirectives = new RegExp(
 | 
			
		||||
      `[%]{2}(?![{]${directiveWithoutOpen.source})(?=[}][%]{2}).*\n`,
 | 
			
		||||
@@ -165,8 +171,8 @@ export const detectDirective = function (
 | 
			
		||||
    log.debug(
 | 
			
		||||
      `Detecting diagram directive${type !== null ? ' type:' + type : ''} based on the text:${text}`
 | 
			
		||||
    );
 | 
			
		||||
    let match;
 | 
			
		||||
    const result = [];
 | 
			
		||||
    let match: RegExpExecArray | null;
 | 
			
		||||
    const result: Directive[] = [];
 | 
			
		||||
    while ((match = directiveRegex.exec(text)) !== null) {
 | 
			
		||||
      // This is necessary to avoid infinite loops with zero-width matches
 | 
			
		||||
      if (match.index === directiveRegex.lastIndex) {
 | 
			
		||||
@@ -183,16 +189,17 @@ export const detectDirective = function (
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    if (result.length === 0) {
 | 
			
		||||
      result.push({ type: text, args: null });
 | 
			
		||||
      return { type: text, args: null };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return result.length === 1 ? result[0] : result;
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
    log.error(
 | 
			
		||||
      `ERROR: ${error.message} - Unable to parse directive
 | 
			
		||||
      ${type !== null ? ' type:' + type : ''} based on the text:${text}`
 | 
			
		||||
      `ERROR: ${
 | 
			
		||||
        (error as Error).message
 | 
			
		||||
      } - Unable to parse directive type: '${type}' based on the text: '${text}'`
 | 
			
		||||
    );
 | 
			
		||||
    return { type: null, args: null };
 | 
			
		||||
    return { type: undefined, args: null };
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@@ -231,7 +238,9 @@ export function interpolateToCurve(
 | 
			
		||||
    return defaultCurve;
 | 
			
		||||
  }
 | 
			
		||||
  const curveName = `curve${interpolate.charAt(0).toUpperCase() + interpolate.slice(1)}`;
 | 
			
		||||
  return d3CurveTypes[curveName] || defaultCurve;
 | 
			
		||||
 | 
			
		||||
  // @ts-ignore TODO: Fix issue with curve type
 | 
			
		||||
  return d3CurveTypes[curveName as keyof typeof d3CurveTypes] ?? defaultCurve;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -244,13 +253,15 @@ export function interpolateToCurve(
 | 
			
		||||
export function formatUrl(linkStr: string, config: MermaidConfig): string | undefined {
 | 
			
		||||
  const url = linkStr.trim();
 | 
			
		||||
 | 
			
		||||
  if (url) {
 | 
			
		||||
    if (config.securityLevel !== 'loose') {
 | 
			
		||||
      return sanitizeUrl(url);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return url;
 | 
			
		||||
  if (!url) {
 | 
			
		||||
    return undefined;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (config.securityLevel !== 'loose') {
 | 
			
		||||
    return sanitizeUrl(url);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return url;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -259,7 +270,7 @@ export function formatUrl(linkStr: string, config: MermaidConfig): string | unde
 | 
			
		||||
 * @param functionName - A dot separated path to the function relative to the `window`
 | 
			
		||||
 * @param params - Parameters to pass to the function
 | 
			
		||||
 */
 | 
			
		||||
export const runFunc = (functionName: string, ...params) => {
 | 
			
		||||
export const runFunc = (functionName: string, ...params: unknown[]) => {
 | 
			
		||||
  const arrPaths = functionName.split('.');
 | 
			
		||||
 | 
			
		||||
  const len = arrPaths.length - 1;
 | 
			
		||||
@@ -267,23 +278,16 @@ export const runFunc = (functionName: string, ...params) => {
 | 
			
		||||
 | 
			
		||||
  let obj = window;
 | 
			
		||||
  for (let i = 0; i < len; i++) {
 | 
			
		||||
    obj = obj[arrPaths[i]];
 | 
			
		||||
    obj = obj[arrPaths[i] as keyof typeof obj];
 | 
			
		||||
    if (!obj) {
 | 
			
		||||
      log.error(`Function name: ${functionName} not found in window`);
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  obj[fnName](...params);
 | 
			
		||||
  obj[fnName as keyof typeof obj](...params);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/** A (x, y) point */
 | 
			
		||||
interface Point {
 | 
			
		||||
  /** The x value */
 | 
			
		||||
  x: number;
 | 
			
		||||
  /** The y value */
 | 
			
		||||
  y: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Finds the distance between two points using the Distance Formula
 | 
			
		||||
 *
 | 
			
		||||
@@ -291,8 +295,11 @@ interface Point {
 | 
			
		||||
 * @param p2 - The second point
 | 
			
		||||
 * @returns The distance between the two points.
 | 
			
		||||
 */
 | 
			
		||||
function distance(p1: Point, p2: Point): number {
 | 
			
		||||
  return p1 && p2 ? Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2)) : 0;
 | 
			
		||||
function distance(p1?: Point, p2?: Point): number {
 | 
			
		||||
  if (!p1 || !p2) {
 | 
			
		||||
    return 0;
 | 
			
		||||
  }
 | 
			
		||||
  return Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -301,7 +308,7 @@ function distance(p1: Point, p2: Point): number {
 | 
			
		||||
 * @param points - List of points
 | 
			
		||||
 */
 | 
			
		||||
function traverseEdge(points: Point[]): Point {
 | 
			
		||||
  let prevPoint;
 | 
			
		||||
  let prevPoint: Point | undefined;
 | 
			
		||||
  let totalDistance = 0;
 | 
			
		||||
 | 
			
		||||
  points.forEach((point) => {
 | 
			
		||||
@@ -310,35 +317,8 @@ function traverseEdge(points: Point[]): Point {
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  // Traverse half of total distance along points
 | 
			
		||||
  let remainingDistance = totalDistance / 2;
 | 
			
		||||
  let center = undefined;
 | 
			
		||||
  prevPoint = undefined;
 | 
			
		||||
  points.forEach((point) => {
 | 
			
		||||
    if (prevPoint && !center) {
 | 
			
		||||
      const vectorDistance = distance(point, prevPoint);
 | 
			
		||||
      if (vectorDistance < remainingDistance) {
 | 
			
		||||
        remainingDistance -= vectorDistance;
 | 
			
		||||
      } else {
 | 
			
		||||
        // The point is remainingDistance from prevPoint in the vector between prevPoint and point
 | 
			
		||||
        // Calculate the coordinates
 | 
			
		||||
        const distanceRatio = remainingDistance / vectorDistance;
 | 
			
		||||
        if (distanceRatio <= 0) {
 | 
			
		||||
          center = prevPoint;
 | 
			
		||||
        }
 | 
			
		||||
        if (distanceRatio >= 1) {
 | 
			
		||||
          center = { x: point.x, y: point.y };
 | 
			
		||||
        }
 | 
			
		||||
        if (distanceRatio > 0 && distanceRatio < 1) {
 | 
			
		||||
          center = {
 | 
			
		||||
            x: (1 - distanceRatio) * prevPoint.x + distanceRatio * point.x,
 | 
			
		||||
            y: (1 - distanceRatio) * prevPoint.y + distanceRatio * point.y,
 | 
			
		||||
          };
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    prevPoint = point;
 | 
			
		||||
  });
 | 
			
		||||
  return center;
 | 
			
		||||
  const remainingDistance = totalDistance / 2;
 | 
			
		||||
  return calculatePoint(points, remainingDistance);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -351,20 +331,16 @@ function calcLabelPosition(points: Point[]): Point {
 | 
			
		||||
  return traverseEdge(points);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const calcCardinalityPosition = (isRelationTypePresent, points, initialPosition) => {
 | 
			
		||||
  let prevPoint;
 | 
			
		||||
  log.info(`our points ${JSON.stringify(points)}`);
 | 
			
		||||
  if (points[0] !== initialPosition) {
 | 
			
		||||
    points = points.reverse();
 | 
			
		||||
  }
 | 
			
		||||
  // Traverse only 25 total distance along points to find cardinality point
 | 
			
		||||
  const distanceToCardinalityPoint = 25;
 | 
			
		||||
export const roundNumber = (num: number, precision = 2) => {
 | 
			
		||||
  const factor = Math.pow(10, precision);
 | 
			
		||||
  return Math.round(num * factor) / factor;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
  let remainingDistance = distanceToCardinalityPoint;
 | 
			
		||||
  let center;
 | 
			
		||||
  prevPoint = undefined;
 | 
			
		||||
  points.forEach((point) => {
 | 
			
		||||
    if (prevPoint && !center) {
 | 
			
		||||
export const calculatePoint = (points: Point[], distanceToTraverse: number): Point => {
 | 
			
		||||
  let prevPoint: Point | undefined = undefined;
 | 
			
		||||
  let remainingDistance = distanceToTraverse;
 | 
			
		||||
  for (const point of points) {
 | 
			
		||||
    if (prevPoint) {
 | 
			
		||||
      const vectorDistance = distance(point, prevPoint);
 | 
			
		||||
      if (vectorDistance < remainingDistance) {
 | 
			
		||||
        remainingDistance -= vectorDistance;
 | 
			
		||||
@@ -373,27 +349,42 @@ const calcCardinalityPosition = (isRelationTypePresent, points, initialPosition)
 | 
			
		||||
        // Calculate the coordinates
 | 
			
		||||
        const distanceRatio = remainingDistance / vectorDistance;
 | 
			
		||||
        if (distanceRatio <= 0) {
 | 
			
		||||
          center = prevPoint;
 | 
			
		||||
          return prevPoint;
 | 
			
		||||
        }
 | 
			
		||||
        if (distanceRatio >= 1) {
 | 
			
		||||
          center = { x: point.x, y: point.y };
 | 
			
		||||
          return { x: point.x, y: point.y };
 | 
			
		||||
        }
 | 
			
		||||
        if (distanceRatio > 0 && distanceRatio < 1) {
 | 
			
		||||
          center = {
 | 
			
		||||
            x: (1 - distanceRatio) * prevPoint.x + distanceRatio * point.x,
 | 
			
		||||
            y: (1 - distanceRatio) * prevPoint.y + distanceRatio * point.y,
 | 
			
		||||
          return {
 | 
			
		||||
            x: roundNumber((1 - distanceRatio) * prevPoint.x + distanceRatio * point.x, 5),
 | 
			
		||||
            y: roundNumber((1 - distanceRatio) * prevPoint.y + distanceRatio * point.y, 5),
 | 
			
		||||
          };
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    prevPoint = point;
 | 
			
		||||
  });
 | 
			
		||||
  }
 | 
			
		||||
  throw new Error('Could not find a suitable point for the given distance');
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const calcCardinalityPosition = (
 | 
			
		||||
  isRelationTypePresent: boolean,
 | 
			
		||||
  points: Point[],
 | 
			
		||||
  initialPosition: Point
 | 
			
		||||
) => {
 | 
			
		||||
  log.info(`our points ${JSON.stringify(points)}`);
 | 
			
		||||
  if (points[0] !== initialPosition) {
 | 
			
		||||
    points = points.reverse();
 | 
			
		||||
  }
 | 
			
		||||
  // Traverse only 25 total distance along points to find cardinality point
 | 
			
		||||
  const distanceToCardinalityPoint = 25;
 | 
			
		||||
  const center = calculatePoint(points, distanceToCardinalityPoint);
 | 
			
		||||
  // if relation is present (Arrows will be added), change cardinality point off-set distance (d)
 | 
			
		||||
  const d = isRelationTypePresent ? 10 : 5;
 | 
			
		||||
  //Calculate Angle for x and y axis
 | 
			
		||||
  const angle = Math.atan2(points[0].y - center.y, points[0].x - center.x);
 | 
			
		||||
  const cardinalityPosition = { x: 0, y: 0 };
 | 
			
		||||
  //Calculation cardinality position using angle, center point on the line/curve but pendicular and with offset-distance
 | 
			
		||||
  //Calculation cardinality position using angle, center point on the line/curve but perpendicular and with offset-distance
 | 
			
		||||
  cardinalityPosition.x = Math.sin(angle) * d + (points[0].x + center.x) / 2;
 | 
			
		||||
  cardinalityPosition.y = -Math.cos(angle) * d + (points[0].y + center.y) / 2;
 | 
			
		||||
  return cardinalityPosition;
 | 
			
		||||
@@ -412,71 +403,36 @@ function calcTerminalLabelPosition(
 | 
			
		||||
  position: 'start_left' | 'start_right' | 'end_left' | 'end_right',
 | 
			
		||||
  _points: Point[]
 | 
			
		||||
): Point {
 | 
			
		||||
  // Todo looking to faster cloning method
 | 
			
		||||
  let points = JSON.parse(JSON.stringify(_points));
 | 
			
		||||
  let prevPoint;
 | 
			
		||||
  const points = structuredClone(_points);
 | 
			
		||||
  log.info('our points', points);
 | 
			
		||||
  if (position !== 'start_left' && position !== 'start_right') {
 | 
			
		||||
    points = points.reverse();
 | 
			
		||||
    points.reverse();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  points.forEach((point) => {
 | 
			
		||||
    prevPoint = point;
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  // Traverse only 25 total distance along points to find cardinality point
 | 
			
		||||
  const distanceToCardinalityPoint = 25 + terminalMarkerSize;
 | 
			
		||||
  const center = calculatePoint(points, distanceToCardinalityPoint);
 | 
			
		||||
 | 
			
		||||
  let remainingDistance = distanceToCardinalityPoint;
 | 
			
		||||
  let center;
 | 
			
		||||
  prevPoint = undefined;
 | 
			
		||||
  points.forEach((point) => {
 | 
			
		||||
    if (prevPoint && !center) {
 | 
			
		||||
      const vectorDistance = distance(point, prevPoint);
 | 
			
		||||
      if (vectorDistance < remainingDistance) {
 | 
			
		||||
        remainingDistance -= vectorDistance;
 | 
			
		||||
      } else {
 | 
			
		||||
        // The point is remainingDistance from prevPoint in the vector between prevPoint and point
 | 
			
		||||
        // Calculate the coordinates
 | 
			
		||||
        const distanceRatio = remainingDistance / vectorDistance;
 | 
			
		||||
        if (distanceRatio <= 0) {
 | 
			
		||||
          center = prevPoint;
 | 
			
		||||
        }
 | 
			
		||||
        if (distanceRatio >= 1) {
 | 
			
		||||
          center = { x: point.x, y: point.y };
 | 
			
		||||
        }
 | 
			
		||||
        if (distanceRatio > 0 && distanceRatio < 1) {
 | 
			
		||||
          center = {
 | 
			
		||||
            x: (1 - distanceRatio) * prevPoint.x + distanceRatio * point.x,
 | 
			
		||||
            y: (1 - distanceRatio) * prevPoint.y + distanceRatio * point.y,
 | 
			
		||||
          };
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    prevPoint = point;
 | 
			
		||||
  });
 | 
			
		||||
  // if relation is present (Arrows will be added), change cardinality point off-set distance (d)
 | 
			
		||||
  const d = 10 + terminalMarkerSize * 0.5;
 | 
			
		||||
  //Calculate Angle for x and y axis
 | 
			
		||||
  const angle = Math.atan2(points[0].y - center.y, points[0].x - center.x);
 | 
			
		||||
 | 
			
		||||
  const cardinalityPosition = { x: 0, y: 0 };
 | 
			
		||||
  const cardinalityPosition: Point = { x: 0, y: 0 };
 | 
			
		||||
  //Calculation cardinality position using angle, center point on the line/curve but perpendicular and with offset-distance
 | 
			
		||||
 | 
			
		||||
  //Calculation cardinality position using angle, center point on the line/curve but pendicular and with offset-distance
 | 
			
		||||
 | 
			
		||||
  cardinalityPosition.x = Math.sin(angle) * d + (points[0].x + center.x) / 2;
 | 
			
		||||
  cardinalityPosition.y = -Math.cos(angle) * d + (points[0].y + center.y) / 2;
 | 
			
		||||
  if (position === 'start_left') {
 | 
			
		||||
    cardinalityPosition.x = Math.sin(angle + Math.PI) * d + (points[0].x + center.x) / 2;
 | 
			
		||||
    cardinalityPosition.y = -Math.cos(angle + Math.PI) * d + (points[0].y + center.y) / 2;
 | 
			
		||||
  }
 | 
			
		||||
  if (position === 'end_right') {
 | 
			
		||||
  } else if (position === 'end_right') {
 | 
			
		||||
    cardinalityPosition.x = Math.sin(angle - Math.PI) * d + (points[0].x + center.x) / 2 - 5;
 | 
			
		||||
    cardinalityPosition.y = -Math.cos(angle - Math.PI) * d + (points[0].y + center.y) / 2 - 5;
 | 
			
		||||
  }
 | 
			
		||||
  if (position === 'end_left') {
 | 
			
		||||
  } else if (position === 'end_left') {
 | 
			
		||||
    cardinalityPosition.x = Math.sin(angle) * d + (points[0].x + center.x) / 2 - 5;
 | 
			
		||||
    cardinalityPosition.y = -Math.cos(angle) * d + (points[0].y + center.y) / 2 - 5;
 | 
			
		||||
  } else {
 | 
			
		||||
    cardinalityPosition.x = Math.sin(angle) * d + (points[0].x + center.x) / 2;
 | 
			
		||||
    cardinalityPosition.y = -Math.cos(angle) * d + (points[0].y + center.y) / 2;
 | 
			
		||||
  }
 | 
			
		||||
  return cardinalityPosition;
 | 
			
		||||
}
 | 
			
		||||
@@ -502,7 +458,7 @@ export function getStylesFromArray(arr: string[]): { style: string; labelStyle:
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return { style: style, labelStyle: labelStyle };
 | 
			
		||||
  return { style, labelStyle };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
let cnt = 0;
 | 
			
		||||
@@ -514,10 +470,10 @@ export const generateId = () => {
 | 
			
		||||
/**
 | 
			
		||||
 * Generates a random hexadecimal id of the given length.
 | 
			
		||||
 *
 | 
			
		||||
 * @param length - Length of ID.
 | 
			
		||||
 * @returns The generated ID.
 | 
			
		||||
 * @param length - Length of string.
 | 
			
		||||
 * @returns The generated string.
 | 
			
		||||
 */
 | 
			
		||||
function makeid(length: number): string {
 | 
			
		||||
function makeRandomHex(length: number): string {
 | 
			
		||||
  let result = '';
 | 
			
		||||
  const characters = '0123456789abcdef';
 | 
			
		||||
  const charactersLength = characters.length;
 | 
			
		||||
@@ -527,8 +483,8 @@ function makeid(length: number): string {
 | 
			
		||||
  return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const random = (options) => {
 | 
			
		||||
  return makeid(options.length);
 | 
			
		||||
export const random = (options: { length: number }) => {
 | 
			
		||||
  return makeRandomHex(options.length);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const getTextObj = function () {
 | 
			
		||||
@@ -544,6 +500,7 @@ export const getTextObj = function () {
 | 
			
		||||
    rx: 0,
 | 
			
		||||
    ry: 0,
 | 
			
		||||
    valign: undefined,
 | 
			
		||||
    text: '',
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@@ -574,7 +531,7 @@ export const drawSimpleText = function (
 | 
			
		||||
 | 
			
		||||
  const [, _fontSizePx] = parseFontSize(textData.fontSize);
 | 
			
		||||
 | 
			
		||||
  const textElem = elem.append('text');
 | 
			
		||||
  const textElem = elem.append('text') as any;
 | 
			
		||||
  textElem.attr('x', textData.x);
 | 
			
		||||
  textElem.attr('y', textData.y);
 | 
			
		||||
  textElem.style('text-anchor', textData.anchor);
 | 
			
		||||
@@ -582,6 +539,7 @@ export const drawSimpleText = function (
 | 
			
		||||
  textElem.style('font-size', _fontSizePx);
 | 
			
		||||
  textElem.style('font-weight', textData.fontWeight);
 | 
			
		||||
  textElem.attr('fill', textData.fill);
 | 
			
		||||
 | 
			
		||||
  if (textData.class !== undefined) {
 | 
			
		||||
    textElem.attr('class', textData.class);
 | 
			
		||||
  }
 | 
			
		||||
@@ -601,9 +559,9 @@ interface WrapLabelConfig {
 | 
			
		||||
  joinWith: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const wrapLabel: (label: string, maxWidth: string, config: WrapLabelConfig) => string =
 | 
			
		||||
export const wrapLabel: (label: string, maxWidth: number, config: WrapLabelConfig) => string =
 | 
			
		||||
  memoize(
 | 
			
		||||
    (label: string, maxWidth: string, config: WrapLabelConfig): string => {
 | 
			
		||||
    (label: string, maxWidth: number, config: WrapLabelConfig): string => {
 | 
			
		||||
      if (!label) {
 | 
			
		||||
        return label;
 | 
			
		||||
      }
 | 
			
		||||
@@ -615,7 +573,7 @@ export const wrapLabel: (label: string, maxWidth: string, config: WrapLabelConfi
 | 
			
		||||
        return label;
 | 
			
		||||
      }
 | 
			
		||||
      const words = label.split(' ');
 | 
			
		||||
      const completedLines = [];
 | 
			
		||||
      const completedLines: string[] = [];
 | 
			
		||||
      let nextLine = '';
 | 
			
		||||
      words.forEach((word, index) => {
 | 
			
		||||
        const wordLength = calculateTextWidth(`${word} `, config);
 | 
			
		||||
@@ -700,10 +658,6 @@ export function calculateTextHeight(
 | 
			
		||||
  text: Parameters<typeof calculateTextDimensions>[0],
 | 
			
		||||
  config: Parameters<typeof calculateTextDimensions>[1]
 | 
			
		||||
): ReturnType<typeof calculateTextDimensions>['height'] {
 | 
			
		||||
  config = Object.assign(
 | 
			
		||||
    { fontSize: 12, fontWeight: 400, fontFamily: 'Arial', margin: 15 },
 | 
			
		||||
    config
 | 
			
		||||
  );
 | 
			
		||||
  return calculateTextDimensions(text, config).height;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -719,20 +673,9 @@ export function calculateTextWidth(
 | 
			
		||||
  text: Parameters<typeof calculateTextDimensions>[0],
 | 
			
		||||
  config: Parameters<typeof calculateTextDimensions>[1]
 | 
			
		||||
): ReturnType<typeof calculateTextDimensions>['width'] {
 | 
			
		||||
  config = Object.assign({ fontSize: 12, fontWeight: 400, fontFamily: 'Arial' }, config);
 | 
			
		||||
  return calculateTextDimensions(text, config).width;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface TextDimensionConfig {
 | 
			
		||||
  fontSize?: number;
 | 
			
		||||
  fontWeight?: number;
 | 
			
		||||
  fontFamily?: string;
 | 
			
		||||
}
 | 
			
		||||
interface TextDimensions {
 | 
			
		||||
  width: number;
 | 
			
		||||
  height: number;
 | 
			
		||||
  lineHeight?: number;
 | 
			
		||||
}
 | 
			
		||||
/**
 | 
			
		||||
 * This calculates the dimensions of the given text, font size, font family, font weight, and
 | 
			
		||||
 * margins.
 | 
			
		||||
@@ -747,8 +690,7 @@ export const calculateTextDimensions: (
 | 
			
		||||
  config: TextDimensionConfig
 | 
			
		||||
) => TextDimensions = memoize(
 | 
			
		||||
  (text: string, config: TextDimensionConfig): TextDimensions => {
 | 
			
		||||
    config = Object.assign({ fontSize: 12, fontWeight: 400, fontFamily: 'Arial' }, config);
 | 
			
		||||
    const { fontSize, fontFamily, fontWeight } = config;
 | 
			
		||||
    const { fontSize = 12, fontFamily = 'Arial', fontWeight = 400 } = config;
 | 
			
		||||
    if (!text) {
 | 
			
		||||
      return { width: 0, height: 0 };
 | 
			
		||||
    }
 | 
			
		||||
@@ -772,12 +714,14 @@ export const calculateTextDimensions: (
 | 
			
		||||
    const g = body.append('svg');
 | 
			
		||||
 | 
			
		||||
    for (const fontFamily of fontFamilies) {
 | 
			
		||||
      let cheight = 0;
 | 
			
		||||
      let cHeight = 0;
 | 
			
		||||
      const dim = { width: 0, height: 0, lineHeight: 0 };
 | 
			
		||||
      for (const line of lines) {
 | 
			
		||||
        const textObj = getTextObj();
 | 
			
		||||
        textObj.text = line || ZERO_WIDTH_SPACE;
 | 
			
		||||
        // @ts-ignore TODO: Fix D3 types
 | 
			
		||||
        const textElem = drawSimpleText(g, textObj)
 | 
			
		||||
          // @ts-ignore TODO: Fix D3 types
 | 
			
		||||
          .style('font-size', _fontSizePx)
 | 
			
		||||
          .style('font-weight', fontWeight)
 | 
			
		||||
          .style('font-family', fontFamily);
 | 
			
		||||
@@ -787,9 +731,9 @@ export const calculateTextDimensions: (
 | 
			
		||||
          throw new Error('svg element not in render tree');
 | 
			
		||||
        }
 | 
			
		||||
        dim.width = Math.round(Math.max(dim.width, bBox.width));
 | 
			
		||||
        cheight = Math.round(bBox.height);
 | 
			
		||||
        dim.height += cheight;
 | 
			
		||||
        dim.lineHeight = Math.round(Math.max(dim.lineHeight, cheight));
 | 
			
		||||
        cHeight = Math.round(bBox.height);
 | 
			
		||||
        dim.height += cHeight;
 | 
			
		||||
        dim.lineHeight = Math.round(Math.max(dim.lineHeight, cHeight));
 | 
			
		||||
      }
 | 
			
		||||
      dims.push(dim);
 | 
			
		||||
    }
 | 
			
		||||
@@ -810,25 +754,18 @@ export const calculateTextDimensions: (
 | 
			
		||||
  (text, config) => `${text}${config.fontSize}${config.fontWeight}${config.fontFamily}`
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
export const initIdGenerator = class iterator {
 | 
			
		||||
  constructor(deterministic, seed?: any) {
 | 
			
		||||
    this.deterministic = deterministic;
 | 
			
		||||
export class InitIDGenerator {
 | 
			
		||||
  private count = 0;
 | 
			
		||||
  public next: () => number;
 | 
			
		||||
  constructor(deterministic = false, seed?: string) {
 | 
			
		||||
    // TODO: Seed is only used for length?
 | 
			
		||||
    this.seed = seed;
 | 
			
		||||
 | 
			
		||||
    // v11: Use the actual value of seed string to generate an initial value for count.
 | 
			
		||||
    this.count = seed ? seed.length : 0;
 | 
			
		||||
    this.next = deterministic ? () => this.count++ : () => Date.now();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
  next() {
 | 
			
		||||
    if (!this.deterministic) {
 | 
			
		||||
      return Date.now();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return this.count++;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
let decoder;
 | 
			
		||||
let decoder: HTMLDivElement;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Decodes HTML, source: {@link https://github.com/shrpne/entity-decode/blob/v2.0.1/browser.js}
 | 
			
		||||
@@ -840,20 +777,23 @@ export const entityDecode = function (html: string): string {
 | 
			
		||||
  decoder = decoder || document.createElement('div');
 | 
			
		||||
  // Escape HTML before decoding for HTML Entities
 | 
			
		||||
  html = escape(html).replace(/%26/g, '&').replace(/%23/g, '#').replace(/%3B/g, ';');
 | 
			
		||||
  // decoding
 | 
			
		||||
  decoder.innerHTML = html;
 | 
			
		||||
  return unescape(decoder.textContent);
 | 
			
		||||
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
 | 
			
		||||
  return unescape(decoder.textContent!);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export interface DetailedError {
 | 
			
		||||
  str: string;
 | 
			
		||||
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
 | 
			
		||||
  hash: any;
 | 
			
		||||
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
 | 
			
		||||
  error?: any;
 | 
			
		||||
  message?: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** @param error - The error to check */
 | 
			
		||||
export function isDetailedError(error: unknown): error is DetailedError {
 | 
			
		||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
 | 
			
		||||
export function isDetailedError(error: any): error is DetailedError {
 | 
			
		||||
  return 'str' in error;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -874,7 +814,7 @@ export function getErrorMessage(error: unknown): string {
 | 
			
		||||
 * @param title - The title. If empty, returns immediately.
 | 
			
		||||
 */
 | 
			
		||||
export const insertTitle = (
 | 
			
		||||
  parent,
 | 
			
		||||
  parent: D3Element,
 | 
			
		||||
  cssClass: string,
 | 
			
		||||
  titleTopMargin: number,
 | 
			
		||||
  title?: string
 | 
			
		||||
@@ -882,7 +822,10 @@ export const insertTitle = (
 | 
			
		||||
  if (!title) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  const bounds = parent.node().getBBox();
 | 
			
		||||
  const bounds = parent.node()?.getBBox();
 | 
			
		||||
  if (!bounds) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  parent
 | 
			
		||||
    .append('text')
 | 
			
		||||
    .text(title)
 | 
			
		||||
@@ -905,7 +848,7 @@ export const parseFontSize = (fontSize: string | number | undefined): [number?,
 | 
			
		||||
    return [fontSize, fontSize + 'px'];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const fontSizeNumber = parseInt(fontSize, 10);
 | 
			
		||||
  const fontSizeNumber = parseInt(fontSize ?? '', 10);
 | 
			
		||||
  if (Number.isNaN(fontSizeNumber)) {
 | 
			
		||||
    // if a number value can't be parsed, return null for both values
 | 
			
		||||
    return [undefined, undefined];
 | 
			
		||||
@@ -941,7 +884,7 @@ export default {
 | 
			
		||||
  random,
 | 
			
		||||
  runFunc,
 | 
			
		||||
  entityDecode,
 | 
			
		||||
  initIdGenerator,
 | 
			
		||||
  insertTitle,
 | 
			
		||||
  parseFontSize,
 | 
			
		||||
  InitIDGenerator,
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user