Skip to content

Commit 0c7d5ad

Browse files
committed
feat: execute controller in memory (DAP-4684)
1 parent b7d5c09 commit 0c7d5ad

File tree

7 files changed

+225
-45
lines changed

7 files changed

+225
-45
lines changed
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
type MemoizedFunction<T, R> = (arg: T) => R
22

33
export const memoize =
4-
<T, R>(fn: MemoizedFunction<T, R>, map = new Map<T, R | MemoizedFunction<T, R>>()) =>
5-
(arg: T): R | MemoizedFunction<T, R> => {
4+
<T, R>(fn: MemoizedFunction<T, R>, map = new Map<T, R>()) =>
5+
(arg: T): R => {
66
const inCache = map.has(arg)
77

88
if (!inCache) {
99
map.set(arg, fn(arg))
1010
}
1111

12-
return map.get(arg) as R | MemoizedFunction<T, R>
12+
return map.get(arg) as R
1313
}

libs/engine/src/app/components/context-manager.tsx

Lines changed: 181 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { FC, useCallback, useMemo, useState } from 'react'
1+
import React, { FC, useCallback, useMemo, useRef, useState } from 'react'
22
import { ContextPortal } from '@mweb/react'
33
import { IContextNode, InsertionPointWithElement } from '@mweb/core'
44
import { useEngine } from '../contexts/engine-context'
@@ -8,15 +8,75 @@ import { usePortalFilter } from '../contexts/engine-context/use-portal-filter'
88
import { ShadowDomWrapper } from '../components/shadow-dom-wrapper'
99
import { ContextTree } from '@mweb/react'
1010
import { useContextApps } from '../contexts/mutable-web-context/use-context-apps'
11+
import { useAppControllers } from '../contexts/mutable-web-context/use-app-controllers'
1112
import { 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'
1314
import { TransferableContext, buildTransferableContext } from '../common/transferable-context'
1415
import { useModal } from '../contexts/modal-context'
1516
import { useMutableWeb } from '../contexts/mutable-web-context'
1617
import { BuiltInLayoutManagers } from '../../constants'
1718
import { TargetService } from '../services/target/target.service'
1819
import { LinkedDataByAccount, LinkIndexRules } from '../services/link-db/link-db.entity'
1920
import { 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

2181
export 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+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { useMemo } from 'react'
2+
import { IContextNode } from '@mweb/core'
3+
import { useMutableWeb } from '.'
4+
5+
export const useAppControllers = (context: IContextNode) => {
6+
const { engine, selectedMutation, activeApps } = useMutableWeb()
7+
8+
const controllers = useMemo(() => {
9+
if (!engine || !selectedMutation?.id) {
10+
return []
11+
} else {
12+
return engine.userLinkService.getControllersForApps(activeApps, context)
13+
}
14+
}, [engine, selectedMutation, activeApps, context])
15+
16+
return { controllers }
17+
}

libs/engine/src/app/contexts/mutable-web-context/use-user-links.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export const useUserLinks = (context: IContextNode) => {
1313
if (!engine || !selectedMutation?.id) {
1414
return []
1515
} else {
16-
return engine.userLinkService.getStaticLinksForApp(activeApps, context)
16+
return engine.userLinkService.getStaticLinksForApps(activeApps, context)
1717
}
1818
}, [engine, selectedMutation, activeApps, context])
1919

libs/engine/src/app/services/application/application.entity.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export type AppMetadata = {
1414
authorId: string
1515
appLocalId: string
1616
targets: AppMetadataTarget[]
17+
controller?: string // BOS Widget ID
1718
metadata: {
1819
name?: string
1920
description?: string

libs/engine/src/app/services/user-link/user-link.entity.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,9 @@ export type BosUserLink = {
3131
static: boolean
3232
// ToDo: add props
3333
}
34+
35+
export type ControllerLink = {
36+
id: string
37+
appId: string
38+
bosWidgetId: string
39+
}

0 commit comments

Comments
 (0)