Skip to content

Commit

Permalink
feat: highlighting nodes in flow diagram (#547)
Browse files Browse the repository at this point in the history
Co-authored-by: Matatjahu <[email protected]>
  • Loading branch information
Ankitchaudharyy and magicmatatjahu authored Jan 19, 2023
1 parent 2571935 commit fb25813
Show file tree
Hide file tree
Showing 9 changed files with 30,455 additions and 98 deletions.
30,442 changes: 30,361 additions & 81 deletions package-lock.json

Large diffs are not rendered by default.

16 changes: 12 additions & 4 deletions src/components/Visualiser/Nodes/ApplicationNode.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { useState, useEffect } from 'react';
import { Handle, Position } from 'reactflow';
import { OldAsyncAPIDocument as AsyncAPIDocument } from '@asyncapi/parser/cjs';

import { useServices } from '../../../services';
import { Markdown } from '../../common';

import type { FunctionComponent } from 'react';
Expand Down Expand Up @@ -49,24 +51,30 @@ const buildNodeData = (spec: AsyncAPIDocument) => {
export const ApplicationNode: FunctionComponent<ApplicationNodeProps> = ({
data: { spec } = {},
}) => {
const { navigationSvc } = useServices();
const [highlight, setHighlight] = useState(false);
const { description, title, version, license, externalDocs, servers, defaultContentType } = buildNodeData(spec as AsyncAPIDocument);

useEffect(() => {
return navigationSvc.highlightVisualiserNode('#server', setHighlight);
}, [navigationSvc, setHighlight]);

return (
<div className="bg-white shadow sm:rounded-lg border-2 border-gray-300 flex">
<div className={`flex transition duration-500 ease-out shadow sm:rounded-lg border-2 ${highlight ? 'bg-gray-300 border-gray-600' : 'bg-white border-gray-300'}`}>
<Handle
type="target"
position={Position.Left}
style={{ background: 'gray' }}
/>
<div className="flex justify-center items-center border-r border-gray-200">
<span className="block transform -rotate-90 uppercase text-green-500 w-full font-bold tracking-widest px-2 ">
<span className="block transform -rotate-90 uppercase text-blue-500 w-full font-bold tracking-widest px-2 ">
In
</span>
</div>
<div>
<div className="px-4 py-5 sm:px-6">
<div className="flex justify-between mb-4">
<span className="block leading-6 text-gray-900 uppercase text-xs font-light">
<span className="block leading-6 text-gray-900 uppercase text-xs font-light">
application
</span>
</div>
Expand Down Expand Up @@ -146,7 +154,7 @@ export const ApplicationNode: FunctionComponent<ApplicationNodeProps> = ({
</div>
</div>
<div className="flex justify-center items-center border-l border-gray-2">
<span className="block transform -rotate-90 uppercase text-yellow-500 w-full font-bold tracking-widest">
<span className="block transform -rotate-90 uppercase text-green-500 w-full font-bold tracking-widest">
Out
</span>
</div>
Expand Down
15 changes: 12 additions & 3 deletions src/components/Visualiser/Nodes/PublishNode.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@

import React from 'react';
import { useState, useEffect } from 'react';
import { Handle, Position } from 'reactflow';

import { useServices } from '../../../services';
import getBackgroundColor from '../utils/random-background-color';

// @ts-ignore
import { Markdown } from '@asyncapi/react-component/lib/esm/components/Markdown';

import type React from 'react';

interface IData {
messages: any[];
channel: string
Expand All @@ -20,8 +22,15 @@ interface PublishNodeProps {
export const PublishNode: React.FunctionComponent<PublishNodeProps> = ({
data: { messages = [], channel, description },
}) => {
const { navigationSvc } = useServices();
const [highlight, setHighlight] = useState(false);

useEffect(() => {
return navigationSvc.highlightVisualiserNode(`#operation-publish-${channel}`, setHighlight);
}, [navigationSvc, setHighlight]);

return (
<div className="bg-white shadow sm:rounded-lg border-2 border-green-400">
<div className={`flex transition duration-500 ease-out shadow sm:rounded-lg border-2 ${highlight ? 'bg-blue-100 border-blue-700' : 'bg-white border-blue-400'}`}>
<div className="px-4 py-5 sm:px-6 space-y-4">
<span className="block leading-6 text-gray-900 text-xs font-light uppercase">You can publish</span>
<div>
Expand Down
12 changes: 11 additions & 1 deletion src/components/Visualiser/Nodes/SubscribeNode.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { useState, useEffect } from 'react';
import { Handle, Position } from 'reactflow';

import { useServices } from '../../../services';
import getBackgroundColor from '../utils/random-background-color';

// @ts-ignore
Expand All @@ -17,8 +20,15 @@ interface PublishNodeProps {
}

export const SubscribeNode: FunctionComponent<PublishNodeProps> = ({ data: { channel, description, messages } }) => {
const { navigationSvc } = useServices();
const [highlight, setHighlight] = useState(false);

useEffect(() => {
return navigationSvc.highlightVisualiserNode(`#operation-subscribe-${channel}`, setHighlight);
}, [navigationSvc, setHighlight]);

return (
<div className="bg-white shadow sm:rounded-lg border-2 border-yellow-400">
<div className={`flex transition duration-500 ease-out shadow sm:rounded-lg border-2 ${highlight ? 'bg-green-200 border-green-700' : 'bg-white border-green-400'}`}>
<Handle
type="target"
position={Position.Left}
Expand Down
4 changes: 2 additions & 2 deletions src/components/Visualiser/utils/node-factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ function buildFlowElementsForOperation({ operation, spec, applicationLinkType, d
description: channelModel.description(),
operationId: operationModel.id(),
elementType: operation,
theme: operation === 'subscribe' ? 'yellow' : 'green',
theme: operation === 'subscribe' ? 'green' : 'blue',
...data
},
position: { x: 0, y: 0 },
Expand All @@ -57,7 +57,7 @@ function buildFlowElementsForOperation({ operation, spec, applicationLinkType, d
// type: 'smoothstep',
// animated: true,
// label: messagesModel.map(message => message.uid()).join(','),
style: { stroke: applicationLinkType === 'target' ? '#7ee3be' : 'orange', strokeWidth: 4 },
style: { stroke: applicationLinkType === 'target' ? '#00A5FA' : '#7ee3be', strokeWidth: 4 },
source: applicationLinkType === 'target' ? `${operation}-${channel.channel}` : 'application',
target: applicationLinkType === 'target' ? 'application' : `${operation}-${channel.channel}`,
};
Expand Down
1 change: 1 addition & 0 deletions src/services/abstract.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ export abstract class AbstractService {
) {}

public onInit(): void | Promise<void> {}
public afterAppInit(): void | Promise<void> {}
}
6 changes: 6 additions & 0 deletions src/services/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,9 @@ export async function createServices() {

return services;
}

export async function afterAppInit(services: Services) {
for (const service in services) {
await services[service as keyof Services].afterAppInit();
}
}
46 changes: 40 additions & 6 deletions src/services/navigation.service.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
import { AbstractService } from './abstract.service';

import type React from 'react';

export class NavigationService extends AbstractService {
override afterAppInit() {
try {
this.scrollToHash();
window.dispatchEvent(new HashChangeEvent('hashchange'));
} catch (err: any) {
console.error(err);
}
}

getUrlParameters() {
const urlParams = new URLSearchParams(window.location.search);
return {
Expand Down Expand Up @@ -30,16 +41,13 @@ export class NavigationService extends AbstractService {
}

async scrollToHash(hash?: string) {
hash = hash || window.location.hash.substring(1);
try {
const escapedHash = CSS.escape(hash);
if (!escapedHash || escapedHash === '#') {
const sanitizedHash = this.sanitizeHash(hash);
if (!sanitizedHash) {
return;
}

const items = document.querySelectorAll(
escapedHash.startsWith('#') ? escapedHash : `#${escapedHash}`,
);
const items = document.querySelectorAll(`#${sanitizedHash}`);
if (items.length) {
const element = items[0];
typeof element.scrollIntoView === 'function' &&
Expand All @@ -62,6 +70,32 @@ export class NavigationService extends AbstractService {
}
}

highlightVisualiserNode(nodeId: string, setState: React.Dispatch<React.SetStateAction<boolean>>) {
function hashChanged() {
if (location.hash.startsWith(nodeId)) {
setState(true);
setTimeout(() => {
setState(false);
}, 1000);
}
}

window.addEventListener('hashchange', hashChanged);
return () => {
window.removeEventListener('hashchange', hashChanged);
};
}

private sanitizeHash(hash?: string): string | undefined {
hash = hash || window.location.hash.substring(1);
try {
const escapedHash = CSS.escape(hash);
return escapedHash.startsWith('#') ? hash.substring(1) : escapedHash;
} catch (err: any) {
return;
}
}

private emitHashChangeEvent(hash: string) {
hash = hash.startsWith('#') ? hash : `#${hash}`;
window.history.pushState({}, '', hash);
Expand Down
11 changes: 10 additions & 1 deletion src/studio.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
import React from 'react';
import React, { useEffect } from 'react';
import { Toaster } from 'react-hot-toast';

import { Content, Sidebar, Template, Toolbar } from './components';

import { afterAppInit, useServices } from './services';
import { appState } from './state';

export interface AsyncAPIStudioProps {}

export const AsyncAPIStudio: React.FunctionComponent<AsyncAPIStudioProps> = () => {
const services = useServices();

useEffect(() => {
setTimeout(() => {
afterAppInit(services);
}, 250);
}, []);

if (appState.getState().readOnly) {
return (
<div className="flex flex-row flex-1 overflow-hidden h-full w-full h-screen">
Expand Down

0 comments on commit fb25813

Please sign in to comment.