Skip to content

Commit

Permalink
feat: plugin sizing updates (#902)
Browse files Browse the repository at this point in the history
Supports easier-to-use plugin sizing and improves performance.
Should be paired with app-runtime >=3.13.0.
See https://dhis2.atlassian.net/browse/LIBS-723

* feat(plugin): simpler sizing; content-driven size without animation loop

* fix(plugins): remove scrollbar padding

* fix: resize dif references
  • Loading branch information
KaiVandivier authored Jan 23, 2025
1 parent 0938b70 commit 1136e0d
Show file tree
Hide file tree
Showing 2 changed files with 57 additions and 55 deletions.
2 changes: 1 addition & 1 deletion examples/simple-app/src/App.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@
align-items: center;
justify-content: center;
font-size: 1rem;
}
}
110 changes: 56 additions & 54 deletions shell/src/PluginLoader.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,64 +10,49 @@ const PluginResizeInner = ({
propsFromParent,
resizePluginHeight,
resizePluginWidth,
clientWidth,
}) => {
const divRef = useRef()
const innerDivRef = useRef()
const resizeDivRef = useRef()

useEffect(() => {
if (divRef && divRef.current && resizePluginHeight) {
const container = divRef.current
if (resizeDivRef?.current) {
const resizeDiv = resizeDivRef.current
const resizeObserver = new ResizeObserver(() => {
// the additional pixels currently account for possible horizontal scroll bar
resizePluginHeight(container.offsetHeight + 20)
if (resizePluginHeight) {
// offsetHeight takes into account possible scrollbar size
resizePluginHeight(resizeDiv.offsetHeight)
}
if (resizePluginWidth) {
resizePluginWidth(resizeDiv.scrollWidth)
}
})
resizeObserver.observe(container)
return () => {
resizeObserver.unobserve(container)
resizeObserver.disconnect()
}
}
}, [resizePluginHeight])

const previousWidth = useRef()
resizeObserver.observe(resizeDiv)

const resetWidth = useCallback(() => {
const currentWidth = innerDivRef.current?.scrollWidth
if (resizePluginWidth && currentWidth) {
if (
previousWidth.current &&
Math.abs(currentWidth - previousWidth.current) > 20
) {
resizePluginWidth(currentWidth + 20)
return () => {
resizeObserver.unobserve(resizeDiv)
resizeObserver.disconnect()
}
previousWidth.current = currentWidth
}
requestAnimationFrame(resetWidth)
}, [resizePluginWidth])

useEffect(() => {
if (resizePluginWidth) {
requestAnimationFrame(resetWidth)
}
}, [resetWidth, resizePluginWidth])
}, [resizePluginHeight, resizePluginWidth])

// inner div disables margin collapsing which would prevent computing correct height
// For the width to be content-driven, the clientWidth needs to be specified
// so that width is not just 'auto'. Then, when content resizes, it will
// actually trigger the resize observer, updating the parent's iframe size
return (
<div ref={divRef}>
<div>
<div id="innerDiv" ref={innerDivRef}>
<D2App
config={config}
resizePluginWidth={resizePluginWidth}
{...propsFromParent}
/>
</div>
</div>
<div id="resizeDiv" ref={resizeDivRef} style={{ width: clientWidth }}>
<D2App
config={config}
resizePluginWidth={resizePluginWidth}
{...propsFromParent}
/>
</div>
)
}

PluginResizeInner.propTypes = {
D2App: PropTypes.object,
clientWidth: PropTypes.string,
config: PropTypes.object,
propsFromParent: PropTypes.object,
resizePluginHeight: PropTypes.func,
Expand All @@ -80,15 +65,13 @@ const PluginInner = ({
propsFromParent,
resizePluginHeight,
resizePluginWidth,
clientWidth,
}) => {
// If a resize function isn't defined, that value is container-driven and
// doesn't need resizing.
// If neither are defined, then don't need the ResizeInner
if (!resizePluginHeight && !resizePluginWidth) {
return (
<D2App
config={config}
resizePluginWidth={resizePluginWidth}
{...propsFromParent}
/>
)
return <D2App config={config} {...propsFromParent} />
}
return (
<PluginResizeInner
Expand All @@ -97,12 +80,14 @@ const PluginInner = ({
propsFromParent={propsFromParent}
resizePluginHeight={resizePluginHeight}
resizePluginWidth={resizePluginWidth}
clientWidth={clientWidth}
/>
)
}

PluginInner.propTypes = {
D2App: PropTypes.object,
clientWidth: PropTypes.string,
config: PropTypes.object,
propsFromParent: PropTypes.object,
resizePluginHeight: PropTypes.func,
Expand All @@ -116,8 +101,15 @@ export const PluginLoader = ({ config, requiredProps, D2App }) => {
const [showAlertsInPlugin, setShowAlertsInPlugin] = useState(false)
const [onPluginError, setOnPluginError] = useState(() => () => {})
const [clearPluginError, setClearPluginError] = useState(() => () => {})
// These two can be populated with callbacks from the parent. They can be
// called with a new value for the respective dimension, which will be set
// on the iframe element to resize the plugin
const [resizePluginHeight, setResizePluginHeight] = useState(null)
const [resizePluginWidth, setResizePluginWidth] = useState(null)
// This value will be used as the 'width' property of the div wrapping the
// app inside the plugin. Gets set by a prop from the parent.
// See the Plugin docs or the PluginResizeInner component for more info
const [clientWidth, setClientWidth] = useState(null)

const receivePropsFromParent = useCallback(
(event) => {
Expand All @@ -127,11 +119,10 @@ export const PluginLoader = ({ config, requiredProps, D2App }) => {
setCommunicationReceived,
alertsAdd,
showAlertsInPlugin,
height,
setPluginHeight,
width,
setPluginWidth,
onError,
clientWidth: clientWidthFromParent,
...explicitlyPassedProps
} = receivedProps

Expand Down Expand Up @@ -173,12 +164,21 @@ export const PluginLoader = ({ config, requiredProps, D2App }) => {
setShowAlertsInPlugin(Boolean(showAlertsInPlugin))
}

if (!height && setPluginHeight) {
// if these resize callbacks are defined, then that dimension isn't
// fixed or container-driven; add them to the props list here.
// It will be called by a resize observer in ResizePluginInner
if (setPluginHeight) {
setResizePluginHeight(() => (height) => setPluginHeight(height))
}
// same as height
if (setPluginWidth) {
setResizePluginWidth(() => (width) => {
setPluginWidth(width)
})
}

if (!width && setPluginWidth) {
setResizePluginWidth(() => (width) => setPluginWidth(width))
if (clientWidthFromParent) {
setClientWidth(clientWidthFromParent)
}
},
[
Expand All @@ -189,6 +189,7 @@ export const PluginLoader = ({ config, requiredProps, D2App }) => {
setShowAlertsInPlugin,
setResizePluginHeight,
setResizePluginWidth,
setClientWidth,
]
)

Expand Down Expand Up @@ -247,6 +248,7 @@ export const PluginLoader = ({ config, requiredProps, D2App }) => {
propsFromParent={propsFromParent}
resizePluginHeight={resizePluginHeight}
resizePluginWidth={resizePluginWidth}
clientWidth={clientWidth}
/>
</React.Suspense>
</AppAdapter>
Expand Down

0 comments on commit 1136e0d

Please sign in to comment.