@likec4/language-server 1.4.0 → 1.6.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/README.md +1 -1
- package/contrib/likec4.tmLanguage.json +1 -1
- package/package.json +26 -14
- package/src/Rpc.ts +25 -2
- package/src/ast.ts +19 -15
- package/src/generated/ast.ts +390 -203
- package/src/generated/grammar.ts +1 -1
- package/src/generated-lib/icons.ts +952 -0
- package/src/like-c4.langium +120 -64
- package/src/likec4lib.ts +7 -0
- package/src/lsp/DocumentSymbolProvider.ts +28 -1
- package/src/lsp/SemanticTokenProvider.ts +41 -22
- package/src/model/fqn-computation.ts +29 -7
- package/src/model/fqn-index.ts +18 -31
- package/src/model/model-builder.ts +13 -9
- package/src/model/model-locator.ts +7 -7
- package/src/model/model-parser.ts +166 -69
- package/src/model-change/changeElementStyle.ts +6 -6
- package/src/model-graph/compute-view/__test__/fixture.ts +52 -24
- package/src/model-graph/compute-view/compute.ts +51 -20
- package/src/model-graph/compute-view/predicates.ts +6 -1
- package/src/model-graph/dynamic-view/__test__/fixture.ts +2 -2
- package/src/model-graph/dynamic-view/compute.ts +2 -2
- package/src/model-graph/utils/{applyElementCustomProperties.ts → applyCustomElementProperties.ts} +5 -3
- package/src/model-graph/utils/applyCustomRelationProperties.ts +50 -0
- package/src/model-graph/utils/applyViewRuleStyles.ts +11 -34
- package/src/model-graph/utils/elementExpressionToPredicate.ts +32 -0
- package/src/references/scope-computation.ts +113 -60
- package/src/references/scope-provider.ts +3 -23
- package/src/shared/NodeKindProvider.ts +1 -0
- package/src/shared/WorkspaceManager.ts +15 -6
- package/src/validation/dynamic-view-rule.ts +19 -26
- package/src/validation/element.ts +8 -4
- package/src/validation/index.ts +9 -6
- package/src/validation/property-checks.ts +23 -1
- package/src/validation/view-predicates/custom-element-expr.ts +21 -8
- package/src/validation/view-predicates/custom-relation-expr.ts +16 -0
- package/src/validation/view-predicates/expanded-element.ts +13 -24
- package/src/validation/view-predicates/incoming.ts +5 -5
- package/src/validation/view-predicates/index.ts +1 -0
- package/src/validation/view-predicates/outgoing.ts +5 -5
- package/src/view-utils/assignNavigateTo.ts +2 -2
- package/src/view-utils/manual-layout.ts +4 -2
- package/src/view-utils/resolve-extended-views.ts +2 -2
- package/src/view-utils/resolve-relative-paths.ts +3 -3
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
BorderStyle,
|
|
3
3
|
ComputedView,
|
|
4
|
+
CustomRelationExpr as C4CustomRelationExpr,
|
|
4
5
|
Element,
|
|
5
6
|
ElementExpression as C4ElementExpression,
|
|
6
7
|
ElementKind,
|
|
@@ -9,15 +10,16 @@ import type {
|
|
|
9
10
|
Fqn,
|
|
10
11
|
Relation,
|
|
11
12
|
RelationID,
|
|
13
|
+
RelationshipArrowType,
|
|
14
|
+
RelationshipLineType,
|
|
12
15
|
Tag,
|
|
13
16
|
ThemeColor,
|
|
14
17
|
ViewID,
|
|
15
18
|
ViewRule,
|
|
16
|
-
|
|
19
|
+
ViewRulePredicate,
|
|
17
20
|
ViewRuleStyle
|
|
18
21
|
} from '@likec4/core'
|
|
19
|
-
import {
|
|
20
|
-
import { indexBy, isString, pick } from 'remeda'
|
|
22
|
+
import { indexBy, isString, map, prop } from 'remeda'
|
|
21
23
|
import { LikeC4ModelGraph } from '../../LikeC4ModelGraph'
|
|
22
24
|
import { computeElementView } from '../index'
|
|
23
25
|
|
|
@@ -195,16 +197,23 @@ export type FakeElementIds = keyof typeof fakeElements
|
|
|
195
197
|
const rel = ({
|
|
196
198
|
source,
|
|
197
199
|
target,
|
|
198
|
-
title
|
|
200
|
+
title,
|
|
201
|
+
...props
|
|
199
202
|
}: {
|
|
200
203
|
source: FakeElementIds
|
|
201
204
|
target: FakeElementIds
|
|
202
205
|
title?: string
|
|
206
|
+
kind?: string
|
|
207
|
+
color?: ThemeColor
|
|
208
|
+
line?: RelationshipLineType
|
|
209
|
+
head?: RelationshipArrowType
|
|
210
|
+
tail?: RelationshipArrowType
|
|
203
211
|
}): Relation => ({
|
|
204
212
|
id: `${source}:${target}` as RelationID,
|
|
205
213
|
title: title ?? '',
|
|
206
214
|
source: source as Fqn,
|
|
207
|
-
target: target as Fqn
|
|
215
|
+
target: target as Fqn,
|
|
216
|
+
...(props as any)
|
|
208
217
|
})
|
|
209
218
|
|
|
210
219
|
export const fakeRelations = [
|
|
@@ -251,17 +260,24 @@ export const fakeRelations = [
|
|
|
251
260
|
rel({
|
|
252
261
|
source: 'cloud.frontend.dashboard',
|
|
253
262
|
target: 'cloud.backend.graphql',
|
|
254
|
-
|
|
263
|
+
kind: 'graphlql',
|
|
264
|
+
title: 'requests',
|
|
265
|
+
line: 'solid'
|
|
255
266
|
}),
|
|
256
267
|
rel({
|
|
257
268
|
source: 'cloud.frontend.adminPanel',
|
|
258
269
|
target: 'cloud.backend.graphql',
|
|
259
|
-
|
|
270
|
+
kind: 'graphlql',
|
|
271
|
+
title: 'fetches',
|
|
272
|
+
line: 'dashed',
|
|
273
|
+
tail: 'odiamond'
|
|
260
274
|
}),
|
|
261
275
|
rel({
|
|
262
276
|
source: 'cloud',
|
|
263
277
|
target: 'amazon',
|
|
264
|
-
title: 'uses'
|
|
278
|
+
title: 'uses',
|
|
279
|
+
head: 'diamond',
|
|
280
|
+
tail: 'odiamond'
|
|
265
281
|
}),
|
|
266
282
|
rel({
|
|
267
283
|
source: 'cloud.backend',
|
|
@@ -328,7 +344,7 @@ type CustomExpr = {
|
|
|
328
344
|
}
|
|
329
345
|
}
|
|
330
346
|
|
|
331
|
-
type Expression =
|
|
347
|
+
export type Expression =
|
|
332
348
|
| ElementRefExpr
|
|
333
349
|
| InOutExpr
|
|
334
350
|
| IncomingExpr
|
|
@@ -336,7 +352,19 @@ type Expression =
|
|
|
336
352
|
| RelationExpr
|
|
337
353
|
| CustomExpr
|
|
338
354
|
|
|
339
|
-
function
|
|
355
|
+
export function $customRelation(
|
|
356
|
+
relation: RelationExpr,
|
|
357
|
+
props: Omit<C4CustomRelationExpr['customRelation'], 'relation'>
|
|
358
|
+
): C4CustomRelationExpr {
|
|
359
|
+
return {
|
|
360
|
+
customRelation: {
|
|
361
|
+
relation: $expr(relation) as any,
|
|
362
|
+
...props
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
export function $expr(expr: Expression | C4Expression): C4Expression {
|
|
340
368
|
if (!isString(expr)) {
|
|
341
369
|
return expr as C4Expression
|
|
342
370
|
}
|
|
@@ -346,31 +374,31 @@ function toExpression(expr: Expression): C4Expression {
|
|
|
346
374
|
if (expr.startsWith('->')) {
|
|
347
375
|
if (expr.endsWith('->')) {
|
|
348
376
|
return {
|
|
349
|
-
inout:
|
|
377
|
+
inout: $expr(expr.replace(/->/g, '').trim() as ElementRefExpr) as any
|
|
350
378
|
}
|
|
351
379
|
}
|
|
352
380
|
return {
|
|
353
|
-
incoming:
|
|
381
|
+
incoming: $expr(expr.replace('-> ', '') as ElementRefExpr) as any
|
|
354
382
|
}
|
|
355
383
|
}
|
|
356
384
|
if (expr.endsWith(' ->')) {
|
|
357
385
|
return {
|
|
358
|
-
outgoing:
|
|
386
|
+
outgoing: $expr(expr.replace(' ->', '') as ElementRefExpr) as any
|
|
359
387
|
}
|
|
360
388
|
}
|
|
361
389
|
if (expr.includes(' <-> ')) {
|
|
362
390
|
const [source, target] = expr.split(' <-> ')
|
|
363
391
|
return {
|
|
364
|
-
source:
|
|
365
|
-
target:
|
|
392
|
+
source: $expr(source as ElementRefExpr) as any,
|
|
393
|
+
target: $expr(target as ElementRefExpr) as any,
|
|
366
394
|
isBidirectional: true
|
|
367
395
|
}
|
|
368
396
|
}
|
|
369
397
|
if (expr.includes(' -> ')) {
|
|
370
398
|
const [source, target] = expr.split(' -> ')
|
|
371
399
|
return {
|
|
372
|
-
source:
|
|
373
|
-
target:
|
|
400
|
+
source: $expr(source as ElementRefExpr) as any,
|
|
401
|
+
target: $expr(target as ElementRefExpr) as any
|
|
374
402
|
}
|
|
375
403
|
}
|
|
376
404
|
if (expr.endsWith('._')) {
|
|
@@ -390,20 +418,20 @@ function toExpression(expr: Expression): C4Expression {
|
|
|
390
418
|
}
|
|
391
419
|
}
|
|
392
420
|
|
|
393
|
-
export function $include(expr: Expression):
|
|
421
|
+
export function $include(expr: Expression | C4Expression): ViewRulePredicate {
|
|
394
422
|
return {
|
|
395
|
-
include: [
|
|
423
|
+
include: [$expr(expr)]
|
|
396
424
|
}
|
|
397
425
|
}
|
|
398
|
-
export function $exclude(expr: Expression):
|
|
426
|
+
export function $exclude(expr: Expression | C4Expression): ViewRulePredicate {
|
|
399
427
|
return {
|
|
400
|
-
exclude: [
|
|
428
|
+
exclude: [$expr(expr)]
|
|
401
429
|
}
|
|
402
430
|
}
|
|
403
431
|
|
|
404
432
|
export function $style(element: ElementRefExpr, style: ViewRuleStyle['style']): ViewRuleStyle {
|
|
405
433
|
return {
|
|
406
|
-
targets: [
|
|
434
|
+
targets: [$expr(element) as C4ElementExpression],
|
|
407
435
|
style: Object.assign({}, style)
|
|
408
436
|
}
|
|
409
437
|
}
|
|
@@ -432,7 +460,7 @@ export function computeView(
|
|
|
432
460
|
)
|
|
433
461
|
}
|
|
434
462
|
return Object.assign(result, {
|
|
435
|
-
nodeIds:
|
|
436
|
-
edgeIds:
|
|
463
|
+
nodeIds: map(result.nodes, prop('id')) as string[],
|
|
464
|
+
edgeIds: map(result.edges, prop('id')) as string[]
|
|
437
465
|
})
|
|
438
466
|
}
|
|
@@ -5,7 +5,7 @@ import type {
|
|
|
5
5
|
Element,
|
|
6
6
|
ElementView,
|
|
7
7
|
Relation,
|
|
8
|
-
|
|
8
|
+
ViewRulePredicate
|
|
9
9
|
} from '@likec4/core'
|
|
10
10
|
import {
|
|
11
11
|
ancestorsFqn,
|
|
@@ -14,15 +14,16 @@ import {
|
|
|
14
14
|
Expr,
|
|
15
15
|
invariant,
|
|
16
16
|
isAncestor,
|
|
17
|
-
|
|
17
|
+
isScopedElementView,
|
|
18
18
|
isViewRuleAutoLayout,
|
|
19
|
-
|
|
19
|
+
isViewRulePredicate,
|
|
20
20
|
nonexhaustive,
|
|
21
21
|
parentFqn
|
|
22
22
|
} from '@likec4/core'
|
|
23
|
-
import { hasAtLeast, isTruthy, unique } from 'remeda'
|
|
23
|
+
import { first, flatMap, hasAtLeast, isTruthy, unique } from 'remeda'
|
|
24
24
|
import type { LikeC4ModelGraph } from '../LikeC4ModelGraph'
|
|
25
|
-
import {
|
|
25
|
+
import { applyCustomElementProperties } from '../utils/applyCustomElementProperties'
|
|
26
|
+
import { applyCustomRelationProperties } from '../utils/applyCustomRelationProperties'
|
|
26
27
|
import { applyViewRuleStyles } from '../utils/applyViewRuleStyles'
|
|
27
28
|
import { buildComputeNodes } from '../utils/buildComputeNodes'
|
|
28
29
|
import { sortNodes } from '../utils/sortNodes'
|
|
@@ -35,6 +36,7 @@ import {
|
|
|
35
36
|
excludeRelationExpr,
|
|
36
37
|
excludeWildcardRef,
|
|
37
38
|
includeCustomElement,
|
|
39
|
+
includeCustomRelation,
|
|
38
40
|
includeElementKindOrTag,
|
|
39
41
|
includeElementRef,
|
|
40
42
|
includeExpandedElementExpr,
|
|
@@ -90,7 +92,7 @@ export class ComputeCtx {
|
|
|
90
92
|
this.reset()
|
|
91
93
|
const { rules, ...view } = this.view
|
|
92
94
|
|
|
93
|
-
const viewPredicates = rules.filter(
|
|
95
|
+
const viewPredicates = rules.filter(isViewRulePredicate)
|
|
94
96
|
if (this.root && viewPredicates.length == 0) {
|
|
95
97
|
this.addElement(this.graph.element(this.root))
|
|
96
98
|
}
|
|
@@ -132,7 +134,7 @@ export class ComputeCtx {
|
|
|
132
134
|
// but we need to keep the initial sort
|
|
133
135
|
const initialSort = elements.flatMap(e => nodesMap.get(e.id) ?? [])
|
|
134
136
|
|
|
135
|
-
const nodes =
|
|
137
|
+
const nodes = applyCustomElementProperties(
|
|
136
138
|
rules,
|
|
137
139
|
applyViewRuleStyles(
|
|
138
140
|
rules,
|
|
@@ -156,18 +158,18 @@ export class ComputeCtx {
|
|
|
156
158
|
...view,
|
|
157
159
|
autoLayout: autoLayoutRule?.autoLayout ?? 'TB',
|
|
158
160
|
nodes,
|
|
159
|
-
edges:
|
|
161
|
+
edges: applyCustomRelationProperties(rules, nodes, sortedEdges)
|
|
160
162
|
}
|
|
161
163
|
}
|
|
162
164
|
|
|
163
165
|
protected get root() {
|
|
164
|
-
return
|
|
166
|
+
return isScopedElementView(this.view) ? this.view.viewOf : null
|
|
165
167
|
}
|
|
166
168
|
|
|
167
169
|
protected get computedEdges(): ComputedEdge[] {
|
|
168
170
|
return this.ctxEdges.map((e): ComputedEdge => {
|
|
169
171
|
invariant(hasAtLeast(e.relations, 1), 'Edge must have at least one relation')
|
|
170
|
-
const relations =
|
|
172
|
+
const relations = e.relations.toSorted(compareRelations)
|
|
171
173
|
const source = e.source.id
|
|
172
174
|
const target = e.target.id
|
|
173
175
|
|
|
@@ -185,21 +187,44 @@ export class ComputeCtx {
|
|
|
185
187
|
relation = relations[0]
|
|
186
188
|
} else {
|
|
187
189
|
relation = relations.find(r => r.source === source && r.target === target)
|
|
188
|
-
relation ??= relations.find(r => r.source === source || r.target === target)
|
|
190
|
+
// relation ??= relations.toReversed().find(r => r.source === source || r.target === target)
|
|
189
191
|
}
|
|
190
192
|
|
|
191
193
|
// This edge represents mutliple relations
|
|
192
194
|
// We use label if only it is the same for all relations
|
|
193
195
|
if (!relation) {
|
|
194
|
-
const
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
edge.label = labels[0]
|
|
198
|
-
} else {
|
|
199
|
-
edge.label = '[...]'
|
|
196
|
+
const shared = relations.reduce((acc, r) => {
|
|
197
|
+
if (r.color && acc.color !== r.color) {
|
|
198
|
+
acc.color = undefined
|
|
200
199
|
}
|
|
201
|
-
|
|
202
|
-
|
|
200
|
+
if (r.head && acc.head !== r.kind) {
|
|
201
|
+
acc.head = undefined
|
|
202
|
+
}
|
|
203
|
+
if (r.tail && acc.tail !== r.kind) {
|
|
204
|
+
acc.tail = undefined
|
|
205
|
+
}
|
|
206
|
+
if (r.line && acc.line !== r.line) {
|
|
207
|
+
acc.line = undefined
|
|
208
|
+
}
|
|
209
|
+
if (isTruthy(r.title) && acc.title !== r.title) {
|
|
210
|
+
acc.title = '[...]'
|
|
211
|
+
}
|
|
212
|
+
return acc
|
|
213
|
+
}, {
|
|
214
|
+
title: first(flatMap(relations, r => isTruthy(r.title) ? r.title : [])),
|
|
215
|
+
head: first(flatMap(relations, r => isTruthy(r.head) ? r.head : [])),
|
|
216
|
+
tail: first(flatMap(relations, r => isTruthy(r.tail) ? r.tail : [])),
|
|
217
|
+
color: first(flatMap(relations, r => isTruthy(r.color) ? r.color : [])),
|
|
218
|
+
line: first(flatMap(relations, r => isTruthy(r.line) ? r.line : []))
|
|
219
|
+
})
|
|
220
|
+
return Object.assign(
|
|
221
|
+
edge,
|
|
222
|
+
isTruthy(shared.title) && { label: shared.title },
|
|
223
|
+
shared.color && { color: shared.color },
|
|
224
|
+
shared.line && { line: shared.line },
|
|
225
|
+
shared.head && { head: shared.head },
|
|
226
|
+
shared.tail && { tail: shared.tail }
|
|
227
|
+
)
|
|
203
228
|
}
|
|
204
229
|
|
|
205
230
|
return Object.assign(
|
|
@@ -374,7 +399,7 @@ export class ComputeCtx {
|
|
|
374
399
|
}, [] as ComputeCtx.Edge[])
|
|
375
400
|
}
|
|
376
401
|
|
|
377
|
-
protected processPredicates(viewRules:
|
|
402
|
+
protected processPredicates(viewRules: ViewRulePredicate[]): this {
|
|
378
403
|
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
379
404
|
for (const rule of viewRules) {
|
|
380
405
|
const isInclude = 'include' in rule
|
|
@@ -386,6 +411,12 @@ export class ComputeCtx {
|
|
|
386
411
|
}
|
|
387
412
|
continue
|
|
388
413
|
}
|
|
414
|
+
if (Expr.isCustomRelationExpr(expr)) {
|
|
415
|
+
if (isInclude) {
|
|
416
|
+
includeCustomRelation.call(this, expr)
|
|
417
|
+
}
|
|
418
|
+
continue
|
|
419
|
+
}
|
|
389
420
|
if (Expr.isExpandedElementExpr(expr)) {
|
|
390
421
|
if (isInclude) {
|
|
391
422
|
includeExpandedElementExpr.call(this, expr)
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import type { Element } from '@likec4/core'
|
|
2
2
|
import { Expr, invariant, nonexhaustive, parentFqn } from '@likec4/core'
|
|
3
|
-
import type { Predicate } from 'rambdax'
|
|
4
3
|
import { isNullish as isNil } from 'remeda'
|
|
5
4
|
import type { ComputeCtx } from './compute'
|
|
6
5
|
|
|
6
|
+
type Predicate<T> = (x: T) => boolean
|
|
7
|
+
|
|
7
8
|
export function includeElementRef(this: ComputeCtx, expr: Expr.ElementRefExpr) {
|
|
8
9
|
// Get the elements that are already in the Ctx before any mutations
|
|
9
10
|
// Because we need to add edges between them and the new elements
|
|
@@ -402,3 +403,7 @@ export function includeCustomElement(this: ComputeCtx, expr: Expr.CustomElementE
|
|
|
402
403
|
this.addEdges(this.graph.anyEdgesBetween(el, currentElements))
|
|
403
404
|
}
|
|
404
405
|
}
|
|
406
|
+
|
|
407
|
+
export function includeCustomRelation(this: ComputeCtx, expr: Expr.CustomRelationExpr) {
|
|
408
|
+
includeRelationExpr.call(this, expr.customRelation.relation)
|
|
409
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { DynamicViewRule, DynamicViewStep, Fqn, ViewID,
|
|
1
|
+
import type { DynamicViewRule, DynamicViewStep, Fqn, ViewID, ViewRulePredicate } from '@likec4/core'
|
|
2
2
|
import { partition } from 'remeda'
|
|
3
3
|
import { type FakeElementIds, fakeModel } from '../../compute-view/__test__/fixture'
|
|
4
4
|
import { computeDynamicView } from '../index'
|
|
@@ -36,7 +36,7 @@ export function $step(expr: StepExpr, title?: string): DynamicViewStep {
|
|
|
36
36
|
throw new Error(`Invalid step expression: ${expr}`)
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
export function compute(stepsAndRules: (DynamicViewStep |
|
|
39
|
+
export function compute(stepsAndRules: (DynamicViewStep | ViewRulePredicate)[]) {
|
|
40
40
|
const [steps, rules] = partition(stepsAndRules, (s): s is DynamicViewStep => 'source' in s)
|
|
41
41
|
const result = computeDynamicView(
|
|
42
42
|
{
|
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
} from '@likec4/core'
|
|
18
18
|
import { isTruthy, unique } from 'remeda'
|
|
19
19
|
import type { LikeC4ModelGraph } from '../LikeC4ModelGraph'
|
|
20
|
-
import {
|
|
20
|
+
import { applyCustomElementProperties } from '../utils/applyCustomElementProperties'
|
|
21
21
|
import { applyViewRuleStyles } from '../utils/applyViewRuleStyles'
|
|
22
22
|
import { buildComputeNodes } from '../utils/buildComputeNodes'
|
|
23
23
|
|
|
@@ -138,7 +138,7 @@ export class DynamicViewComputeCtx {
|
|
|
138
138
|
return edge
|
|
139
139
|
})
|
|
140
140
|
|
|
141
|
-
const nodes =
|
|
141
|
+
const nodes = applyCustomElementProperties(
|
|
142
142
|
rules,
|
|
143
143
|
applyViewRuleStyles(
|
|
144
144
|
rules,
|
package/src/model-graph/utils/{applyElementCustomProperties.ts → applyCustomElementProperties.ts}
RENAMED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { ComputedNode, ViewRule } from '@likec4/core'
|
|
2
2
|
import { Expr, nonNullable } from '@likec4/core'
|
|
3
|
-
import { isEmpty,
|
|
3
|
+
import { isEmpty, isNonNullish, pickBy } from 'remeda'
|
|
4
4
|
|
|
5
|
-
export function
|
|
5
|
+
export function applyCustomElementProperties(_rules: ViewRule[], _nodes: ComputedNode[]) {
|
|
6
6
|
const rules = _rules.flatMap(r => ('include' in r ? r.include.filter(Expr.isCustomElement) : []))
|
|
7
7
|
if (rules.length === 0) {
|
|
8
8
|
return _nodes
|
|
@@ -18,10 +18,11 @@ export function applyElementCustomProperties(_rules: ViewRule[], _nodes: Compute
|
|
|
18
18
|
continue
|
|
19
19
|
}
|
|
20
20
|
let node = nonNullable(nodes[nodeIdx])
|
|
21
|
-
const { border, opacity, ...rest } =
|
|
21
|
+
const { border, opacity, ...rest } = pickBy(props, isNonNullish)
|
|
22
22
|
if (!isEmpty(rest)) {
|
|
23
23
|
node = {
|
|
24
24
|
...node,
|
|
25
|
+
isCustomized: true,
|
|
25
26
|
...rest
|
|
26
27
|
}
|
|
27
28
|
}
|
|
@@ -36,6 +37,7 @@ export function applyElementCustomProperties(_rules: ViewRule[], _nodes: Compute
|
|
|
36
37
|
if (styleOverride) {
|
|
37
38
|
node = {
|
|
38
39
|
...node,
|
|
40
|
+
isCustomized: true,
|
|
39
41
|
style: {
|
|
40
42
|
...node.style,
|
|
41
43
|
...styleOverride
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { ComputedEdge, ComputedNode, ViewRule } from '@likec4/core'
|
|
2
|
+
import { Expr } from '@likec4/core'
|
|
3
|
+
import { isEmpty } from 'remeda'
|
|
4
|
+
import { elementExprToPredicate } from './elementExpressionToPredicate'
|
|
5
|
+
|
|
6
|
+
export function applyCustomRelationProperties(
|
|
7
|
+
_rules: ViewRule[],
|
|
8
|
+
nodes: ComputedNode[],
|
|
9
|
+
_edges: Iterable<ComputedEdge>
|
|
10
|
+
): ComputedEdge[] {
|
|
11
|
+
const rules = _rules.flatMap(r => ('include' in r ? r.include.filter(Expr.isCustomRelationExpr) : []))
|
|
12
|
+
const edges = Array.from(_edges)
|
|
13
|
+
if (rules.length === 0) {
|
|
14
|
+
return edges
|
|
15
|
+
}
|
|
16
|
+
for (
|
|
17
|
+
const {
|
|
18
|
+
customRelation: { relation, title, ...props }
|
|
19
|
+
} of rules
|
|
20
|
+
) {
|
|
21
|
+
if (isEmpty(props) && !title) {
|
|
22
|
+
continue
|
|
23
|
+
}
|
|
24
|
+
const isSource = elementExprToPredicate(relation.source)
|
|
25
|
+
const isTarget = elementExprToPredicate(relation.target)
|
|
26
|
+
const satisfies = (edge: ComputedEdge) => {
|
|
27
|
+
const source = nodes.find(n => n.id === edge.source)
|
|
28
|
+
const target = nodes.find(n => n.id === edge.target)
|
|
29
|
+
if (!source || !target) {
|
|
30
|
+
return false
|
|
31
|
+
}
|
|
32
|
+
let result = isSource(source) && isTarget(target)
|
|
33
|
+
if (!result && relation.isBidirectional) {
|
|
34
|
+
result = isSource(target) && isTarget(source)
|
|
35
|
+
}
|
|
36
|
+
return result
|
|
37
|
+
}
|
|
38
|
+
edges.forEach((edge, i) => {
|
|
39
|
+
if (satisfies(edge)) {
|
|
40
|
+
edges[i] = {
|
|
41
|
+
...edge,
|
|
42
|
+
label: title ?? edge.label,
|
|
43
|
+
isCustomized: true,
|
|
44
|
+
...props
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
})
|
|
48
|
+
}
|
|
49
|
+
return edges
|
|
50
|
+
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type { ComputedNode, ViewRule } from '@likec4/core'
|
|
2
|
-
import { Expr, isViewRuleStyle
|
|
3
|
-
import { anyPass, filter,
|
|
4
|
-
import {
|
|
2
|
+
import { Expr, isViewRuleStyle } from '@likec4/core'
|
|
3
|
+
import { anyPass, filter, isDefined } from 'remeda'
|
|
4
|
+
import { elementExprToPredicate } from './elementExpressionToPredicate'
|
|
5
|
+
|
|
6
|
+
type Predicate<T> = (x: T) => boolean
|
|
5
7
|
|
|
6
8
|
export function applyViewRuleStyles(_rules: ViewRule[], nodes: ComputedNode[]) {
|
|
7
9
|
const rules = _rules.filter(isViewRuleStyle)
|
|
@@ -15,44 +17,19 @@ export function applyViewRuleStyles(_rules: ViewRule[], nodes: ComputedNode[]) {
|
|
|
15
17
|
predicates.push(() => true)
|
|
16
18
|
break
|
|
17
19
|
}
|
|
18
|
-
|
|
19
|
-
predicates.push(
|
|
20
|
-
target.isEqual ? n => n.kind === target.elementKind : n => n.kind !== target.elementKind
|
|
21
|
-
)
|
|
22
|
-
continue
|
|
23
|
-
}
|
|
24
|
-
if (Expr.isElementTagExpr(target)) {
|
|
25
|
-
predicates.push(
|
|
26
|
-
target.isEqual
|
|
27
|
-
? ({ tags }) => !!tags && tags.includes(target.elementTag)
|
|
28
|
-
: ({ tags }) => isNullish(tags) || !tags.includes(target.elementTag)
|
|
29
|
-
)
|
|
30
|
-
continue
|
|
31
|
-
}
|
|
32
|
-
if (Expr.isExpandedElementExpr(target)) {
|
|
33
|
-
predicates.push(n => n.id === target.expanded || parentFqn(n.id) === target.expanded)
|
|
34
|
-
continue
|
|
35
|
-
}
|
|
36
|
-
if (Expr.isElementRef(target)) {
|
|
37
|
-
const { element, isDescedants } = target
|
|
38
|
-
predicates.push(
|
|
39
|
-
isDescedants ? n => n.id.startsWith(element + '.') : n => (n.id as string) === element
|
|
40
|
-
)
|
|
41
|
-
continue
|
|
42
|
-
}
|
|
43
|
-
nonexhaustive(target)
|
|
20
|
+
predicates.push(elementExprToPredicate(target))
|
|
44
21
|
}
|
|
45
|
-
filter(anyPass(predicates)
|
|
22
|
+
filter(nodes, anyPass(predicates)).forEach(n => {
|
|
46
23
|
n.shape = rule.style.shape ?? n.shape
|
|
47
24
|
n.color = rule.style.color ?? n.color
|
|
48
|
-
if (isDefined
|
|
25
|
+
if (isDefined(rule.style.icon)) {
|
|
49
26
|
n.icon = rule.style.icon
|
|
50
27
|
}
|
|
51
28
|
let styleOverride: ComputedNode['style'] | undefined
|
|
52
|
-
if (isDefined
|
|
53
|
-
styleOverride = {
|
|
29
|
+
if (isDefined(rule.style.border)) {
|
|
30
|
+
styleOverride = { border: rule.style.border }
|
|
54
31
|
}
|
|
55
|
-
if (isDefined
|
|
32
|
+
if (isDefined(rule.style.opacity)) {
|
|
56
33
|
styleOverride = { ...styleOverride, opacity: rule.style.opacity }
|
|
57
34
|
}
|
|
58
35
|
if (styleOverride) {
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { ComputedNode } from '@likec4/core'
|
|
2
|
+
import { Expr, nonexhaustive, parentFqn } from '@likec4/core'
|
|
3
|
+
import { isNullish } from 'remeda'
|
|
4
|
+
|
|
5
|
+
type Predicate<T> = (x: T) => boolean
|
|
6
|
+
|
|
7
|
+
export function elementExprToPredicate(target: Expr.ElementPredicateExpression): Predicate<ComputedNode> {
|
|
8
|
+
if (Expr.isWildcard(target)) {
|
|
9
|
+
return () => true
|
|
10
|
+
}
|
|
11
|
+
if (Expr.isElementKindExpr(target)) {
|
|
12
|
+
return target.isEqual ? n => n.kind === target.elementKind : n => n.kind !== target.elementKind
|
|
13
|
+
}
|
|
14
|
+
if (Expr.isElementTagExpr(target)) {
|
|
15
|
+
return target.isEqual
|
|
16
|
+
? ({ tags }) => !!tags && tags.includes(target.elementTag)
|
|
17
|
+
: ({ tags }) => isNullish(tags) || !tags.includes(target.elementTag)
|
|
18
|
+
}
|
|
19
|
+
if (Expr.isExpandedElementExpr(target)) {
|
|
20
|
+
return n => n.id === target.expanded || parentFqn(n.id) === target.expanded
|
|
21
|
+
}
|
|
22
|
+
if (Expr.isElementRef(target)) {
|
|
23
|
+
const { element, isDescedants } = target
|
|
24
|
+
return isDescedants
|
|
25
|
+
? n => n.id.startsWith(element + '.')
|
|
26
|
+
: n => (n.id as string) === element
|
|
27
|
+
}
|
|
28
|
+
if (Expr.isCustomElement(target)) {
|
|
29
|
+
return n => (n.id as string) === target.custom.element
|
|
30
|
+
}
|
|
31
|
+
nonexhaustive(target)
|
|
32
|
+
}
|