1- import React , { FC , useCallback , useMemo , useState } from 'react'
1+ import React , { FC , useCallback , useMemo , useRef , useState } from 'react'
22import { ContextPortal } from '@mweb/react'
33import { IContextNode , InsertionPointWithElement } from '@mweb/core'
44import { useEngine } from '../contexts/engine-context'
@@ -8,15 +8,75 @@ import { usePortalFilter } from '../contexts/engine-context/use-portal-filter'
88import { ShadowDomWrapper } from '../components/shadow-dom-wrapper'
99import { ContextTree } from '@mweb/react'
1010import { useContextApps } from '../contexts/mutable-web-context/use-context-apps'
11+ import { useAppControllers } from '../contexts/mutable-web-context/use-app-controllers'
1112import { AppId , AppMetadata } from '../services/application/application.entity'
12- import { BosUserLink , UserLinkId } from '../services/user-link/user-link.entity'
13+ import { BosUserLink , ControllerLink , UserLinkId } from '../services/user-link/user-link.entity'
1314import { TransferableContext , buildTransferableContext } from '../common/transferable-context'
1415import { useModal } from '../contexts/modal-context'
1516import { useMutableWeb } from '../contexts/mutable-web-context'
1617import { BuiltInLayoutManagers } from '../../constants'
1718import { TargetService } from '../services/target/target.service'
1819import { LinkedDataByAccount , LinkIndexRules } from '../services/link-db/link-db.entity'
1920import { memoize } from '../common/memoize'
21+ import { createPortal } from 'react-dom'
22+ import { ModalProps } from '../contexts/modal-context/modal-context'
23+ import { InjectableTarget } from '../contexts/engine-context/engine-context'
24+
25+ interface WidgetProps {
26+ context : TransferableContext
27+ link ?: {
28+ id : UserLinkId // Static link ID can also be here
29+ authorId : string
30+ }
31+ notify : ( modalProps : ModalProps ) => void
32+ linkDb : {
33+ get : (
34+ ctx : TransferableContext ,
35+ accountIds ?: string [ ] | string ,
36+ indexRules ?: LinkIndexRules
37+ ) => Promise < LinkedDataByAccount >
38+ set : (
39+ ctx : TransferableContext ,
40+ dataByAccount : LinkedDataByAccount ,
41+ indexRules : LinkIndexRules
42+ ) => Promise < void >
43+ }
44+ }
45+
46+ interface LayoutManagerProps {
47+ context : TransferableContext
48+ apps : {
49+ id : string
50+ metadata : {
51+ name ?: string
52+ description ?: string
53+ image ?: {
54+ ipfs_cid ?: string
55+ }
56+ }
57+ } [ ]
58+ widgets : {
59+ linkId : UserLinkId // Static link ID can also be here
60+ linkAuthorId : string
61+ static : boolean
62+ src : string
63+ props : WidgetProps
64+ isSuitable : boolean
65+ } [ ]
66+ components : {
67+ key : string
68+ target : InjectableTarget
69+ component : React . FC < unknown >
70+ } [ ]
71+ isEditMode : boolean
72+ createUserLink : ( appId : AppId ) => Promise < void >
73+ deleteUserLink : ( userLinkId : UserLinkId ) => Promise < void >
74+ enableEditMode : ( ) => void
75+ disableEditMode : ( ) => void
76+ attachContextRef : ( callback : ( r : React . Component | Element | null | undefined ) => void ) => void
77+ attachInsPointRef : ( callback : ( r : React . Component | Element | null | undefined ) => void ) => void
78+ notify : ( modalProps : ModalProps ) => void
79+ }
2080
2181export const ContextManager : FC = ( ) => {
2282 return < ContextTree children = { ContextHandler } />
@@ -26,8 +86,10 @@ const ContextHandler: FC<{ context: IContextNode; insPoints: InsertionPointWithE
2686 context,
2787 insPoints,
2888} ) => {
89+ const { controllers } = useAppControllers ( context )
2990 const { links, createUserLink, deleteUserLink } = useUserLinks ( context )
3091 const { apps } = useContextApps ( context )
92+ const { engine, selectedMutation } = useMutableWeb ( )
3193
3294 const [ isEditMode , setIsEditMode ] = useState ( false )
3395
@@ -49,6 +111,40 @@ const ContextHandler: FC<{ context: IContextNode; insPoints: InsertionPointWithE
49111 setIsEditMode ( false )
50112 } , [ setIsEditMode ] )
51113
114+ // These handlers are memoized to prevent unnecessary rerenders
115+ // Move to a separate hook when App wrapper is ready
116+ const handleGetLinkDataCurry = useCallback (
117+ memoize (
118+ ( appId : AppId ) =>
119+ ( ctx : TransferableContext , accountIds ?: string [ ] | string , indexRules ?: LinkIndexRules ) => {
120+ if ( ! selectedMutation ) throw new Error ( 'No selected mutation' )
121+ return engine . linkDbService . get ( selectedMutation . id , appId , ctx , accountIds , indexRules )
122+ }
123+ ) ,
124+ [ engine , selectedMutation ]
125+ )
126+
127+ const handleSetLinkDataCurry = useCallback (
128+ memoize (
129+ ( appId : AppId ) =>
130+ (
131+ ctx : TransferableContext ,
132+ dataByAccount : LinkedDataByAccount ,
133+ indexRules : LinkIndexRules
134+ ) => {
135+ if ( ! selectedMutation ) throw new Error ( 'No selected mutation' )
136+ return engine . linkDbService . set (
137+ selectedMutation . id ,
138+ appId ,
139+ ctx ,
140+ dataByAccount ,
141+ indexRules
142+ )
143+ }
144+ ) ,
145+ [ engine , selectedMutation ]
146+ )
147+
52148 return (
53149 < >
54150 { insPoints . map ( ( ip ) => (
@@ -66,6 +162,8 @@ const ContextHandler: FC<{ context: IContextNode; insPoints: InsertionPointWithE
66162 onEnableEditMode = { handleEnableEditMode }
67163 onDisableEditMode = { handleDisableEditMode }
68164 onAttachContextRef = { attachContextRef }
165+ onGetLinkDataCurry = { handleGetLinkDataCurry }
166+ onSetLinkDataCurry = { handleSetLinkDataCurry }
69167 />
70168 </ ContextPortal >
71169 ) ) }
@@ -82,8 +180,20 @@ const ContextHandler: FC<{ context: IContextNode; insPoints: InsertionPointWithE
82180 onEnableEditMode = { handleEnableEditMode }
83181 onDisableEditMode = { handleDisableEditMode }
84182 onAttachContextRef = { attachContextRef }
183+ onGetLinkDataCurry = { handleGetLinkDataCurry }
184+ onSetLinkDataCurry = { handleSetLinkDataCurry }
85185 />
86186 </ ContextPortal >
187+
188+ { controllers . map ( ( c ) => (
189+ < ControllerHandler
190+ key = { c . id }
191+ transferableContext = { transferableContext }
192+ controller = { c }
193+ onGetLinkDataCurry = { handleGetLinkDataCurry }
194+ onSetLinkDataCurry = { handleSetLinkDataCurry }
195+ />
196+ ) ) }
87197 </ >
88198 )
89199}
@@ -101,6 +211,20 @@ const InsPointHandler: FC<{
101211 onEnableEditMode : ( ) => void
102212 onDisableEditMode : ( ) => void
103213 onAttachContextRef : ( callback : ( r : React . Component | Element | null | undefined ) => void ) => void
214+ onGetLinkDataCurry : (
215+ appId : string
216+ ) => (
217+ ctx : TransferableContext ,
218+ accountIds ?: string [ ] | string ,
219+ indexRules ?: LinkIndexRules
220+ ) => Promise < LinkedDataByAccount >
221+ onSetLinkDataCurry : (
222+ appId : string
223+ ) => (
224+ ctx : TransferableContext ,
225+ dataByAccount : LinkedDataByAccount ,
226+ indexRules : LinkIndexRules
227+ ) => Promise < void >
104228} > = ( {
105229 insPointName,
106230 bosLayoutManager,
@@ -114,9 +238,11 @@ const InsPointHandler: FC<{
114238 onEnableEditMode,
115239 onDisableEditMode,
116240 onAttachContextRef,
241+ onGetLinkDataCurry,
242+ onSetLinkDataCurry,
117243} ) => {
118244 const { redirectMap, isDevServerLoading } = useEngine ( )
119- const { config, engine, selectedMutation } = useMutableWeb ( )
245+ const { config, engine } = useMutableWeb ( )
120246 const { components } = usePortalFilter ( context , insPointName ) // ToDo: extract to the separate AppManager component
121247 const { notify } = useModal ( )
122248
@@ -132,39 +258,6 @@ const InsPointHandler: FC<{
132258 [ context , insPointName ]
133259 )
134260
135- // These handlers are memoized to prevent unnecessary rerenders
136- const handleGetLinkDataCurry = useCallback (
137- memoize (
138- ( appId : AppId ) =>
139- ( ctx : TransferableContext , accountIds ?: string [ ] | string , indexRules ?: LinkIndexRules ) => {
140- if ( ! selectedMutation ) throw new Error ( 'No selected mutation' )
141- return engine . linkDbService . get ( selectedMutation . id , appId , ctx , accountIds , indexRules )
142- }
143- ) ,
144- [ engine , selectedMutation ]
145- )
146-
147- const handleSetLinkDataCurry = useCallback (
148- memoize (
149- ( appId : AppId ) =>
150- (
151- ctx : TransferableContext ,
152- dataByAccount : LinkedDataByAccount ,
153- indexRules : LinkIndexRules
154- ) => {
155- if ( ! selectedMutation ) throw new Error ( 'No selected mutation' )
156- return engine . linkDbService . set (
157- selectedMutation . id ,
158- appId ,
159- ctx ,
160- dataByAccount ,
161- indexRules
162- )
163- }
164- ) ,
165- [ engine , selectedMutation ]
166- )
167-
168261 // prevents blinking
169262 if ( isDevServerLoading ) {
170263 return null
@@ -186,7 +279,7 @@ const InsPointHandler: FC<{
186279
187280 // ToDo: extract App specific links to the separate AppManager component
188281
189- const props = {
282+ const props : LayoutManagerProps = {
190283 // ToDo: unify context forwarding
191284 context : transferableContext ,
192285 apps : apps
@@ -229,8 +322,8 @@ const InsPointHandler: FC<{
229322 } ,
230323 notify,
231324 linkDb : {
232- get : handleGetLinkDataCurry ( link . appId ) ,
233- set : handleSetLinkDataCurry ( link . appId ) ,
325+ get : onGetLinkDataCurry ( link . appId ) ,
326+ set : onSetLinkDataCurry ( link . appId ) ,
234327 } ,
235328 } , // ToDo: add props
236329 isSuitable : link . insertionPoint === insPointName , // ToDo: LM know about widgets from other LM
@@ -271,3 +364,52 @@ const InsPointHandler: FC<{
271364 </ ShadowDomWrapper >
272365 )
273366}
367+
368+ /**
369+ * Executes a BOS widget in-memory without rendering it
370+ */
371+ const ControllerHandler : FC < {
372+ transferableContext : TransferableContext
373+ controller : ControllerLink
374+ onGetLinkDataCurry : (
375+ appId : string
376+ ) => (
377+ ctx : TransferableContext ,
378+ accountIds ?: string [ ] | string ,
379+ indexRules ?: LinkIndexRules
380+ ) => Promise < LinkedDataByAccount >
381+ onSetLinkDataCurry : (
382+ appId : string
383+ ) => (
384+ ctx : TransferableContext ,
385+ dataByAccount : LinkedDataByAccount ,
386+ indexRules : LinkIndexRules
387+ ) => Promise < void >
388+ } > = ( { transferableContext, controller, onGetLinkDataCurry, onSetLinkDataCurry } ) => {
389+ const { redirectMap, isDevServerLoading } = useEngine ( )
390+ const portalRef = useRef < DocumentFragment | null > ( null )
391+ const { notify } = useModal ( )
392+
393+ if ( ! portalRef . current ) {
394+ // A document fragment where BOS widget will be "rendered" to
395+ portalRef . current = document . createDocumentFragment ( )
396+ }
397+
398+ if ( isDevServerLoading ) {
399+ return null
400+ }
401+
402+ const props : WidgetProps = {
403+ context : transferableContext ,
404+ notify,
405+ linkDb : {
406+ get : onGetLinkDataCurry ( controller . appId ) ,
407+ set : onSetLinkDataCurry ( controller . appId ) ,
408+ } ,
409+ }
410+
411+ return createPortal (
412+ < Widget src = { controller . bosWidgetId } props = { props } loading = { < > </ > } config = { { redirectMap } } /> ,
413+ portalRef . current
414+ )
415+ }
0 commit comments