@likec4/core 1.6.1 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/colors/element.d.ts +1 -1
- package/dist/colors/element.js +1 -1
- package/dist/colors/index.d.ts +2 -2
- package/dist/colors/relationships.d.ts +1 -1
- package/dist/colors/relationships.js +1 -1
- package/dist/types/_common.d.ts +1 -1
- package/dist/types/expression.d.ts +26 -6
- package/dist/types/expression.js +15 -3
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.js +1 -0
- package/dist/types/operators.d.ts +35 -0
- package/dist/types/operators.js +59 -0
- package/dist/types/relation.d.ts +2 -0
- package/dist/types/view-changes.d.ts +3 -11
- package/dist/types/view.d.ts +53 -13
- package/dist/types/view.js +6 -0
- package/package.json +4 -4
- package/src/colors/element.ts +1 -1
- package/src/colors/relationships.ts +1 -1
- package/src/types/_common.ts +1 -1
- package/src/types/expression.ts +40 -9
- package/src/types/index.ts +1 -0
- package/src/types/operators.ts +114 -0
- package/src/types/relation.ts +2 -0
- package/src/types/view-changes.ts +3 -11
- package/src/types/view.ts +76 -13
package/dist/colors/element.d.ts
CHANGED
package/dist/colors/element.js
CHANGED
package/dist/colors/index.d.ts
CHANGED
|
@@ -48,7 +48,7 @@ export declare const defaultTheme: {
|
|
|
48
48
|
readonly fill: "#AC4D39";
|
|
49
49
|
readonly stroke: "#853A2D";
|
|
50
50
|
readonly hiContrast: "#FBD3CB";
|
|
51
|
-
readonly loContrast: "#
|
|
51
|
+
readonly loContrast: "#f5b2a3";
|
|
52
52
|
};
|
|
53
53
|
readonly green: {
|
|
54
54
|
readonly fill: "#428a4f";
|
|
@@ -108,7 +108,7 @@ export declare const defaultTheme: {
|
|
|
108
108
|
red: {
|
|
109
109
|
lineColor: "#AC4D39";
|
|
110
110
|
labelBgColor: "#b91c1c";
|
|
111
|
-
labelColor: "#
|
|
111
|
+
labelColor: "#f5b2a3";
|
|
112
112
|
};
|
|
113
113
|
secondary: {
|
|
114
114
|
lineColor: "#0ea5e9";
|
package/dist/types/_common.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { Opaque } from './opaque';
|
|
|
2
2
|
export type NonEmptyArray<T> = [T, ...T[]] | [...T[], T];
|
|
3
3
|
export type IconUrl = Opaque<string, 'IconUrl'>;
|
|
4
4
|
export type Point = readonly [x: number, y: number];
|
|
5
|
-
export interface
|
|
5
|
+
export interface XYPoint {
|
|
6
6
|
x: number;
|
|
7
7
|
y: number;
|
|
8
8
|
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import type { IconUrl } from './_common';
|
|
2
2
|
import type { BorderStyle, ElementKind, ElementShape, Fqn, Tag } from './element';
|
|
3
|
+
import type { WhereOperator } from './operators';
|
|
3
4
|
import type { RelationshipArrowType, RelationshipLineType } from './relation';
|
|
4
5
|
import type { ThemeColor } from './theme';
|
|
5
6
|
import type { ViewID } from './view';
|
|
6
7
|
interface BaseExpr {
|
|
8
|
+
where?: never;
|
|
7
9
|
element?: never;
|
|
8
10
|
custom?: never;
|
|
9
11
|
expanded?: never;
|
|
@@ -30,7 +32,7 @@ export interface ExpandedElementExpr extends Omit<BaseExpr, 'expanded'> {
|
|
|
30
32
|
export declare function isExpandedElementExpr(expr: Expression): expr is ExpandedElementExpr;
|
|
31
33
|
export interface CustomElementExpr extends Omit<BaseExpr, 'custom'> {
|
|
32
34
|
custom: {
|
|
33
|
-
|
|
35
|
+
expr: ElementExpression | ElementWhereExpr;
|
|
34
36
|
title?: string;
|
|
35
37
|
description?: string;
|
|
36
38
|
technology?: string;
|
|
@@ -59,7 +61,15 @@ export interface ElementTagExpr extends Omit<BaseExpr, 'elementTag' | 'isEqual'>
|
|
|
59
61
|
export declare function isElementTagExpr(expr: Expression): expr is ElementTagExpr;
|
|
60
62
|
export type ElementExpression = ElementRefExpr | WildcardExpr | ElementKindExpr | ElementTagExpr | ExpandedElementExpr;
|
|
61
63
|
export declare function isElement(expr: Expression): expr is ElementExpression;
|
|
62
|
-
export
|
|
64
|
+
export interface ElementWhereExpr extends Omit<BaseExpr, 'where'> {
|
|
65
|
+
where: {
|
|
66
|
+
expr: ElementExpression;
|
|
67
|
+
condition: WhereOperator;
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
export declare function isElementWhere(expr: Expression): expr is ElementWhereExpr;
|
|
71
|
+
export type ElementPredicateExpression = ElementExpression | ElementWhereExpr | CustomElementExpr;
|
|
72
|
+
export declare function isElementPredicateExpr(expr: Expression): expr is ElementPredicateExpression;
|
|
63
73
|
export interface RelationExpr extends Omit<BaseExpr, 'source' | 'target'> {
|
|
64
74
|
source: ElementExpression;
|
|
65
75
|
target: ElementExpression;
|
|
@@ -78,10 +88,21 @@ export interface OutgoingExpr extends Omit<BaseExpr, 'outgoing'> {
|
|
|
78
88
|
outgoing: ElementExpression;
|
|
79
89
|
}
|
|
80
90
|
export declare function isOutgoing(expr: Expression): expr is OutgoingExpr;
|
|
91
|
+
export type RelationExpression = RelationExpr | InOutExpr | IncomingExpr | OutgoingExpr;
|
|
92
|
+
export declare function isRelationExpression(expr: Expression): expr is RelationExpression;
|
|
93
|
+
export interface RelationWhereExpr extends Omit<BaseExpr, 'where'> {
|
|
94
|
+
where: {
|
|
95
|
+
expr: RelationExpression;
|
|
96
|
+
condition: WhereOperator;
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
export declare function isRelationWhere(expr: Expression): expr is RelationWhereExpr;
|
|
81
100
|
export interface CustomRelationExpr extends Omit<BaseExpr, 'customRelation'> {
|
|
82
101
|
customRelation: {
|
|
83
|
-
relation:
|
|
102
|
+
relation: RelationExpression | RelationWhereExpr;
|
|
84
103
|
title?: string;
|
|
104
|
+
description?: string;
|
|
105
|
+
technology?: string;
|
|
85
106
|
color?: ThemeColor;
|
|
86
107
|
line?: RelationshipLineType;
|
|
87
108
|
head?: RelationshipArrowType;
|
|
@@ -89,9 +110,8 @@ export interface CustomRelationExpr extends Omit<BaseExpr, 'customRelation'> {
|
|
|
89
110
|
};
|
|
90
111
|
}
|
|
91
112
|
export declare function isCustomRelationExpr(expr: Expression): expr is CustomRelationExpr;
|
|
92
|
-
export type
|
|
93
|
-
export
|
|
94
|
-
export declare function isAnyRelation(expr: Expression): expr is RelationPredicateExpression;
|
|
113
|
+
export type RelationPredicateExpression = RelationExpression | RelationWhereExpr | CustomRelationExpr;
|
|
114
|
+
export declare function isRelationPredicateExpr(expr: Expression): expr is RelationPredicateExpression;
|
|
95
115
|
export type Expression = ElementPredicateExpression | RelationPredicateExpression;
|
|
96
116
|
export {};
|
|
97
117
|
//# sourceMappingURL=expression.d.ts.map
|
package/dist/types/expression.js
CHANGED
|
@@ -5,7 +5,7 @@ export function isExpandedElementExpr(expr) {
|
|
|
5
5
|
return 'expanded' in expr;
|
|
6
6
|
}
|
|
7
7
|
export function isCustomElement(expr) {
|
|
8
|
-
return 'custom' in expr;
|
|
8
|
+
return 'custom' in expr && (isElement(expr.custom.expr) || isElementWhere(expr.custom.expr));
|
|
9
9
|
}
|
|
10
10
|
export function isWildcard(expr) {
|
|
11
11
|
return 'wildcard' in expr;
|
|
@@ -23,6 +23,12 @@ export function isElement(expr) {
|
|
|
23
23
|
|| isElementTagExpr(expr)
|
|
24
24
|
|| isExpandedElementExpr(expr);
|
|
25
25
|
}
|
|
26
|
+
export function isElementWhere(expr) {
|
|
27
|
+
return 'where' in expr && isElement(expr.where.expr);
|
|
28
|
+
}
|
|
29
|
+
export function isElementPredicateExpr(expr) {
|
|
30
|
+
return isElement(expr) || isElementWhere(expr) || isCustomElement(expr);
|
|
31
|
+
}
|
|
26
32
|
export function isRelation(expr) {
|
|
27
33
|
return 'source' in expr && 'target' in expr;
|
|
28
34
|
}
|
|
@@ -35,11 +41,17 @@ export function isIncoming(expr) {
|
|
|
35
41
|
export function isOutgoing(expr) {
|
|
36
42
|
return 'outgoing' in expr;
|
|
37
43
|
}
|
|
44
|
+
export function isRelationExpression(expr) {
|
|
45
|
+
return isRelation(expr) || isInOut(expr) || isIncoming(expr) || isOutgoing(expr);
|
|
46
|
+
}
|
|
47
|
+
export function isRelationWhere(expr) {
|
|
48
|
+
return 'where' in expr && isRelationExpression(expr.where.expr);
|
|
49
|
+
}
|
|
38
50
|
export function isCustomRelationExpr(expr) {
|
|
39
51
|
return 'customRelation' in expr;
|
|
40
52
|
}
|
|
41
|
-
export function
|
|
42
|
-
return
|
|
53
|
+
export function isRelationPredicateExpr(expr) {
|
|
54
|
+
return isRelationExpression(expr) || isRelationWhere(expr) || isCustomRelationExpr(expr);
|
|
43
55
|
}
|
|
44
56
|
// export const Expr = {
|
|
45
57
|
// isElementRef,
|
package/dist/types/index.d.ts
CHANGED
package/dist/types/index.js
CHANGED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { NonEmptyArray } from './_common';
|
|
2
|
+
export type EqualOperator<V> = {
|
|
3
|
+
eq: V;
|
|
4
|
+
} | {
|
|
5
|
+
neq: V;
|
|
6
|
+
};
|
|
7
|
+
export interface TagEqual {
|
|
8
|
+
tag: EqualOperator<string>;
|
|
9
|
+
}
|
|
10
|
+
export declare const isTagEqual: (operator: WhereOperator) => operator is TagEqual;
|
|
11
|
+
export interface KindEqual {
|
|
12
|
+
kind: EqualOperator<string>;
|
|
13
|
+
}
|
|
14
|
+
export declare const isKindEqual: (operator: WhereOperator) => operator is KindEqual;
|
|
15
|
+
export interface NotOperator<T> {
|
|
16
|
+
not: T;
|
|
17
|
+
}
|
|
18
|
+
export declare const isNotOperator: (operator: WhereOperator) => operator is NotOperator<WhereOperator>;
|
|
19
|
+
export interface AndOperator<T> {
|
|
20
|
+
and: NonEmptyArray<T>;
|
|
21
|
+
}
|
|
22
|
+
export declare const isAndOperator: (operator: WhereOperator) => operator is AndOperator<WhereOperator>;
|
|
23
|
+
export interface OrOperator<T> {
|
|
24
|
+
or: NonEmptyArray<T>;
|
|
25
|
+
}
|
|
26
|
+
export declare const isOrOperator: (operator: WhereOperator) => operator is OrOperator<WhereOperator>;
|
|
27
|
+
export type WhereOperator = TagEqual | KindEqual | NotOperator<WhereOperator> | AndOperator<WhereOperator> | OrOperator<WhereOperator>;
|
|
28
|
+
type Filterable<FTag extends string = string, FKind extends string = string> = {
|
|
29
|
+
tags?: FTag[] | null;
|
|
30
|
+
kind?: FKind;
|
|
31
|
+
};
|
|
32
|
+
type OperatorPredicate<V extends Filterable> = (value: V) => boolean;
|
|
33
|
+
export declare function whereOperatorAsPredicate<FTag extends string = string, FKind extends string = string>(operator: WhereOperator): OperatorPredicate<Filterable<FTag, FKind>>;
|
|
34
|
+
export {};
|
|
35
|
+
//# sourceMappingURL=operators.d.ts.map
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { allPass, anyPass, isNot, isNullish } from 'remeda';
|
|
2
|
+
import { nonexhaustive } from '../errors';
|
|
3
|
+
export const isTagEqual = (operator) => {
|
|
4
|
+
return 'tag' in operator;
|
|
5
|
+
};
|
|
6
|
+
export const isKindEqual = (operator) => {
|
|
7
|
+
return 'kind' in operator;
|
|
8
|
+
};
|
|
9
|
+
export const isNotOperator = (operator) => {
|
|
10
|
+
return 'not' in operator;
|
|
11
|
+
};
|
|
12
|
+
export const isAndOperator = (operator) => {
|
|
13
|
+
return 'and' in operator;
|
|
14
|
+
};
|
|
15
|
+
export const isOrOperator = (operator) => {
|
|
16
|
+
return 'or' in operator;
|
|
17
|
+
};
|
|
18
|
+
export function whereOperatorAsPredicate(operator) {
|
|
19
|
+
switch (true) {
|
|
20
|
+
case isTagEqual(operator): {
|
|
21
|
+
if ('eq' in operator.tag) {
|
|
22
|
+
const tag = operator.tag.eq;
|
|
23
|
+
return (value) => {
|
|
24
|
+
return Array.isArray(value.tags) && value.tags.includes(tag);
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
const tag = operator.tag.neq;
|
|
28
|
+
return (value) => {
|
|
29
|
+
return !Array.isArray(value.tags) || !value.tags.includes(tag);
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
case isKindEqual(operator): {
|
|
33
|
+
if ('eq' in operator.kind) {
|
|
34
|
+
const kind = operator.kind.eq;
|
|
35
|
+
return (value) => {
|
|
36
|
+
return value.kind === kind;
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
const kind = operator.kind.neq;
|
|
40
|
+
return (value) => {
|
|
41
|
+
return isNullish(value.kind) || value.kind !== kind;
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
case isNotOperator(operator): {
|
|
45
|
+
const predicate = whereOperatorAsPredicate(operator.not);
|
|
46
|
+
return isNot(predicate);
|
|
47
|
+
}
|
|
48
|
+
case isAndOperator(operator): {
|
|
49
|
+
const predicates = operator.and.map(whereOperatorAsPredicate);
|
|
50
|
+
return allPass(predicates);
|
|
51
|
+
}
|
|
52
|
+
case isOrOperator(operator): {
|
|
53
|
+
const predicates = operator.or.map(whereOperatorAsPredicate);
|
|
54
|
+
return anyPass(predicates);
|
|
55
|
+
}
|
|
56
|
+
default:
|
|
57
|
+
nonexhaustive(operator);
|
|
58
|
+
}
|
|
59
|
+
}
|
package/dist/types/relation.d.ts
CHANGED
|
@@ -14,6 +14,8 @@ export interface Relation {
|
|
|
14
14
|
readonly source: Fqn;
|
|
15
15
|
readonly target: Fqn;
|
|
16
16
|
readonly title: string;
|
|
17
|
+
readonly description?: string;
|
|
18
|
+
readonly technology?: string;
|
|
17
19
|
readonly tags?: NonEmptyArray<Tag>;
|
|
18
20
|
readonly kind?: RelationshipKind;
|
|
19
21
|
readonly color?: ThemeColor;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type { NonEmptyArray
|
|
1
|
+
import type { NonEmptyArray } from './_common';
|
|
2
2
|
import type { BorderStyle, ElementShape, Fqn } from './element';
|
|
3
3
|
import type { ThemeColor } from './theme';
|
|
4
|
-
import type {
|
|
4
|
+
import type { ViewManualLayout } from './view';
|
|
5
5
|
export declare namespace ViewChanges {
|
|
6
6
|
interface ChangeElementStyle {
|
|
7
7
|
op: 'change-element-style';
|
|
@@ -15,15 +15,7 @@ export declare namespace ViewChanges {
|
|
|
15
15
|
}
|
|
16
16
|
interface SaveManualLayout {
|
|
17
17
|
op: 'save-manual-layout';
|
|
18
|
-
|
|
19
|
-
x: number;
|
|
20
|
-
y: number;
|
|
21
|
-
width: number;
|
|
22
|
-
height: number;
|
|
23
|
-
}>;
|
|
24
|
-
edges: Record<EdgeId, {
|
|
25
|
-
controlPoints: XYPosition[];
|
|
26
|
-
}>;
|
|
18
|
+
layout: ViewManualLayout;
|
|
27
19
|
}
|
|
28
20
|
}
|
|
29
21
|
export type ViewChangeOp = ViewChanges.ChangeElementStyle | ViewChanges.SaveManualLayout;
|
package/dist/types/view.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { IconUrl, NonEmptyArray, Point,
|
|
1
|
+
import type { IconUrl, NonEmptyArray, Point, XYPoint } from './_common';
|
|
2
2
|
import type { ElementKind, ElementShape, ElementStyle, Fqn, Tag } from './element';
|
|
3
3
|
import type { ElementExpression, ElementPredicateExpression, Expression } from './expression';
|
|
4
4
|
import type { Opaque } from './opaque';
|
|
@@ -43,9 +43,8 @@ export interface BasicView<ViewType extends 'element' | 'dynamic'> {
|
|
|
43
43
|
/**
|
|
44
44
|
* For all views we find common ancestor path.
|
|
45
45
|
* This is used to generate relative paths, i.e.:
|
|
46
|
-
* - ""
|
|
47
|
-
* - "subdir"
|
|
48
|
-
* - "subdir/subdir1" for views in "<root>/subdir/subdir1"
|
|
46
|
+
* - "/home/project/index.c4" becomes "index.c4"
|
|
47
|
+
* - "/home/project/subdir/views.c4" becomes "subdir/views.c4"
|
|
49
48
|
*
|
|
50
49
|
* Undefined if the view is auto-generated.
|
|
51
50
|
*/
|
|
@@ -70,6 +69,12 @@ export interface DynamicViewStep {
|
|
|
70
69
|
readonly source: Fqn;
|
|
71
70
|
readonly target: Fqn;
|
|
72
71
|
readonly title: string | null;
|
|
72
|
+
readonly description?: string;
|
|
73
|
+
readonly technology?: string;
|
|
74
|
+
readonly color?: ThemeColor;
|
|
75
|
+
readonly line?: RelationshipLineType;
|
|
76
|
+
readonly head?: RelationshipArrowType;
|
|
77
|
+
readonly tail?: RelationshipArrowType;
|
|
73
78
|
readonly isBackward?: boolean;
|
|
74
79
|
}
|
|
75
80
|
export type DynamicViewIncludeRule = {
|
|
@@ -130,6 +135,8 @@ export interface ComputedEdge {
|
|
|
130
135
|
source: NodeId;
|
|
131
136
|
target: NodeId;
|
|
132
137
|
label: string | null;
|
|
138
|
+
description?: string;
|
|
139
|
+
technology?: string;
|
|
133
140
|
relations: RelationID[];
|
|
134
141
|
color?: ThemeColor;
|
|
135
142
|
line?: RelationshipLineType;
|
|
@@ -145,16 +152,31 @@ export interface ComputedEdge {
|
|
|
145
152
|
*/
|
|
146
153
|
dir?: 'forward' | 'back';
|
|
147
154
|
}
|
|
148
|
-
export interface ComputedElementView extends Omit<ElementView, 'rules'> {
|
|
155
|
+
export interface ComputedElementView extends Omit<ElementView, 'rules' | 'docUri'> {
|
|
149
156
|
readonly extends?: ViewID;
|
|
150
157
|
readonly autoLayout: ViewRuleAutoLayout['autoLayout'];
|
|
151
158
|
readonly nodes: ComputedNode[];
|
|
152
159
|
readonly edges: ComputedEdge[];
|
|
160
|
+
rules?: never;
|
|
161
|
+
docUri?: never;
|
|
162
|
+
/**
|
|
163
|
+
* Hash of the view object.
|
|
164
|
+
* This is used to detect changes in layout
|
|
165
|
+
*/
|
|
166
|
+
hash: string;
|
|
153
167
|
}
|
|
154
|
-
export interface ComputedDynamicView extends Omit<DynamicView, 'rules' | 'steps'> {
|
|
168
|
+
export interface ComputedDynamicView extends Omit<DynamicView, 'rules' | 'steps' | 'docUri'> {
|
|
155
169
|
readonly autoLayout: ViewRuleAutoLayout['autoLayout'];
|
|
156
170
|
readonly nodes: ComputedNode[];
|
|
157
171
|
readonly edges: ComputedEdge[];
|
|
172
|
+
steps?: never;
|
|
173
|
+
rules?: never;
|
|
174
|
+
docUri?: never;
|
|
175
|
+
/**
|
|
176
|
+
* Hash of the view object.
|
|
177
|
+
* This is used to detect changes in layout
|
|
178
|
+
*/
|
|
179
|
+
hash: string;
|
|
158
180
|
}
|
|
159
181
|
export declare function isComputedDynamicView(view: ComputedView): view is ComputedDynamicView;
|
|
160
182
|
export type ComputedView = ComputedElementView | ComputedDynamicView;
|
|
@@ -165,6 +187,7 @@ export type BBox = {
|
|
|
165
187
|
width: number;
|
|
166
188
|
height: number;
|
|
167
189
|
};
|
|
190
|
+
export declare function getBBoxCenter({ x, y, width, height }: BBox): XYPoint;
|
|
168
191
|
export interface DiagramNode extends ComputedNode {
|
|
169
192
|
width: number;
|
|
170
193
|
height: number;
|
|
@@ -173,23 +196,40 @@ export interface DiagramNode extends ComputedNode {
|
|
|
173
196
|
}
|
|
174
197
|
export interface DiagramEdge extends ComputedEdge {
|
|
175
198
|
points: NonEmptyArray<Point>;
|
|
199
|
+
controlPoints?: NonEmptyArray<XYPoint>;
|
|
176
200
|
labelBBox?: BBox | null;
|
|
201
|
+
dotpos?: string;
|
|
177
202
|
}
|
|
178
|
-
export interface DiagramView extends Omit<ComputedView, 'nodes' | 'edges'> {
|
|
203
|
+
export interface DiagramView extends Omit<ComputedView, 'nodes' | 'edges' | 'manualLayout'> {
|
|
179
204
|
readonly nodes: DiagramNode[];
|
|
180
205
|
readonly edges: DiagramEdge[];
|
|
206
|
+
readonly bounds: BBox;
|
|
207
|
+
/**
|
|
208
|
+
* If diagram has manual layout
|
|
209
|
+
* But was changed and layout should be recalculated
|
|
210
|
+
*/
|
|
211
|
+
hasLayoutDrift?: boolean;
|
|
212
|
+
manualLayout?: never;
|
|
213
|
+
}
|
|
214
|
+
export type ViewManualLayout = {
|
|
215
|
+
readonly hash: string;
|
|
216
|
+
readonly x: number;
|
|
217
|
+
readonly y: number;
|
|
181
218
|
readonly width: number;
|
|
182
219
|
readonly height: number;
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
220
|
+
readonly autoLayout: AutoLayoutDirection;
|
|
221
|
+
readonly nodes: Record<string, {
|
|
222
|
+
isCompound: boolean;
|
|
186
223
|
x: number;
|
|
187
224
|
y: number;
|
|
188
225
|
width: number;
|
|
189
226
|
height: number;
|
|
190
227
|
}>;
|
|
191
|
-
readonly edges: Record<
|
|
192
|
-
|
|
228
|
+
readonly edges: Record<string, {
|
|
229
|
+
dotpos?: string;
|
|
230
|
+
points: NonEmptyArray<Point>;
|
|
231
|
+
controlPoints?: NonEmptyArray<XYPoint>;
|
|
232
|
+
labelBBox?: BBox;
|
|
193
233
|
}>;
|
|
194
|
-
}
|
|
234
|
+
};
|
|
195
235
|
//# sourceMappingURL=view.d.ts.map
|
package/dist/types/view.js
CHANGED
|
@@ -42,3 +42,9 @@ export function isComputedDynamicView(view) {
|
|
|
42
42
|
export function isComputedElementView(view) {
|
|
43
43
|
return isNullish(view.__) || view.__ === 'element';
|
|
44
44
|
}
|
|
45
|
+
export function getBBoxCenter({ x, y, width, height }) {
|
|
46
|
+
return {
|
|
47
|
+
x: x + width / 2,
|
|
48
|
+
y: y + height / 2
|
|
49
|
+
};
|
|
50
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@likec4/core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"homepage": "https://likec4.dev",
|
|
6
6
|
"author": "Denis Davydkov <denis@davydkov.com>",
|
|
@@ -71,11 +71,11 @@
|
|
|
71
71
|
"type-fest": "^4.21.0"
|
|
72
72
|
},
|
|
73
73
|
"devDependencies": {
|
|
74
|
-
"@likec4/tsconfig": "1.
|
|
74
|
+
"@likec4/tsconfig": "1.7.0",
|
|
75
75
|
"@total-typescript/ts-reset": "^0.5.1",
|
|
76
76
|
"execa": "^9.3.0",
|
|
77
|
-
"typescript": "^5.5.
|
|
78
|
-
"vitest": "~2.0.
|
|
77
|
+
"typescript": "^5.5.4",
|
|
78
|
+
"vitest": "~2.0.5"
|
|
79
79
|
},
|
|
80
80
|
"packageManager": "yarn@4.3.1"
|
|
81
81
|
}
|
package/src/colors/element.ts
CHANGED
package/src/types/_common.ts
CHANGED
package/src/types/expression.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import type { IconUrl } from './_common'
|
|
2
2
|
import type { BorderStyle, ElementKind, ElementShape, Fqn, Tag } from './element'
|
|
3
|
+
import type { WhereOperator } from './operators'
|
|
3
4
|
import type { RelationshipArrowType, RelationshipLineType } from './relation'
|
|
4
5
|
import type { ThemeColor } from './theme'
|
|
5
6
|
import type { ViewID } from './view'
|
|
6
7
|
|
|
7
8
|
interface BaseExpr {
|
|
9
|
+
where?: never
|
|
8
10
|
element?: never
|
|
9
11
|
custom?: never
|
|
10
12
|
expanded?: never
|
|
@@ -38,7 +40,7 @@ export function isExpandedElementExpr(expr: Expression): expr is ExpandedElement
|
|
|
38
40
|
|
|
39
41
|
export interface CustomElementExpr extends Omit<BaseExpr, 'custom'> {
|
|
40
42
|
custom: {
|
|
41
|
-
|
|
43
|
+
expr: ElementExpression | ElementWhereExpr
|
|
42
44
|
title?: string
|
|
43
45
|
description?: string
|
|
44
46
|
technology?: string
|
|
@@ -52,7 +54,7 @@ export interface CustomElementExpr extends Omit<BaseExpr, 'custom'> {
|
|
|
52
54
|
}
|
|
53
55
|
|
|
54
56
|
export function isCustomElement(expr: Expression): expr is CustomElementExpr {
|
|
55
|
-
return 'custom' in expr
|
|
57
|
+
return 'custom' in expr && (isElement(expr.custom.expr) || isElementWhere(expr.custom.expr))
|
|
56
58
|
}
|
|
57
59
|
|
|
58
60
|
export interface WildcardExpr extends Omit<BaseExpr, 'wildcard'> {
|
|
@@ -91,7 +93,20 @@ export function isElement(expr: Expression): expr is ElementExpression {
|
|
|
91
93
|
|| isExpandedElementExpr(expr)
|
|
92
94
|
}
|
|
93
95
|
|
|
94
|
-
export
|
|
96
|
+
export interface ElementWhereExpr extends Omit<BaseExpr, 'where'> {
|
|
97
|
+
where: {
|
|
98
|
+
expr: ElementExpression
|
|
99
|
+
condition: WhereOperator
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
export function isElementWhere(expr: Expression): expr is ElementWhereExpr {
|
|
103
|
+
return 'where' in expr && isElement(expr.where.expr)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export type ElementPredicateExpression = ElementExpression | ElementWhereExpr | CustomElementExpr
|
|
107
|
+
export function isElementPredicateExpr(expr: Expression): expr is ElementPredicateExpression {
|
|
108
|
+
return isElement(expr) || isElementWhere(expr) || isCustomElement(expr)
|
|
109
|
+
}
|
|
95
110
|
|
|
96
111
|
export interface RelationExpr extends Omit<BaseExpr, 'source' | 'target'> {
|
|
97
112
|
source: ElementExpression
|
|
@@ -122,10 +137,28 @@ export function isOutgoing(expr: Expression): expr is OutgoingExpr {
|
|
|
122
137
|
return 'outgoing' in expr
|
|
123
138
|
}
|
|
124
139
|
|
|
140
|
+
export type RelationExpression = RelationExpr | InOutExpr | IncomingExpr | OutgoingExpr
|
|
141
|
+
|
|
142
|
+
export function isRelationExpression(expr: Expression): expr is RelationExpression {
|
|
143
|
+
return isRelation(expr) || isInOut(expr) || isIncoming(expr) || isOutgoing(expr)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export interface RelationWhereExpr extends Omit<BaseExpr, 'where'> {
|
|
147
|
+
where: {
|
|
148
|
+
expr: RelationExpression
|
|
149
|
+
condition: WhereOperator
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
export function isRelationWhere(expr: Expression): expr is RelationWhereExpr {
|
|
153
|
+
return 'where' in expr && isRelationExpression(expr.where.expr)
|
|
154
|
+
}
|
|
155
|
+
|
|
125
156
|
export interface CustomRelationExpr extends Omit<BaseExpr, 'customRelation'> {
|
|
126
157
|
customRelation: {
|
|
127
|
-
relation:
|
|
158
|
+
relation: RelationExpression | RelationWhereExpr
|
|
128
159
|
title?: string
|
|
160
|
+
description?: string
|
|
161
|
+
technology?: string
|
|
129
162
|
color?: ThemeColor
|
|
130
163
|
line?: RelationshipLineType
|
|
131
164
|
head?: RelationshipArrowType
|
|
@@ -136,12 +169,10 @@ export function isCustomRelationExpr(expr: Expression): expr is CustomRelationEx
|
|
|
136
169
|
return 'customRelation' in expr
|
|
137
170
|
}
|
|
138
171
|
|
|
139
|
-
export type
|
|
140
|
-
|
|
141
|
-
export type RelationPredicateExpression = RelationExpression | CustomRelationExpr
|
|
172
|
+
export type RelationPredicateExpression = RelationExpression | RelationWhereExpr | CustomRelationExpr
|
|
142
173
|
|
|
143
|
-
export function
|
|
144
|
-
return
|
|
174
|
+
export function isRelationPredicateExpr(expr: Expression): expr is RelationPredicateExpression {
|
|
175
|
+
return isRelationExpression(expr) || isRelationWhere(expr) || isCustomRelationExpr(expr)
|
|
145
176
|
}
|
|
146
177
|
|
|
147
178
|
export type Expression = ElementPredicateExpression | RelationPredicateExpression
|
package/src/types/index.ts
CHANGED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { allPass, anyPass, isNot, isNullish } from 'remeda'
|
|
2
|
+
import { nonexhaustive } from '../errors'
|
|
3
|
+
import type { NonEmptyArray } from './_common'
|
|
4
|
+
|
|
5
|
+
export type EqualOperator<V> = {
|
|
6
|
+
eq: V
|
|
7
|
+
} | {
|
|
8
|
+
neq: V
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface TagEqual {
|
|
12
|
+
tag: EqualOperator<string>
|
|
13
|
+
}
|
|
14
|
+
export const isTagEqual = (operator: WhereOperator): operator is TagEqual => {
|
|
15
|
+
return 'tag' in operator
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface KindEqual {
|
|
19
|
+
kind: EqualOperator<string>
|
|
20
|
+
}
|
|
21
|
+
export const isKindEqual = (operator: WhereOperator): operator is KindEqual => {
|
|
22
|
+
return 'kind' in operator
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface NotOperator<T> {
|
|
26
|
+
not: T
|
|
27
|
+
}
|
|
28
|
+
export const isNotOperator = (operator: WhereOperator): operator is NotOperator<WhereOperator> => {
|
|
29
|
+
return 'not' in operator
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface AndOperator<T> {
|
|
33
|
+
and: NonEmptyArray<T>
|
|
34
|
+
}
|
|
35
|
+
export const isAndOperator = (operator: WhereOperator): operator is AndOperator<WhereOperator> => {
|
|
36
|
+
return 'and' in operator
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface OrOperator<T> {
|
|
40
|
+
or: NonEmptyArray<T>
|
|
41
|
+
}
|
|
42
|
+
export const isOrOperator = (operator: WhereOperator): operator is OrOperator<WhereOperator> => {
|
|
43
|
+
return 'or' in operator
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export type WhereOperator =
|
|
47
|
+
| TagEqual
|
|
48
|
+
| KindEqual
|
|
49
|
+
| NotOperator<WhereOperator>
|
|
50
|
+
| AndOperator<WhereOperator>
|
|
51
|
+
| OrOperator<WhereOperator>
|
|
52
|
+
// type Ops<T> = UnionToIntersection<Op<T>>
|
|
53
|
+
// type Op1<T> = RequireExactlyOne<T>
|
|
54
|
+
|
|
55
|
+
// export type Operator1<T> = T extends Op<T> ? Op1<Ops<T>> : never
|
|
56
|
+
// export type Operator = Operator1<any>
|
|
57
|
+
|
|
58
|
+
// type L = keyof UnionToIntersection<Op<any>>
|
|
59
|
+
|
|
60
|
+
type Filterable<
|
|
61
|
+
FTag extends string = string,
|
|
62
|
+
FKind extends string = string
|
|
63
|
+
> = {
|
|
64
|
+
tags?: FTag[] | null
|
|
65
|
+
kind?: FKind
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
type OperatorPredicate<V extends Filterable> = (value: V) => boolean
|
|
69
|
+
|
|
70
|
+
export function whereOperatorAsPredicate<
|
|
71
|
+
FTag extends string = string,
|
|
72
|
+
FKind extends string = string
|
|
73
|
+
>(operator: WhereOperator): OperatorPredicate<Filterable<FTag, FKind>> {
|
|
74
|
+
switch (true) {
|
|
75
|
+
case isTagEqual(operator): {
|
|
76
|
+
if ('eq' in operator.tag) {
|
|
77
|
+
const tag = operator.tag.eq
|
|
78
|
+
return (value) => {
|
|
79
|
+
return Array.isArray(value.tags) && value.tags.includes(tag as FTag)
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
const tag = operator.tag.neq
|
|
83
|
+
return (value) => {
|
|
84
|
+
return !Array.isArray(value.tags) || !value.tags.includes(tag as FTag)
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
case isKindEqual(operator): {
|
|
88
|
+
if ('eq' in operator.kind) {
|
|
89
|
+
const kind = operator.kind.eq
|
|
90
|
+
return (value) => {
|
|
91
|
+
return value.kind === kind
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
const kind = operator.kind.neq
|
|
95
|
+
return (value) => {
|
|
96
|
+
return isNullish(value.kind) || value.kind !== kind
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
case isNotOperator(operator): {
|
|
100
|
+
const predicate = whereOperatorAsPredicate(operator.not)
|
|
101
|
+
return isNot(predicate)
|
|
102
|
+
}
|
|
103
|
+
case isAndOperator(operator): {
|
|
104
|
+
const predicates = operator.and.map(whereOperatorAsPredicate)
|
|
105
|
+
return allPass(predicates)
|
|
106
|
+
}
|
|
107
|
+
case isOrOperator(operator): {
|
|
108
|
+
const predicates = operator.or.map(whereOperatorAsPredicate)
|
|
109
|
+
return anyPass(predicates)
|
|
110
|
+
}
|
|
111
|
+
default:
|
|
112
|
+
nonexhaustive(operator)
|
|
113
|
+
}
|
|
114
|
+
}
|
package/src/types/relation.ts
CHANGED
|
@@ -30,6 +30,8 @@ export interface Relation {
|
|
|
30
30
|
readonly source: Fqn
|
|
31
31
|
readonly target: Fqn
|
|
32
32
|
readonly title: string
|
|
33
|
+
readonly description?: string
|
|
34
|
+
readonly technology?: string
|
|
33
35
|
readonly tags?: NonEmptyArray<Tag>
|
|
34
36
|
readonly kind?: RelationshipKind
|
|
35
37
|
readonly color?: ThemeColor
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type { NonEmptyArray,
|
|
1
|
+
import type { NonEmptyArray, XYPoint } from './_common'
|
|
2
2
|
import type { BorderStyle, ElementShape, Fqn } from './element'
|
|
3
3
|
import type { ThemeColor } from './theme'
|
|
4
|
-
import type { EdgeId } from './view'
|
|
4
|
+
import type { EdgeId, ViewManualLayout } from './view'
|
|
5
5
|
|
|
6
6
|
export namespace ViewChanges {
|
|
7
7
|
export interface ChangeElementStyle {
|
|
@@ -17,15 +17,7 @@ export namespace ViewChanges {
|
|
|
17
17
|
|
|
18
18
|
export interface SaveManualLayout {
|
|
19
19
|
op: 'save-manual-layout'
|
|
20
|
-
|
|
21
|
-
x: number
|
|
22
|
-
y: number
|
|
23
|
-
width: number
|
|
24
|
-
height: number
|
|
25
|
-
}>
|
|
26
|
-
edges: Record<EdgeId, {
|
|
27
|
-
controlPoints: XYPosition[]
|
|
28
|
-
}>
|
|
20
|
+
layout: ViewManualLayout
|
|
29
21
|
}
|
|
30
22
|
}
|
|
31
23
|
export type ViewChangeOp = ViewChanges.ChangeElementStyle | ViewChanges.SaveManualLayout
|
package/src/types/view.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { isNullish } from 'remeda'
|
|
2
|
-
import type { IconUrl, NonEmptyArray, Point,
|
|
2
|
+
import type { IconUrl, NonEmptyArray, Point, XYPoint } from './_common'
|
|
3
3
|
import type { ElementKind, ElementShape, ElementStyle, Fqn, Tag } from './element'
|
|
4
4
|
import type { ElementExpression, ElementPredicateExpression, Expression } from './expression'
|
|
5
5
|
import type { Opaque } from './opaque'
|
|
@@ -63,9 +63,8 @@ export interface BasicView<ViewType extends 'element' | 'dynamic'> {
|
|
|
63
63
|
/**
|
|
64
64
|
* For all views we find common ancestor path.
|
|
65
65
|
* This is used to generate relative paths, i.e.:
|
|
66
|
-
* - ""
|
|
67
|
-
* - "subdir"
|
|
68
|
-
* - "subdir/subdir1" for views in "<root>/subdir/subdir1"
|
|
66
|
+
* - "/home/project/index.c4" becomes "index.c4"
|
|
67
|
+
* - "/home/project/subdir/views.c4" becomes "subdir/views.c4"
|
|
69
68
|
*
|
|
70
69
|
* Undefined if the view is auto-generated.
|
|
71
70
|
*/
|
|
@@ -94,6 +93,12 @@ export interface DynamicViewStep {
|
|
|
94
93
|
readonly source: Fqn
|
|
95
94
|
readonly target: Fqn
|
|
96
95
|
readonly title: string | null
|
|
96
|
+
readonly description?: string
|
|
97
|
+
readonly technology?: string
|
|
98
|
+
readonly color?: ThemeColor
|
|
99
|
+
readonly line?: RelationshipLineType
|
|
100
|
+
readonly head?: RelationshipArrowType
|
|
101
|
+
readonly tail?: RelationshipArrowType
|
|
97
102
|
readonly isBackward?: boolean
|
|
98
103
|
}
|
|
99
104
|
|
|
@@ -188,6 +193,8 @@ export interface ComputedEdge {
|
|
|
188
193
|
source: NodeId
|
|
189
194
|
target: NodeId
|
|
190
195
|
label: string | null
|
|
196
|
+
description?: string
|
|
197
|
+
technology?: string
|
|
191
198
|
relations: RelationID[]
|
|
192
199
|
color?: ThemeColor
|
|
193
200
|
line?: RelationshipLineType
|
|
@@ -204,16 +211,33 @@ export interface ComputedEdge {
|
|
|
204
211
|
dir?: 'forward' | 'back'
|
|
205
212
|
}
|
|
206
213
|
|
|
207
|
-
export interface ComputedElementView extends Omit<ElementView, 'rules'> {
|
|
214
|
+
export interface ComputedElementView extends Omit<ElementView, 'rules' | 'docUri'> {
|
|
208
215
|
readonly extends?: ViewID
|
|
209
216
|
readonly autoLayout: ViewRuleAutoLayout['autoLayout']
|
|
210
217
|
readonly nodes: ComputedNode[]
|
|
211
218
|
readonly edges: ComputedEdge[]
|
|
219
|
+
rules?: never
|
|
220
|
+
docUri?: never
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Hash of the view object.
|
|
224
|
+
* This is used to detect changes in layout
|
|
225
|
+
*/
|
|
226
|
+
hash: string
|
|
212
227
|
}
|
|
213
|
-
export interface ComputedDynamicView extends Omit<DynamicView, 'rules' | 'steps'> {
|
|
228
|
+
export interface ComputedDynamicView extends Omit<DynamicView, 'rules' | 'steps' | 'docUri'> {
|
|
214
229
|
readonly autoLayout: ViewRuleAutoLayout['autoLayout']
|
|
215
230
|
readonly nodes: ComputedNode[]
|
|
216
231
|
readonly edges: ComputedEdge[]
|
|
232
|
+
steps?: never
|
|
233
|
+
rules?: never
|
|
234
|
+
docUri?: never
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Hash of the view object.
|
|
238
|
+
* This is used to detect changes in layout
|
|
239
|
+
*/
|
|
240
|
+
hash: string
|
|
217
241
|
}
|
|
218
242
|
export function isComputedDynamicView(view: ComputedView): view is ComputedDynamicView {
|
|
219
243
|
return view.__ === 'dynamic'
|
|
@@ -233,6 +257,18 @@ export type BBox = {
|
|
|
233
257
|
height: number
|
|
234
258
|
}
|
|
235
259
|
|
|
260
|
+
export function getBBoxCenter({
|
|
261
|
+
x,
|
|
262
|
+
y,
|
|
263
|
+
width,
|
|
264
|
+
height
|
|
265
|
+
}: BBox): XYPoint {
|
|
266
|
+
return {
|
|
267
|
+
x: x + width / 2,
|
|
268
|
+
y: y + height / 2
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
236
272
|
export interface DiagramNode extends ComputedNode {
|
|
237
273
|
width: number
|
|
238
274
|
height: number
|
|
@@ -242,25 +278,52 @@ export interface DiagramNode extends ComputedNode {
|
|
|
242
278
|
}
|
|
243
279
|
|
|
244
280
|
export interface DiagramEdge extends ComputedEdge {
|
|
281
|
+
// Bezier points
|
|
245
282
|
points: NonEmptyArray<Point>
|
|
283
|
+
// Control points to adjust the edge
|
|
284
|
+
controlPoints?: NonEmptyArray<XYPoint>
|
|
246
285
|
labelBBox?: BBox | null
|
|
286
|
+
// Graphviz edge POS
|
|
287
|
+
// TODO: temporary solution, should be moved out
|
|
288
|
+
dotpos?: string
|
|
247
289
|
}
|
|
248
290
|
|
|
249
|
-
export interface DiagramView extends Omit<ComputedView, 'nodes' | 'edges'> {
|
|
291
|
+
export interface DiagramView extends Omit<ComputedView, 'nodes' | 'edges' | 'manualLayout'> {
|
|
250
292
|
readonly nodes: DiagramNode[]
|
|
251
293
|
readonly edges: DiagramEdge[]
|
|
252
|
-
readonly
|
|
253
|
-
|
|
294
|
+
readonly bounds: BBox
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* If diagram has manual layout
|
|
298
|
+
* But was changed and layout should be recalculated
|
|
299
|
+
*/
|
|
300
|
+
hasLayoutDrift?: boolean
|
|
301
|
+
// Should not exist in DiagramView
|
|
302
|
+
manualLayout?: never
|
|
254
303
|
}
|
|
255
304
|
|
|
256
|
-
export
|
|
257
|
-
|
|
305
|
+
export type ViewManualLayout = {
|
|
306
|
+
// Object hash of previous layout
|
|
307
|
+
readonly hash: string
|
|
308
|
+
readonly x: number
|
|
309
|
+
readonly y: number
|
|
310
|
+
readonly width: number
|
|
311
|
+
readonly height: number
|
|
312
|
+
readonly autoLayout: AutoLayoutDirection
|
|
313
|
+
readonly nodes: Record<string, {
|
|
314
|
+
isCompound: boolean
|
|
258
315
|
x: number
|
|
259
316
|
y: number
|
|
260
317
|
width: number
|
|
261
318
|
height: number
|
|
262
319
|
}>
|
|
263
|
-
readonly edges: Record<
|
|
264
|
-
|
|
320
|
+
readonly edges: Record<string, {
|
|
321
|
+
// Graphviz edge POS
|
|
322
|
+
dotpos?: string
|
|
323
|
+
// Bezier points
|
|
324
|
+
points: NonEmptyArray<Point>
|
|
325
|
+
// Control points to adjust the edge
|
|
326
|
+
controlPoints?: NonEmptyArray<XYPoint>
|
|
327
|
+
labelBBox?: BBox
|
|
265
328
|
}>
|
|
266
329
|
}
|