Skip to content

Commit c353b3f

Browse files
authored
feat(plugin): support topbar.right plugin ui slot (#3621)
* add `PluginUITopBar` demo view
1 parent cf421fe commit c353b3f

11 files changed

Lines changed: 197 additions & 54 deletions

File tree

eslint.config.js

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import ts from 'eslint-config-cheminfo-typescript/base';
33
import unicorn from 'eslint-config-cheminfo-typescript/unicorn';
44

55
export default [
6-
76
{
87
ignores: [
98
'build',
@@ -14,15 +13,16 @@ export default [
1413
'playwright-report',
1514
'test-results',
1615
'public',
16+
'.yalc',
1717
],
1818
},
1919
...ts,
2020
...unicorn,
2121
...react,
2222
{
23-
files: ["**/pdnd.cts"],
23+
files: ['**/pdnd.cts'],
2424
rules: {
25-
"import/no-extraneous-dependencies": "off",
25+
'import/no-extraneous-dependencies': 'off',
2626
},
2727
},
2828
{
@@ -54,7 +54,8 @@ export default [
5454
{
5555
name: '@simbathesailor/use-what-changed',
5656
message: 'Remove use-what-changed before committing the code',
57-
}],
57+
},
58+
],
5859
'react/no-unknown-property': ['error', { ignore: ['css'] }],
5960
'react/forbid-dom-props': [
6061
'error',

package-lock.json

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

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,8 @@
6464
"@emotion/styled": "^11.14.1",
6565
"@hookform/resolvers": "^5.1.1",
6666
"@zakodium/nmr-types": "^0.1.0",
67-
"@zakodium/nmrium-core": "^0.1.4",
68-
"@zakodium/nmrium-core-plugins": "^0.1.7",
67+
"@zakodium/nmrium-core": "^0.1.5",
68+
"@zakodium/nmrium-core-plugins": "^0.1.8",
6969
"@zip.js/zip.js": "^2.7.68",
7070
"cheminfo-font": "^1.13.1",
7171
"cheminfo-types": "^1.8.1",

src/component/header/Header.tsx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import styled from '@emotion/styled';
12
import { memo, useMemo } from 'react';
23
import {
34
FaFilm,
@@ -9,6 +10,7 @@ import { Toolbar, useFullscreen } from 'react-science/ui';
910

1011
import { docsBaseUrl } from '../../constants.js';
1112
import { useChartData } from '../context/ChartContext.js';
13+
import { useCore } from '../context/CoreContext.js';
1214
import {
1315
usePreferences,
1416
useWorkspacesList,
@@ -26,6 +28,7 @@ import AboutUsModal from '../modal/aboutUs/AboutUsModal.js';
2628
import GeneralSettingsModal from '../modal/setting/GeneralSettings.js';
2729
import WorkspaceItem from '../modal/setting/WorkspaceItem.js';
2830
import { options } from '../toolbar/ToolTypes.js';
31+
import { renderCoreSlot } from '../utility/renderCoreSlot.js';
2932

3033
import { AutoPeakPickingOptionPanel } from './AutoPeakPickingOptionPanel.js';
3134
import { HeaderWrapper } from './HeaderWrapper.js';
@@ -60,6 +63,7 @@ interface HeaderInnerProps {
6063

6164
function HeaderInner(props: HeaderInnerProps) {
6265
const { selectedOptionPanel, height } = props;
66+
const core = useCore();
6367

6468
const {
6569
current: {
@@ -144,6 +148,10 @@ function HeaderInner(props: HeaderInnerProps) {
144148
alignItems: 'center',
145149
}}
146150
>
151+
<PluginTopBarRight>
152+
{renderCoreSlot(core, 'topbar.right')}
153+
</PluginTopBarRight>
154+
147155
{!hideWorkspaces && (
148156
<DropDownButton
149157
data={workspacesList}
@@ -242,3 +250,10 @@ export function Header() {
242250
/>
243251
);
244252
}
253+
254+
const PluginTopBarRight = styled.div`
255+
display: flex;
256+
align-items: center;
257+
gap: 5px;
258+
margin-right: 5px;
259+
`;
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import type { NMRiumCore, SupportedUISlot } from '@zakodium/nmrium-core';
2+
import type { ReactNode } from 'react';
3+
4+
export function renderCoreSlot(
5+
core: NMRiumCore,
6+
slot: SupportedUISlot,
7+
): ReactNode[] {
8+
return Array.from(core.slot(slot), ([key, Component]) => (
9+
<Component key={key} slot={slot} />
10+
));
11+
}

src/demo/samples.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,5 +345,15 @@
345345
"width": "1000px",
346346
"height": "600px"
347347
}
348+
},
349+
{
350+
"groupName": "Plugin UI",
351+
"children": [
352+
{
353+
"title": "TopBar Plugin",
354+
"file": "./data/cytisine/1H.json",
355+
"view": "PluginUITopBar"
356+
}
357+
]
348358
}
349359
]

src/demo/views/PluginUITopBar.tsx

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import type { IconName } from '@blueprintjs/icons';
2+
import { IconNames } from '@blueprintjs/icons';
3+
import styled from '@emotion/styled';
4+
import init from '@zakodium/nmrium-core-plugins';
5+
import {
6+
createContext,
7+
useCallback,
8+
useContext,
9+
useMemo,
10+
useState,
11+
} from 'react';
12+
import { Toolbar } from 'react-science/ui';
13+
14+
import { NMRium } from '../../component/main/index.js';
15+
16+
import type { ViewProps } from './View.helpers.js';
17+
import { useView } from './View.helpers.js';
18+
19+
const Container = styled.section`
20+
display: flex;
21+
flex-direction: column;
22+
height: 100%;
23+
24+
& > h1 {
25+
font-weight: 700;
26+
font-size: 1.5em;
27+
line-height: 1.4em;
28+
padding: 0.75em;
29+
}
30+
`;
31+
32+
const NMRiumContainer = styled.div`
33+
flex: 1;
34+
`;
35+
36+
const DemoContext = createContext<{
37+
setRandom: () => void;
38+
icon: IconName;
39+
// eslint-disable-next-line @typescript-eslint/no-empty-function
40+
}>({ icon: 'console', setRandom: () => {} });
41+
42+
function DemoTopBarRight() {
43+
const { setRandom, icon } = useContext(DemoContext);
44+
45+
return (
46+
<Toolbar>
47+
<Toolbar.Item
48+
id="demo-topbar-right"
49+
icon={icon}
50+
tooltip="Demo topbar right"
51+
onClick={setRandom}
52+
/>
53+
</Toolbar>
54+
);
55+
}
56+
57+
const core = init([
58+
{
59+
id: 'demo-plugin-topbar',
60+
version: 1,
61+
migrations: [],
62+
ui: {
63+
'topbar.right': DemoTopBarRight,
64+
},
65+
},
66+
]);
67+
68+
const possibleIcons = Object.values(IconNames);
69+
export default function PluginUITopBar(props: ViewProps) {
70+
const [data, otherProps] = useView(props);
71+
72+
const { workspace, customWorkspaces } = otherProps;
73+
74+
const [icon, setIcon] = useState<IconName>('console');
75+
const setRandom = useCallback(() => {
76+
const randomIndex = Math.floor(Math.random() * possibleIcons.length);
77+
setIcon(possibleIcons[randomIndex]);
78+
}, []);
79+
const context = useMemo(() => ({ setRandom, icon }), [icon, setRandom]);
80+
81+
return (
82+
<Container>
83+
<h1>TopBar Plugin</h1>
84+
85+
<NMRiumContainer>
86+
<DemoContext.Provider value={context}>
87+
<NMRium
88+
core={core}
89+
data={data}
90+
{...(workspace && { workspace })}
91+
{...(customWorkspaces && { customWorkspaces })}
92+
/>
93+
</DemoContext.Provider>
94+
</NMRiumContainer>
95+
</Container>
96+
);
97+
}

src/demo/views/Test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { DropZone } from 'react-science/ui';
55

66
import { NMRium } from '../../component/main/index.js';
77

8-
import { loadData } from './View.js';
8+
import { loadData } from './View.helpers.js';
99

1010
function searchDeep(obj, searchKey) {
1111
const result: any = [];

src/demo/views/View.helpers.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { useEffect, useState } from 'react';
2+
3+
import type { BaseViewProps } from './BaseView.js';
4+
5+
export async function loadData(file) {
6+
const response = await fetch(file);
7+
checkStatus(response);
8+
const data = await response.json();
9+
return data;
10+
}
11+
12+
function checkStatus(response) {
13+
if (!response.ok) {
14+
throw new Error(`HTTP ${response.status} - ${response.statusText}`);
15+
}
16+
return response;
17+
}
18+
19+
export interface ViewProps extends Omit<BaseViewProps, 'data'> {
20+
file: string;
21+
baseURL: string;
22+
}
23+
24+
export function useView(props: ViewProps) {
25+
const [data, setData] = useState();
26+
27+
const { file, baseURL, ...otherProps } = props;
28+
29+
useEffect(() => {
30+
if (file) {
31+
void loadData(file).then((d) => {
32+
const _d = JSON.parse(JSON.stringify(d).replaceAll(/\.\/+?/g, baseURL));
33+
setData(_d);
34+
});
35+
} else {
36+
setData(undefined);
37+
}
38+
}, [baseURL, file]);
39+
40+
return [data, otherProps] as const;
41+
}

src/demo/views/View.tsx

Lines changed: 3 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,9 @@
1-
import { useEffect, useState } from 'react';
2-
3-
import type { BaseViewProps } from './BaseView.js';
41
import BaseView from './BaseView.js';
5-
6-
export async function loadData(file) {
7-
const response = await fetch(file);
8-
checkStatus(response);
9-
const data = await response.json();
10-
return data;
11-
}
12-
13-
function checkStatus(response) {
14-
if (!response.ok) {
15-
throw new Error(`HTTP ${response.status} - ${response.statusText}`);
16-
}
17-
return response;
18-
}
19-
20-
interface ViewProps extends Omit<BaseViewProps, 'data'> {
21-
file: string;
22-
baseURL: string;
23-
}
2+
import type { ViewProps } from './View.helpers.js';
3+
import { useView } from './View.helpers.js';
244

255
export default function View(props: ViewProps) {
26-
const [data, setData] = useState();
27-
28-
const { file, baseURL, ...otherProps } = props;
29-
30-
useEffect(() => {
31-
if (file) {
32-
void loadData(file).then((d) => {
33-
const _d = JSON.parse(JSON.stringify(d).replaceAll(/\.\/+?/g, baseURL));
34-
setData(_d);
35-
});
36-
} else {
37-
setData(undefined);
38-
}
39-
}, [baseURL, file]);
6+
const [data, otherProps] = useView(props);
407

418
return <BaseView {...otherProps} data={data} />;
429
}

0 commit comments

Comments
 (0)