Skip to content

Commit 462c86e

Browse files
refactor: simplify state management, part 1 (#515)
1 parent be9d311 commit 462c86e

File tree

14 files changed

+621
-71
lines changed

14 files changed

+621
-71
lines changed

package-lock.json

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@
4545
"react-dom": "^18.2.0",
4646
"react-hot-toast": "2.4.0",
4747
"react-icons": "^4.6.0",
48-
"reactflow": "^11.2.0"
48+
"reactflow": "^11.2.0",
49+
"zustand": "^4.1.4"
4950
},
5051
"scripts": {
5152
"dev": "npm run start",

src/components/Content.tsx

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import React from 'react';
2-
31
import SplitPane from './SplitPane';
42
import { Editor } from './Editor/Editor';
53
import { Navigation } from './Navigation';
@@ -8,17 +6,19 @@ import { NewFileModal, RedirectedModal } from './Modals';
86
import { VisualiserTemplate } from './Visualiser';
97

108
import { debounce } from '../helpers';
11-
import state from '../state';
9+
import { usePanelsState } from '../state/index.state';
10+
11+
import type { FunctionComponent } from 'react';
1212

1313
interface ContentProps {}
1414

15-
export const Content: React.FunctionComponent<ContentProps> = () => { // eslint-disable-line sonarjs/cognitive-complexity
16-
const sidebarState = state.useSidebarState();
15+
export const Content: FunctionComponent<ContentProps> = () => { // eslint-disable-line sonarjs/cognitive-complexity
16+
const { show, secondaryPanelType } = usePanelsState();
1717

18-
const navigationEnabled = sidebarState.panels.navigation.get();
19-
const editorEnabled = sidebarState.panels.editor.get();
20-
const viewEnabled = sidebarState.panels.view.get();
21-
const viewType = sidebarState.panels.viewType.get();
18+
const navigationEnabled = show.primarySidebar;
19+
const editorEnabled = show.primaryPanel;
20+
const viewEnabled = show.secondaryPanel;
21+
const viewType = secondaryPanelType;
2222

2323
const splitPosLeft = 'splitPos:left';
2424
const splitPosRight = 'splitPos:right';
@@ -74,4 +74,4 @@ export const Content: React.FunctionComponent<ContentProps> = () => { // eslint-
7474
</div>
7575
</div>
7676
);
77-
};
77+
};

src/components/Modals/NewFileModal.tsx

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,24 @@
1-
import React, { useState } from 'react';
1+
import { useState } from 'react';
22
import { BsFillCheckCircleFill } from 'react-icons/bs';
33
import toast from 'react-hot-toast';
44

5-
import { ConfirmModal } from './index';
65
import examples from '../../examples';
6+
7+
import { ConfirmModal } from './ConfirmModal';
78
import { useServices } from '../../services';
8-
import state from '../../state';
9+
import { usePanelsState, panelsState } from '../../state/index.state';
10+
11+
import type { ComponentType, MouseEventHandler, FunctionComponent } from 'react';
912

1013
interface TemplateListItemProps {
1114
title: string;
12-
description: React.ComponentType;
15+
description: ComponentType;
1316
isSelected: boolean;
14-
onClick: React.MouseEventHandler<HTMLButtonElement>;
17+
onClick: MouseEventHandler<HTMLButtonElement>;
1518
key: string;
1619
}
1720

18-
const TemplateListItem: React.FunctionComponent<TemplateListItemProps> = ({ title, description: Description, onClick, isSelected }) => {
21+
const TemplateListItem: FunctionComponent<TemplateListItemProps> = ({ title, description: Description, onClick, isSelected }) => {
1922
const containerStyles = isSelected ? 'border-pink-500' : 'border-gray-200';
2023
const textStyles = isSelected ? 'text-pink-600' : 'text-gray-600';
2124

@@ -32,15 +35,14 @@ const TemplateListItem: React.FunctionComponent<TemplateListItemProps> = ({ titl
3235
);
3336
};
3437

35-
export const NewFileModal: React.FunctionComponent = () => {
38+
export const NewFileModal: FunctionComponent = () => {
3639
const { editorSvc } = useServices();
37-
const sidebarState = state.useSidebarState();
38-
const newFileEnabled = sidebarState.panels.newFile.get();
40+
const newFileOpened = usePanelsState(state => state.newFileOpened);
3941
const [selectedTemplate, setSelectedTemplate] = useState({ title: '', template: '' });
4042

4143
const onSubmit = () => {
4244
editorSvc.updateState({ content: selectedTemplate.template, updateModel: true });
43-
sidebarState.panels.newFile.set(false);
45+
panelsState.setState({ newFileOpened: false });
4446
setSelectedTemplate({ title: '', template: '' });
4547

4648
toast.success(
@@ -51,7 +53,7 @@ export const NewFileModal: React.FunctionComponent = () => {
5153
};
5254

5355
const onCancel = () => {
54-
sidebarState.panels.newFile.set(false);
56+
panelsState.setState({ newFileOpened: false });
5557
setSelectedTemplate({ title: '', template: '' });
5658
};
5759

@@ -64,7 +66,7 @@ export const NewFileModal: React.FunctionComponent = () => {
6466
title="AsyncAPI Templates - Start with our template examples"
6567
confirmText="Use Template"
6668
confirmDisabled={!selectedTemplate.template}
67-
show={newFileEnabled}
69+
show={newFileOpened}
6870
onSubmit={onSubmit}
6971
onCancel={onCancel}
7072
>
@@ -103,4 +105,4 @@ export const NewFileModal: React.FunctionComponent = () => {
103105
</div>
104106
</ConfirmModal>
105107
);
106-
};
108+
};

src/components/Sidebar.tsx

Lines changed: 41 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,108 +1,103 @@
1-
import React from 'react';
21
import { VscListSelection, VscCode, VscOpenPreview, VscGraph, VscNewFile } from 'react-icons/vsc';
32

43
import { Tooltip } from './common';
54
import { SettingsModal } from './Modals/Settings/SettingsModal';
65

7-
import state from '../state';
6+
import { usePanelsState, panelsState } from '../state/index.state';
87

9-
type NavItemType = 'navigation' | 'editor' | 'template' | 'visualiser';
8+
import type { FunctionComponent, ReactNode } from 'react';
9+
import type { PanelsState } from '../state/panels.state';
1010

11-
function setActiveNav(navItem: NavItemType) {
12-
const panels = state.sidebar.panels;
13-
const panelsState = panels.get();
11+
function updateState(panelName: keyof PanelsState['show'], type?: PanelsState['secondaryPanelType']) {
12+
const settingsState = panelsState.getState();
13+
let secondaryPanelType = settingsState.secondaryPanelType;
14+
const newShow = { ...settingsState.show };
1415

15-
const newState = {
16-
...panelsState,
17-
};
18-
19-
if (navItem === 'template' || navItem === 'visualiser') {
16+
if (type === 'template' || type === 'visualiser') {
2017
// on current type
21-
if (newState.viewType === navItem) {
22-
newState.view = !newState.view;
18+
if (secondaryPanelType === type) {
19+
newShow[`${panelName}`] = !newShow[`${panelName}`];
2320
} else {
24-
newState.viewType = navItem;
25-
if (newState.view === false) {
26-
newState.view = true;
21+
secondaryPanelType = type;
22+
if (newShow[`${panelName}`] === false) {
23+
newShow[`${panelName}`] = true;
2724
}
2825
}
2926
} else {
30-
newState[`${navItem}`] = !newState[`${navItem}`];
27+
newShow[`${panelName}`] = !newShow[`${panelName}`];
3128
}
3229

33-
if (newState.navigation && !newState.editor && !newState.view) {
34-
panels.set({
35-
...newState,
36-
view: true,
37-
});
38-
return;
39-
}
40-
if (!Object.values(newState).some(itemNav => itemNav === true)) {
41-
panels.set({
42-
...newState,
43-
view: true,
44-
});
45-
return;
30+
if (!newShow.primaryPanel && !newShow.secondaryPanel) {
31+
newShow.secondaryPanel = true;
4632
}
4733

48-
panels.set(newState);
34+
panelsState.setState({
35+
show: newShow,
36+
secondaryPanelType,
37+
});
4938
}
5039

5140
interface NavItem {
5241
name: string;
5342
title: string;
54-
isActive: () => boolean;
55-
icon: React.ReactNode;
56-
tooltip: React.ReactNode;
43+
isActive: boolean;
44+
updateState?: () => void;
45+
icon: ReactNode;
46+
tooltip: ReactNode;
5747
}
5848

5949
interface SidebarProps {}
6050

61-
export const Sidebar: React.FunctionComponent<SidebarProps> = () => {
62-
const sidebarState = state.useSidebarState();
51+
export const Sidebar: FunctionComponent<SidebarProps> = () => {
52+
const { show, secondaryPanelType } = usePanelsState();
6353

64-
if (sidebarState.show.get() === false) {
54+
if (show.activityBar === false) {
6555
return null;
6656
}
6757

6858
const navigation: NavItem[] = [
6959
// navigation
7060
{
71-
name: 'navigation',
61+
name: 'primarySidebar',
7262
title: 'Navigation',
73-
isActive: () => sidebarState.panels.navigation.get(),
63+
isActive: show.primarySidebar,
64+
updateState: () => updateState('primarySidebar'),
7465
icon: <VscListSelection className="w-5 h-5" />,
7566
tooltip: 'Navigation',
7667
},
7768
// editor
7869
{
79-
name: 'editor',
70+
name: 'primaryPanel',
8071
title: 'Editor',
81-
isActive: () => sidebarState.panels.editor.get(),
72+
isActive: show.primaryPanel,
73+
updateState: () => updateState('primaryPanel'),
8274
icon: <VscCode className="w-5 h-5" />,
8375
tooltip: 'Editor',
8476
},
8577
// template
8678
{
8779
name: 'template',
8880
title: 'Template',
89-
isActive: () => sidebarState.panels.view.get() && sidebarState.panels.viewType.get() === 'template',
81+
isActive: show.secondaryPanel && secondaryPanelType === 'template',
82+
updateState: () => updateState('secondaryPanel', 'template'),
9083
icon: <VscOpenPreview className="w-5 h-5" />,
9184
tooltip: 'HTML preview',
9285
},
9386
// visuliser
9487
{
9588
name: 'visualiser',
9689
title: 'Visualiser',
97-
isActive: () => sidebarState.panels.view.get() && sidebarState.panels.viewType.get() === 'visualiser',
90+
isActive: show.secondaryPanel && secondaryPanelType === 'visualiser',
91+
updateState: () => updateState('secondaryPanel', 'visualiser'),
9892
icon: <VscGraph className="w-5 h-5" />,
9993
tooltip: 'Blocks visualiser',
10094
},
10195
// newFile
10296
{
10397
name: 'newFile',
10498
title: 'New file',
105-
isActive: () => false,
99+
isActive: false,
100+
updateState: () => panelsState.setState({ newFileOpened: true }),
106101
icon: <VscNewFile className="w-5 h-5" />,
107102
tooltip: 'New file',
108103
},
@@ -115,11 +110,11 @@ export const Sidebar: React.FunctionComponent<SidebarProps> = () => {
115110
<Tooltip content={item.tooltip} placement='right' hideOnClick={true} key={item.name}>
116111
<button
117112
title={item.title}
118-
onClick={() => setActiveNav(item.name as NavItemType)}
113+
onClick={() => item.updateState?.()}
119114
className={'flex text-sm focus:outline-none border-box p-2'}
120115
type="button"
121116
>
122-
<div className={item.isActive() ? 'bg-gray-600 p-2 rounded text-white' : 'p-2 text-gray-500 hover:text-white'}>
117+
<div className={item.isActive ? 'bg-gray-600 p-2 rounded text-white' : 'p-2 text-gray-500 hover:text-white'}>
123118
{item.icon}
124119
</div>
125120
</button>

src/services/converter.service.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { AbstractService } from './abstract.service';
2+
3+
import { convert } from '@asyncapi/converter';
4+
5+
import type { ConvertVersion, ConvertOptions } from '@asyncapi/converter';
6+
7+
export class ConverterService extends AbstractService {
8+
async convert(
9+
spec: string,
10+
version?: ConvertVersion,
11+
options?: ConvertOptions,
12+
): Promise<string> {
13+
version = version || this.svcs.specificationSvc.getLastVersion();
14+
15+
try {
16+
const converted = convert(spec, version, options);
17+
if (typeof converted === 'object') {
18+
return JSON.stringify(converted, undefined, 2);
19+
}
20+
return converted;
21+
} catch (err) {
22+
console.error(err);
23+
throw err;
24+
}
25+
}
26+
}

0 commit comments

Comments
 (0)