theia-ide / sprotty

A next-gen web-based graphics framework
Apache License 2.0
138 stars 23 forks source link

sprotty server side rendering #230

Open dvc94ch opened 6 years ago

dvc94ch commented 6 years ago

For some tasks - like generating reference documentation - it would be useful to have a headless mode that renders straight to svg.

dvc94ch commented 6 years ago

Any thoughts on if this is:

a) possible b) in the scope of sprotty c) what would have to be done

There's always the possibility of using chromium-headless or something like that, but that a gigantic dependency I can't manage with yarn, so I'd rather not...

I think the only thing that strictly requires a browser is getBBox.

spoenemann commented 6 years ago

I think that is already possible using the snabbdom-to-html package. See for example this test code:

https://github.com/theia-ide/sprotty/blob/95cd7dd9876ae6d5d815a2efceae13b451b0c2d6/client/src/graph/views.spec.tsx#L98-L113

You just need to create an instance of ModelRenderer through dependency injection like it's done in that test.

dvc94ch commented 6 years ago

Got side tracked... The layout is not being applied to the svg correctly - 'updated model' is never logged as in the example, but the layout is performed. How do I get rid of Action is postponed due to block condition?

12:41:00 sprotty CommandStack: Executing SetModelCommand {
  action: SetModelAction { newRoot: { type: 'NONE', id: 'EMPTY' }, kind: 'setModel' } }
layout
12:41:01 sprotty Viewer: rendering SModelRoot {
  children: [],
  canvasBounds: { x: 0, y: 0, width: -1, height: -1 },
  type: 'NONE',
  id: 'EMPTY' }
end layout
12:41:01 sprotty ActionDispatcher: Action is postponed due to block condition SetModelAction {
  newRoot: { type: 'graph', id: 'netlist-graph', children: [ [Object] ] },
  kind: 'setModel' }
import 'reflect-metadata';
import * as snabbdom from 'snabbdom-jsx';
import { Container } from 'inversify';
import { TYPES, SGraphFactory, ModelRendererFactory,
         IVNodeDecorator, defaultModule, SModelRoot,
         overrideViewerOptions, ConsoleLogger, LogLevel } from 'sprotty/lib';
import { NetlistGraphGenerator, configureSchematicModelElements,
         ElkGraphLayout, ElkFactory, NetlistGraphModelSource, IGraphGenerator } from './browser';

import * as fs from 'fs'
import * as path from 'path'
import elkFactory from './browser/graph/elk-bundled';

const toHTML = require('snabbdom-to-html');

declare let global: any;

function createContainer(): Container {
    const container = new Container();
    container.load(defaultModule)

    container.rebind(TYPES.IModelFactory).to(SGraphFactory).inSingletonScope();
    container.rebind(TYPES.ILogger).to(ConsoleLogger).inSingletonScope();
    container.rebind(TYPES.LogLevel).toConstantValue(LogLevel.log);

    configureSchematicModelElements(container);

    container.bind(IGraphGenerator).to(NetlistGraphGenerator).inSingletonScope()
    container.bind(TYPES.ModelSource).to(NetlistGraphModelSource).inSingletonScope()
    container.bind(TYPES.IModelLayoutEngine).to(ElkGraphLayout)
    container.bind(ElkFactory).toConstantValue(elkFactory);

    overrideViewerOptions(container, {
        needsClientLayout: false,
        needsServerLayout: true
    });

    return container
}

function getCss(paths: string[]): string {
    let css = '';
    for (let p of paths) {
        css += fs.readFileSync(path.resolve(path.join(__dirname, p))).toString()
    }
    return css;
}

function renderGraph(graph: SModelRoot, style: string) {
    const decorators = container.getAll<IVNodeDecorator>(TYPES.IVNodeDecorator);
    const context = container.get<ModelRendererFactory>(TYPES.ModelRendererFactory)(decorators);

    // For PreRenderedView to work
    global.document = {createElement: snabbdom.svg, getElementById: () => null};

    const vnode = context.renderElement(graph);

    if (vnode.data && vnode.data.attrs && vnode.data.class) {
        vnode.data.attrs['xmlns'] = 'http://www.w3.org/2000/svg'
        vnode.data.class['sprotty'] = true
    }

    vnode.children = [].concat.apply([], [
        [snabbdom.html('style', [], style)],
        vnode.children || []
    ])

    return toHTML(vnode)
}

const input = require('../tinyFPGA.lec.json')
const outputPath = path.resolve(__dirname + '/../tinyFPGA.svg')

const style = getCss([
    '../src/cli.css',
    '../src/browser/style/orientation.css',
    '../src/browser/style/schematic.css',
])

const container = createContainer()

const modelSource = container.get<NetlistGraphModelSource>(TYPES.ModelSource);
modelSource.graphGenerator.addNetlist('netlist', input);
modelSource.updateModel().then(() => {
    console.log('updated model')
    const graphFactory = container.get<SGraphFactory>(TYPES.IModelFactory);
    const graph = graphFactory.createRoot(modelSource.getModel())
    console.log(graph)
    fs.writeFileSync(outputPath, renderGraph(graph, style))
})