diff --git a/examples/circlegraph/css/diagram.css b/examples/circlegraph/css/diagram.css index 3918cb22..d5a88d44 100644 --- a/examples/circlegraph/css/diagram.css +++ b/examples/circlegraph/css/diagram.css @@ -38,13 +38,4 @@ .sprotty-node.selected { stroke: #dd8; stroke-width: 6; -} - -.sprotty-missing { - stroke-width: 1; - stroke: #f00; - fill: #f00; - font-family: SansSerif; - font-size: 14pt; - text-anchor: middle; } \ No newline at end of file diff --git a/examples/classdiagram/css/diagram.css b/examples/classdiagram/css/diagram.css index a04f3357..63b9e045 100644 --- a/examples/classdiagram/css/diagram.css +++ b/examples/classdiagram/css/diagram.css @@ -123,14 +123,6 @@ stroke-width: 1; } -.sprotty-missing { - stroke-width: 1; - stroke: #f00; - fill: #f00; - font-size: 14pt; - text-anchor: middle; -} - .sprotty-popup-title { font-weight: bold; margin-bottom: 10px; diff --git a/examples/multicore/css/diagram.css b/examples/multicore/css/diagram.css index 131bf15e..089924f3 100644 --- a/examples/multicore/css/diagram.css +++ b/examples/multicore/css/diagram.css @@ -62,14 +62,6 @@ text-align: start; } -.sprotty-missing { - stroke-width: 1; - stroke: #f00; - fill: #f00; - font-size: 14pt; - text-anchor: middle; -} - .sprotty-popup-title { font-weight: bold; margin-bottom: 10px; diff --git a/packages/sprotty/css/sprotty.css b/packages/sprotty/css/sprotty.css index 1a04b7fe..002795d9 100644 --- a/packages/sprotty/css/sprotty.css +++ b/packages/sprotty/css/sprotty.css @@ -93,3 +93,11 @@ .animation-spin { animation: spin 1.5s linear infinite; } + +.sprotty-missing { + stroke-width: 1; + stroke: #f00; + fill: #f00; + font-size: 14pt; + text-anchor: start; +} \ No newline at end of file diff --git a/packages/sprotty/src/base/views/view.spec.ts b/packages/sprotty/src/base/views/view.spec.ts index 880707c1..03488e5b 100644 --- a/packages/sprotty/src/base/views/view.spec.ts +++ b/packages/sprotty/src/base/views/view.spec.ts @@ -45,7 +45,7 @@ describe('base views', () => { it('missing view', () => { const vnode = missingView.render(emptyRoot, context); - expect(toHTML(vnode)).to.be.equal('?EMPTY?'); + expect(toHTML(vnode)).to.be.equal('missing "NONE" view'); const model = new SNodeImpl(); model.bounds = { x: 42, @@ -56,7 +56,7 @@ describe('base views', () => { model.id = 'foo'; model.type = 'type'; const vnode1 = missingView.render(model, context); - expect(toHTML(vnode1)).to.be.equal('?foo?'); + expect(toHTML(vnode1)).to.be.equal('missing "type" view'); }); }); diff --git a/packages/sprotty/src/base/views/view.tsx b/packages/sprotty/src/base/views/view.tsx index 6643ae2a..ac2ae94b 100644 --- a/packages/sprotty/src/base/views/view.tsx +++ b/packages/sprotty/src/base/views/view.tsx @@ -17,7 +17,7 @@ /** @jsx svg */ import { svg } from '../../lib/jsx'; -import { injectable, multiInject, optional, interfaces } from 'inversify'; +import { injectable, multiInject, optional, interfaces, inject } from 'inversify'; import { VNode } from 'snabbdom'; import { TYPES } from '../types'; import { InstanceRegistry } from '../../utils/registry'; @@ -26,6 +26,7 @@ import { SModelElementImpl, SModelRootImpl, SParentElementImpl } from '../model/ import { EMPTY_ROOT, CustomFeatures } from '../model/smodel-factory'; import { registerModelElement } from '../model/smodel-utils'; import { Point } from 'sprotty-protocol'; +import { ILogger } from '../../utils/logging'; /** * Arguments for `IView` rendering. @@ -94,6 +95,9 @@ export type ViewRegistrationFactory = () => ViewRegistration; */ @injectable() export class ViewRegistry extends InstanceRegistry { + + @inject(TYPES.ILogger) protected logger: ILogger; + constructor(@multiInject(TYPES.ViewRegistration) @optional() registrations: ViewRegistration[]) { super(); this.registerDefaults(); @@ -107,6 +111,7 @@ export class ViewRegistry extends InstanceRegistry { } override missing(key: string): IView { + this.logger.warn(this, `no registered view for type '${key}', please configure a view in the ContainerModule`); return new MissingView(); } } @@ -155,8 +160,20 @@ export class EmptyView implements IView { */ @injectable() export class MissingView implements IView { + private static positionMap = new Map(); + render(model: Readonly, context: RenderingContext): VNode { - const position: Point = (model as any).position || Point.ORIGIN; - return ?{model.id}?; + const position: Point = (model as any).position || this.getPostion(model.type); + return missing "{model.type}" view; + } + + getPostion(type: string) { + let position = MissingView.positionMap.get(type); + if (!position) { + position = Point.ORIGIN; + MissingView.positionMap.forEach(value => position = value.y >= position!.y ? {x: 0, y: value.y + 20} : position); + MissingView.positionMap.set(type, position); + } + return position; } }