style(arch): linting

This commit is contained in:
NicolasNewman 2024-05-06 10:04:17 -05:00
parent 734bde3877
commit 48e6901936
12 changed files with 142 additions and 97 deletions

View File

@ -25,7 +25,7 @@ const MERMAID_CONFIG_DIAGRAM_KEYS = [
'sankey', 'sankey',
'block', 'block',
'packet', 'packet',
'architecture' 'architecture',
] as const; ] as const;
/** /**

View File

@ -40,17 +40,25 @@ const state = new ImperativeState<ArchitectureState>(() => ({
registeredIds: {}, registeredIds: {},
config: DEFAULT_ARCHITECTURE_CONFIG, config: DEFAULT_ARCHITECTURE_CONFIG,
dataStructures: undefined, dataStructures: undefined,
elements: {} elements: {},
})) }));
const clear = (): void => { const clear = (): void => {
state.reset() state.reset();
commonClear(); commonClear();
}; };
const addService = function ({ id, icon, in: parent, title, iconText }: Omit<ArchitectureService, "edges">) { const addService = function ({
id,
icon,
in: parent,
title,
iconText,
}: Omit<ArchitectureService, 'edges'>) {
if (state.records.registeredIds[id] !== undefined) { if (state.records.registeredIds[id] !== undefined) {
throw new Error(`The service id [${id}] is already in use by another ${state.records.registeredIds[id]}`); throw new Error(
`The service id [${id}] is already in use by another ${state.records.registeredIds[id]}`
);
} }
if (parent !== undefined) { if (parent !== undefined) {
if (id === parent) { if (id === parent) {
@ -82,7 +90,9 @@ const getServices = (): ArchitectureService[] => Object.values(state.records.ser
const addGroup = function ({ id, icon, in: parent, title }: ArchitectureGroup) { const addGroup = function ({ id, icon, in: parent, title }: ArchitectureGroup) {
if (state.records.registeredIds[id] !== undefined) { if (state.records.registeredIds[id] !== undefined) {
throw new Error(`The group id [${id}] is already in use by another ${state.records.registeredIds[id]}`); throw new Error(
`The group id [${id}] is already in use by another ${state.records.registeredIds[id]}`
);
} }
if (parent !== undefined) { if (parent !== undefined) {
if (id === parent) { if (id === parent) {
@ -111,10 +121,15 @@ const getGroups = (): ArchitectureGroup[] => {
return Object.values(state.records.groups); return Object.values(state.records.groups);
}; };
const addEdge = function ( const addEdge = function ({
{ lhsId, rhsId, lhsDir, rhsDir, lhsInto, rhsInto, title }: ArchitectureEdge lhsId,
) { rhsId,
lhsDir,
rhsDir,
lhsInto,
rhsInto,
title,
}: ArchitectureEdge) {
if (!isArchitectureDirection(lhsDir)) { if (!isArchitectureDirection(lhsDir)) {
throw new Error( throw new Error(
`Invalid direction given for left hand side of edge ${lhsId}--${rhsId}. Expected (L,R,T,B) got ${lhsDir}` `Invalid direction given for left hand side of edge ${lhsId}--${rhsId}. Expected (L,R,T,B) got ${lhsDir}`
@ -152,7 +167,6 @@ const addEdge = function (
state.records.services[lhsId].edges.push(state.records.edges[state.records.edges.length - 1]); state.records.services[lhsId].edges.push(state.records.edges[state.records.edges.length - 1]);
state.records.services[rhsId].edges.push(state.records.edges[state.records.edges.length - 1]); state.records.services[rhsId].edges.push(state.records.edges[state.records.edges.length - 1]);
} else if (state.records.groups[lhsId] && state.records.groups[rhsId]) { } else if (state.records.groups[lhsId] && state.records.groups[rhsId]) {
} }
}; };
@ -168,28 +182,27 @@ const getDataStructures = () => {
// Create an adjacency list of the diagram to perform BFS on // Create an adjacency list of the diagram to perform BFS on
// Outer reduce applied on all services // Outer reduce applied on all services
// Inner reduce applied on the edges for a service // Inner reduce applied on the edges for a service
const adjList = Object.entries(state.records.services).reduce<{ [id: string]: ArchitectureDirectionPairMap }>( const adjList = Object.entries(state.records.services).reduce<{
(prevOuter, [id, service]) => { [id: string]: ArchitectureDirectionPairMap;
prevOuter[id] = service.edges.reduce<ArchitectureDirectionPairMap>((prevInner, edge) => { }>((prevOuter, [id, service]) => {
if (edge.lhsId === id) { prevOuter[id] = service.edges.reduce<ArchitectureDirectionPairMap>((prevInner, edge) => {
// source is LHS if (edge.lhsId === id) {
const pair = getArchitectureDirectionPair(edge.lhsDir, edge.rhsDir); // source is LHS
if (pair) { const pair = getArchitectureDirectionPair(edge.lhsDir, edge.rhsDir);
prevInner[pair] = edge.rhsId; if (pair) {
} prevInner[pair] = edge.rhsId;
} else {
// source is RHS
const pair = getArchitectureDirectionPair(edge.rhsDir, edge.lhsDir);
if (pair) {
prevInner[pair] = edge.lhsId;
}
} }
return prevInner; } else {
}, {}); // source is RHS
return prevOuter; const pair = getArchitectureDirectionPair(edge.rhsDir, edge.lhsDir);
}, if (pair) {
{} prevInner[pair] = edge.lhsId;
); }
}
return prevInner;
}, {});
return prevOuter;
}, {});
// Configuration for the initial pass of BFS // Configuration for the initial pass of BFS
const firstId = Object.keys(adjList)[0]; const firstId = Object.keys(adjList)[0];

View File

@ -12,7 +12,7 @@ import type {
ArchitectureDataStructures, ArchitectureDataStructures,
ArchitectureSpatialMap, ArchitectureSpatialMap,
EdgeSingularData, EdgeSingularData,
EdgeSingular EdgeSingular,
} from './architectureTypes.js'; } from './architectureTypes.js';
import { import {
type ArchitectureDB, type ArchitectureDB,
@ -25,7 +25,7 @@ import {
isArchitectureDirectionXY, isArchitectureDirectionXY,
isArchitectureDirectionY, isArchitectureDirectionY,
nodeData, nodeData,
edgeData edgeData,
} from './architectureTypes.js'; } from './architectureTypes.js';
import { select } from 'd3'; import { select } from 'd3';
import { setupGraphViewbox } from '../../setupGraphViewbox.js'; import { setupGraphViewbox } from '../../setupGraphViewbox.js';
@ -54,8 +54,10 @@ function addServices(services: ArchitectureService[], cy: cytoscape.Core) {
function positionServices(db: ArchitectureDB, cy: cytoscape.Core) { function positionServices(db: ArchitectureDB, cy: cytoscape.Core) {
cy.nodes().map((node) => { cy.nodes().map((node) => {
const data = nodeData(node) const data = nodeData(node);
if (data.type === 'group') { return; } if (data.type === 'group') {
return;
}
data.x = node.position().x; data.x = node.position().x;
data.y = node.position().y; data.y = node.position().y;
@ -126,8 +128,12 @@ function getAlignments(spatialMaps: ArchitectureSpatialMap[]): fcose.FcoseAlignm
const verticalAlignments: Record<number, string[]> = {}; const verticalAlignments: Record<number, string[]> = {};
// Group service ids in an object with their x and y coordinate as the key // Group service ids in an object with their x and y coordinate as the key
Object.entries(spatialMap).forEach(([id, [x, y]]) => { Object.entries(spatialMap).forEach(([id, [x, y]]) => {
if (!horizontalAlignments[y]) { horizontalAlignments[y] = []; } if (!horizontalAlignments[y]) {
if (!verticalAlignments[x]) { verticalAlignments[x] = []; } horizontalAlignments[y] = [];
}
if (!verticalAlignments[x]) {
verticalAlignments[x] = [];
}
horizontalAlignments[y].push(id); horizontalAlignments[y].push(id);
verticalAlignments[x].push(id); verticalAlignments[x].push(id);
}); });
@ -221,7 +227,7 @@ function layoutArchitecture(
selector: 'edge', selector: 'edge',
style: { style: {
'curve-style': 'straight', 'curve-style': 'straight',
'label': 'data(label)', label: 'data(label)',
'source-endpoint': 'data(sourceEndpoint)', 'source-endpoint': 'data(sourceEndpoint)',
'target-endpoint': 'data(targetEndpoint)', 'target-endpoint': 'data(targetEndpoint)',
}, },
@ -300,16 +306,16 @@ function layoutArchitecture(
// Hacky fix for: https://github.com/iVis-at-Bilkent/cytoscape.js-fcose/issues/67 // Hacky fix for: https://github.com/iVis-at-Bilkent/cytoscape.js-fcose/issues/67
idealEdgeLength(edge: EdgeSingular) { idealEdgeLength(edge: EdgeSingular) {
const [nodeA, nodeB] = edge.connectedNodes(); const [nodeA, nodeB] = edge.connectedNodes();
const { parent: parentA } = nodeData(nodeA) const { parent: parentA } = nodeData(nodeA);
const { parent: parentB } = nodeData(nodeB) const { parent: parentB } = nodeData(nodeB);
const elasticity = const elasticity =
parentA === parentB ? 1.5 * getConfigField('iconSize') : 0.5 * getConfigField('iconSize'); parentA === parentB ? 1.5 * getConfigField('iconSize') : 0.5 * getConfigField('iconSize');
return elasticity; return elasticity;
}, },
edgeElasticity(edge: EdgeSingular) { edgeElasticity(edge: EdgeSingular) {
const [nodeA, nodeB] = edge.connectedNodes(); const [nodeA, nodeB] = edge.connectedNodes();
const { parent: parentA } = nodeData(nodeA) const { parent: parentA } = nodeData(nodeA);
const { parent: parentB } = nodeData(nodeB) const { parent: parentB } = nodeData(nodeB);
const elasticity = parentA === parentB ? 0.45 : 0.001; const elasticity = parentA === parentB ? 0.45 : 0.001;
return elasticity; return elasticity;
}, },
@ -374,7 +380,7 @@ function layoutArchitecture(
if (sX !== tX && sY !== tY) { if (sX !== tX && sY !== tY) {
const sEP = edge.sourceEndpoint(); const sEP = edge.sourceEndpoint();
const tEP = edge.targetEndpoint(); const tEP = edge.targetEndpoint();
const { sourceDir } = edgeData(edge) const { sourceDir } = edgeData(edge);
const [pointX, pointY] = isArchitectureDirectionY(sourceDir) const [pointX, pointY] = isArchitectureDirectionY(sourceDir)
? [sEP.x, tEP.y] ? [sEP.x, tEP.y]
: [tEP.x, sEP.y]; : [tEP.x, sEP.y];

View File

@ -21,8 +21,7 @@ export type ArchitectureDirectionPair = Exclude<
>; >;
export type ArchitectureDirectionPairXY = Exclude< export type ArchitectureDirectionPairXY = Exclude<
InvalidArchitectureDirectionPair, InvalidArchitectureDirectionPair,
'LL' | 'RR' | 'TT' | 'BB' 'LL' | 'RR' | 'TT' | 'BB' | 'LR' | 'RL' | 'TB' | 'BT'
| 'LR' | 'RL' | 'TB' | 'BT'
>; >;
export const ArchitectureDirectionName = { export const ArchitectureDirectionName = {
@ -85,7 +84,7 @@ export const isArchitectureDirectionXY = function (
}; };
export const isArchitecturePairXY = function ( export const isArchitecturePairXY = function (
pair: ArchitectureDirectionPair, pair: ArchitectureDirectionPair
): pair is ArchitectureDirectionPairXY { ): pair is ArchitectureDirectionPairXY {
const lhs = pair[0] as ArchitectureDirection; const lhs = pair[0] as ArchitectureDirection;
const rhs = pair[1] as ArchitectureDirection; const rhs = pair[1] as ArchitectureDirection;
@ -161,13 +160,13 @@ export const getArchitectureDirectionXYFactors = function (
pair: ArchitectureDirectionPairXY pair: ArchitectureDirectionPairXY
): number[] { ): number[] {
if (pair === 'LT' || pair === 'TL') { if (pair === 'LT' || pair === 'TL') {
return [1, 1] return [1, 1];
} else if (pair === 'BL' || pair === 'LB') { } else if (pair === 'BL' || pair === 'LB') {
return [1, -1] return [1, -1];
} else if (pair === 'BR' || pair === 'RB') { } else if (pair === 'BR' || pair === 'RB') {
return [-1, -1] return [-1, -1];
} else { } else {
return [-1, 1] return [-1, 1];
} }
}; };
@ -209,13 +208,11 @@ export interface ArchitectureEdge {
export interface ArchitectureDB extends DiagramDB { export interface ArchitectureDB extends DiagramDB {
clear: () => void; clear: () => void;
addService: (service: Omit<ArchitectureService, "edges">) => void; addService: (service: Omit<ArchitectureService, 'edges'>) => void;
getServices: () => ArchitectureService[]; getServices: () => ArchitectureService[];
addGroup: (group: ArchitectureGroup) => void; addGroup: (group: ArchitectureGroup) => void;
getGroups: () => ArchitectureGroup[]; getGroups: () => ArchitectureGroup[];
addEdge: ( addEdge: (edge: ArchitectureEdge) => void;
edge: ArchitectureEdge
) => void;
getEdges: () => ArchitectureEdge[]; getEdges: () => ArchitectureEdge[];
setElementForId: (id: string, element: D3Element) => void; setElementForId: (id: string, element: D3Element) => void;
getElementById: (id: string) => D3Element; getElementById: (id: string) => D3Element;
@ -257,7 +254,7 @@ export type EdgeSingularData = {
export const edgeData = (edge: cytoscape.EdgeSingular) => { export const edgeData = (edge: cytoscape.EdgeSingular) => {
return edge.data() as EdgeSingularData; return edge.data() as EdgeSingularData;
} };
export interface EdgeSingular extends cytoscape.EdgeSingular { export interface EdgeSingular extends cytoscape.EdgeSingular {
_private: { _private: {
@ -275,28 +272,29 @@ export interface EdgeSingular extends cytoscape.EdgeSingular {
data<T extends keyof EdgeSingularData>(key: T): EdgeSingularData[T]; data<T extends keyof EdgeSingularData>(key: T): EdgeSingularData[T];
} }
export type NodeSingularData = { export type NodeSingularData =
type: 'service';
id: string;
icon?: string;
label?: string;
parent?: string;
width: number;
height: number;
[key: string]: any;
}
| { | {
type: 'group'; type: 'service';
id: string; id: string;
icon?: string; icon?: string;
label?: string; label?: string;
parent?: string; parent?: string;
[key: string]: any; width: number;
}; height: number;
[key: string]: any;
}
| {
type: 'group';
id: string;
icon?: string;
label?: string;
parent?: string;
[key: string]: any;
};
export const nodeData = (node: cytoscape.NodeSingular) => { export const nodeData = (node: cytoscape.NodeSingular) => {
return node.data() as NodeSingularData; return node.data() as NodeSingularData;
} };
export interface NodeSingular extends cytoscape.NodeSingular { export interface NodeSingular extends cytoscape.NodeSingular {
_private: { _private: {

View File

@ -227,7 +227,7 @@ export const drawServices = function (
}, },
getConfig() getConfig()
); );
textElem textElem
.attr('dy', '1em') .attr('dy', '1em')
.attr('alignment-baseline', 'middle') .attr('alignment-baseline', 'middle')
@ -247,14 +247,24 @@ export const drawServices = function (
} else if (service.iconText) { } else if (service.iconText) {
bkgElem = getIcon('blank')?.(bkgElem, iconSize); bkgElem = getIcon('blank')?.(bkgElem, iconSize);
const textElemContainer = bkgElem.append('g'); const textElemContainer = bkgElem.append('g');
const fo = textElemContainer.append('foreignObject').attr('width', iconSize).attr('height', iconSize); const fo = textElemContainer
.append('foreignObject')
.attr('width', iconSize)
.attr('height', iconSize);
const divElem = fo const divElem = fo
.append('div') .append('div')
.attr('class', 'node-icon-text') .attr('class', 'node-icon-text')
.attr('style', `height: ${iconSize}px;`) .attr('style', `height: ${iconSize}px;`)
.append('div').html(service.iconText); .append('div')
const fontSize = parseInt(window.getComputedStyle(divElem.node(), null).getPropertyValue("font-size").replace(/[^\d]/g, '')) ?? 16; .html(service.iconText);
divElem.attr('style', `-webkit-line-clamp: ${Math.floor((iconSize - 2) / fontSize)};`) const fontSize =
parseInt(
window
.getComputedStyle(divElem.node(), null)
.getPropertyValue('font-size')
.replace(/[^\d]/g, '')
) ?? 16;
divElem.attr('style', `-webkit-line-clamp: ${Math.floor((iconSize - 2) / fontSize)};`);
} else { } else {
bkgElem bkgElem
.append('path') .append('path')

View File

@ -20,7 +20,7 @@ architecture
## Syntax ## Syntax
The building blocks of an architecture are `groups`, `services`, and `edges`. The building blocks of an architecture are `groups`, `services`, and `edges`.
For supporting components, icons are declared by surrounding the icon name with `()`, while labels are declared by surrounding the text with `[]`. For supporting components, icons are declared by surrounding the icon name with `()`, while labels are declared by surrounding the text with `[]`.
@ -29,6 +29,7 @@ To begin an architecture diagram, use the keyword `architecture`, followed by yo
### Groups ### Groups
The syntax for declaring a group is: The syntax for declaring a group is:
``` ```
group {group id}({icon name})[{title}] (in {parent id})? group {group id}({icon name})[{title}] (in {parent id})?
``` ```
@ -48,12 +49,15 @@ group private_api(cloud)[Private API] in public_api
``` ```
### Services ### Services
The syntax for declaring a group is: The syntax for declaring a group is:
``` ```
service {service id}({icon name})[{title}] (in {parent id})? service {service id}({icon name})[{title}] (in {parent id})?
``` ```
Put together: Put together:
``` ```
service database(db)[Database] service database(db)[Database]
``` ```
@ -67,6 +71,7 @@ service database(db)[Database] in private_api
``` ```
### Edges ### Edges
TODO TODO
## Configuration ## Configuration

View File

@ -16,7 +16,7 @@ import type { DetailedError } from './utils.js';
import type { ExternalDiagramDefinition } from './diagram-api/types.js'; import type { ExternalDiagramDefinition } from './diagram-api/types.js';
import type { UnknownDiagramError } from './errors.js'; import type { UnknownDiagramError } from './errors.js';
import type { IconLibrary, IconResolver } from './rendering-util/svgRegister.js'; import type { IconLibrary, IconResolver } from './rendering-util/svgRegister.js';
import { createIcon } from './rendering-util/svgRegister.js'; import { createIcon } from './rendering-util/svgRegister.js';
import { addDiagrams } from './diagram-api/diagram-orchestration.js'; import { addDiagrams } from './diagram-api/diagram-orchestration.js';
export type { export type {
@ -29,10 +29,10 @@ export type {
ParseResult, ParseResult,
UnknownDiagramError, UnknownDiagramError,
IconLibrary, IconLibrary,
IconResolver IconResolver,
}; };
export { createIcon } export { createIcon };
export interface RunOptions { export interface RunOptions {
/** /**

View File

@ -5,9 +5,9 @@
import { createIcon } from '../svgRegister.js'; import { createIcon } from '../svgRegister.js';
export default createIcon( export default createIcon(
`<g> `<g>
<rect width="80" height="80" style="fill: #087ebf; stroke-width: 0px;"/> <rect width="80" height="80" style="fill: #087ebf; stroke-width: 0px;"/>
<path d="m65,47.5c0,2.76-2.24,5-5,5H20c-2.76,0-5-2.24-5-5,0-1.87,1.03-3.51,2.56-4.36-.04-.21-.06-.42-.06-.64,0-2.6,2.48-4.74,5.65-4.97,1.65-4.51,6.34-7.76,11.85-7.76.86,0,1.69.08,2.5.23,2.09-1.57,4.69-2.5,7.5-2.5,6.1,0,11.19,4.38,12.28,10.17,2.14.56,3.72,2.51,3.72,4.83,0,.03,0,.07-.01.1,2.29.46,4.01,2.48,4.01,4.9Z" style="fill: none; stroke: #fff; stroke-miterlimit: 10; stroke-width: 2px;"/> <path d="m65,47.5c0,2.76-2.24,5-5,5H20c-2.76,0-5-2.24-5-5,0-1.87,1.03-3.51,2.56-4.36-.04-.21-.06-.42-.06-.64,0-2.6,2.48-4.74,5.65-4.97,1.65-4.51,6.34-7.76,11.85-7.76.86,0,1.69.08,2.5.23,2.09-1.57,4.69-2.5,7.5-2.5,6.1,0,11.19,4.38,12.28,10.17,2.14.56,3.72,2.51,3.72,4.83,0,.03,0,.07-.01.1,2.29.46,4.01,2.48,4.01,4.9Z" style="fill: none; stroke: #fff; stroke-miterlimit: 10; stroke-width: 2px;"/>
</g>`, </g>`,
80 80
); );

View File

@ -43,4 +43,12 @@ const getIcon = (name: string): IconResolver | null => {
return icons['unknown']; return icons['unknown'];
}; };
export { registerIcon, registerIcons, getIcon, isIconNameInUse, createIcon, IconLibrary, IconResolver }; export {
registerIcon,
registerIcons,
getIcon,
isIconNameInUse,
createIcon,
IconLibrary,
IconResolver,
};

View File

@ -35,7 +35,10 @@ export type ArchitectureServices = LangiumCoreServices & ArchitectureAddedServic
* Dependency injection module that overrides Langium default services and * Dependency injection module that overrides Langium default services and
* contributes the declared `Architecture` services. * contributes the declared `Architecture` services.
*/ */
export const ArchitectureModule: Module<ArchitectureServices, PartialLangiumCoreServices & ArchitectureAddedServices> = { export const ArchitectureModule: Module<
ArchitectureServices,
PartialLangiumCoreServices & ArchitectureAddedServices
> = {
parser: { parser: {
TokenBuilder: () => new ArchitectureTokenBuilder(), TokenBuilder: () => new ArchitectureTokenBuilder(),
ValueConverter: () => new ArchitectureValueConverter(), ValueConverter: () => new ArchitectureValueConverter(),
@ -56,7 +59,9 @@ export const ArchitectureModule: Module<ArchitectureServices, PartialLangiumCore
* @param context - Optional module context with the LSP connection * @param context - Optional module context with the LSP connection
* @returns An object wrapping the shared services and the language-specific services * @returns An object wrapping the shared services and the language-specific services
*/ */
export function createArchitectureServices(context: DefaultSharedCoreModuleContext = EmptyFileSystem): { export function createArchitectureServices(
context: DefaultSharedCoreModuleContext = EmptyFileSystem
): {
shared: LangiumSharedCoreServices; shared: LangiumSharedCoreServices;
Architecture: ArchitectureServices; Architecture: ArchitectureServices;
} { } {

View File

@ -10,12 +10,12 @@ export class ArchitectureValueConverter extends AbstractMermaidValueConverter {
_cstNode: CstNode _cstNode: CstNode
): ValueType | undefined { ): ValueType | undefined {
if (rule.name === 'ARCH_ICON') { if (rule.name === 'ARCH_ICON') {
return input.replace(/[()]/g, '').trim(); return input.replace(/[()]/g, '').trim();
} else if (rule.name === 'ARCH_TEXT_ICON') { } else if (rule.name === 'ARCH_TEXT_ICON') {
return input.replace(/[()"]/g, ''); return input.replace(/[()"]/g, '');
} else if (rule.name === 'ARCH_TITLE') { } else if (rule.name === 'ARCH_TITLE') {
return input.replace(/[[\]]/g, '').trim(); return input.replace(/[[\]]/g, '').trim();
} }
return undefined return undefined;
} }
} }

View File

@ -12,7 +12,7 @@ export {
isPacketBlock, isPacketBlock,
isPie, isPie,
isPieSection, isPieSection,
isArchitecture isArchitecture,
} from './generated/ast.js'; } from './generated/ast.js';
export { export {
@ -20,7 +20,7 @@ export {
MermaidGeneratedSharedModule, MermaidGeneratedSharedModule,
PacketGeneratedModule, PacketGeneratedModule,
PieGeneratedModule, PieGeneratedModule,
ArchitectureGeneratedModule ArchitectureGeneratedModule,
} from './generated/module.js'; } from './generated/module.js';
export * from './common/index.js'; export * from './common/index.js';