Skip to content

Commit 2dff4ed

Browse files
samuel-olivierLoïc Mangeonjean
authored andcommitted
feat: add vscode part component logic
1 parent b1299a7 commit 2dff4ed

File tree

2 files changed

+162
-0
lines changed

2 files changed

+162
-0
lines changed

package.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,22 @@
1414
"moduleResolution": "node",
1515
"main": "dist/index.js",
1616
"module": "dist/index.js",
17+
"exports": {
18+
".": {
19+
"default": "./dist/index.js"
20+
},
21+
"./vscodeParts": {
22+
"types": "./dist/vscodeParts.d.ts",
23+
"default": "./dist/vscodeParts.js"
24+
}
25+
},
26+
"typesVersions": {
27+
"*": {
28+
"vscodeParts": [
29+
"./dist/vscodeParts.d.ts"
30+
]
31+
}
32+
},
1733
"files": [
1834
"dist/"
1935
],

src/vscodeParts.tsx

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
import React, { Fragment, ReactNode, useEffect, useRef, useState } from 'react'
2+
import * as monaco from 'monaco-editor'
3+
import { renderSidebarPart, renderActivitybarPar, renderEditorPart, renderPanelPart, renderStatusBarPart, registerCustomView, ViewContainerLocation, CustomViewOption } from '@codingame/monaco-vscode-views-service-override/views'
4+
import { createPortal } from 'react-dom'
5+
import { initializePromise } from '@codingame/monaco-editor-wrapper'
6+
import { DisposableStore } from 'vscode/monaco'
7+
8+
interface CustomView extends Omit<CustomViewOption, 'renderBody' | 'location' | 'actions'> {
9+
node: ReactNode
10+
actions?: (Omit<NonNullable<CustomViewOption['actions']>[0], 'render'> & {
11+
node?: ReactNode
12+
})[]
13+
}
14+
15+
interface VscodePartRendererProps {
16+
views?: CustomView[]
17+
className?: string
18+
style?: React.CSSProperties
19+
}
20+
21+
function createPart (location: ViewContainerLocation | null, renderPart: (container: HTMLElement) => monaco.IDisposable) {
22+
const element = document.createElement('div')
23+
element.style.flex = '1'
24+
element.style.minWidth = '0'
25+
26+
initializePromise.then(() => {
27+
renderPart(element)
28+
}, console.error)
29+
30+
let counter = 0
31+
32+
function getViewId (id: string, counter: number) {
33+
if (counter > 0) {
34+
return `${id}-${counter}`
35+
}
36+
return id
37+
}
38+
39+
return function VscodePartRenderer ({ views = [], className, style }: VscodePartRendererProps) {
40+
const ref = useRef<HTMLDivElement>(null)
41+
useEffect(() => {
42+
ref.current?.append(element)
43+
}, [])
44+
45+
const [renderCounter] = useState(() => counter++)
46+
47+
const [portalComponents, setPortalComponents] = useState({} as Record<string, HTMLElement>)
48+
49+
useEffect(() => {
50+
if (location == null) {
51+
return
52+
}
53+
const disposableStore = new DisposableStore()
54+
for (const view of views) {
55+
disposableStore.add(registerCustomView({
56+
id: getViewId(view.id, renderCounter),
57+
name: view.name,
58+
order: view.order,
59+
renderBody: function (container: HTMLElement): monaco.IDisposable {
60+
setPortalComponents(portalComponents => ({
61+
...portalComponents,
62+
[view.id]: container
63+
}))
64+
65+
return {
66+
dispose () {
67+
setPortalComponents(portalComponents => {
68+
const { [view.id]: _, ...remaining } = portalComponents
69+
return remaining
70+
})
71+
}
72+
}
73+
},
74+
location,
75+
icon: view.icon,
76+
actions: view.actions?.map(action => {
77+
return {
78+
...action,
79+
render: action.node != null
80+
? (container) => {
81+
setPortalComponents(portalComponents => ({
82+
...portalComponents,
83+
[action.id]: container
84+
}))
85+
86+
return {
87+
dispose () {
88+
setPortalComponents(portalComponents => {
89+
const { [action.id]: _, ...remaining } = portalComponents
90+
return remaining
91+
})
92+
}
93+
}
94+
}
95+
: undefined
96+
}
97+
})
98+
}))
99+
}
100+
return () => {
101+
disposableStore.dispose()
102+
}
103+
}, [renderCounter, views])
104+
105+
return (
106+
<>
107+
{views.filter(view => view.id in portalComponents).map((view) => {
108+
return (
109+
<Fragment key={view.id}>
110+
{createPortal(
111+
view.node,
112+
portalComponents[view.id]!,
113+
view.id
114+
)}
115+
{view.actions?.filter(action => action.id in portalComponents).map(action => (
116+
<Fragment key={action.id}>
117+
{createPortal(
118+
action.node,
119+
portalComponents[action.id]!,
120+
action.id
121+
)}
122+
</Fragment>
123+
))}
124+
</Fragment>
125+
)
126+
})}
127+
<div
128+
ref={ref} className={className} style={{
129+
display: 'flex',
130+
alignItems: 'stretch',
131+
justifyContent: 'stretch',
132+
...style
133+
}}
134+
/>
135+
</>
136+
)
137+
}
138+
}
139+
140+
export const SidebarPart = createPart(ViewContainerLocation.Sidebar, renderSidebarPart)
141+
export const ActivitybarPart = createPart(null, renderActivitybarPar)
142+
export const EditorPart = createPart(null, renderEditorPart)
143+
export const StatusBarPart = createPart(null, renderStatusBarPart)
144+
export const PanelPart = createPart(ViewContainerLocation.Panel, renderPanelPart)
145+
146+
export type { CustomView }

0 commit comments

Comments
 (0)