Skip to content

Commit

Permalink
feat: align elements in different screen sections
Browse files Browse the repository at this point in the history
We support several screen elements that need to be aligned together: maximized elements on top-left (like video/screenshare/plugins), top-right elements (voice and text chats) and previews on bottom-right (video/plugins/screencast). Video/screencast/plugins can also move between top-left and bottom-right - the important thing here is to not make them detach from parent when moving between screen sections - and it's pretty challenging to satisfy all this.

Current solution uses flexbox for SectionedScreen component and SectionedScreenPortal elements (used as Portal for all abovementioned). SectionedScreenPortal appends a child to SectionedScreen using proper class based on target section.

Different sections' classes have slightly different styles/order and additional logic is applied to force the flexbox break into 2 rows if both top-right and bottom-right elements are detected. We use ":has" selector that currently has relatively limited support - it currently works on Chrome/Brave, but on Firefox you still need to activate it with config. There's a fallback option allowing almost same functionality without the ability to use full height of the screen when only top-left and/or top-right elements are present - they're at 60% height then.

Using flexbox is a working though not perfect solution as it's not really intended for such layout.

I tried display:grid but it becomes even more complex to manipulate it in flexible enough manner based on content.

My original approach was with 2 nested containers - one for top/bottom row and one left/right for each row - it was working nicely as a layout but moving between sections triggers child detach, which causes interruptions with iframe videos, etc. It's probably possibly to use shadow DOM for translating screen element into different sections but I doubt it's possible using only css.

Another planned approach was having React Context used by SectionedScreen and SectionedScreenPortals for sharing info between them and applying different styles and helper elements based on that, but it seems css with :has selector allows to satisfy all our current requirements.

Some relatively new css selectors like :only-child, :only-of-type, :nth-of-type could also be of use in future.

Some useful links:
https://ishadeed.com/article/conditional-css/
https://tobiasahlin.com/blog/flexbox-break-to-new-row/

https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Flexible_Box_Layout/Aligning_Items_in_a_Flex_Container

https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Grid_Layout/Basic_Concepts_of_Grid_Layout

Shadow DOM:
whatwg/html#5484 (comment) - nice and simple example
https://stackoverflow.com/questions/42274721/shadow-dom-and-reactjs
https://developer.mozilla.org/en-US/docs/Web/API/Element/attachShadow
  • Loading branch information
dmitry-yudakov committed Feb 13, 2023
1 parent d14d268 commit 82bf81d
Show file tree
Hide file tree
Showing 18 changed files with 194 additions and 273 deletions.
4 changes: 3 additions & 1 deletion packages/app/src/scenes/AppLayers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ const AppLayers: FC<PropsInterface> = (props) => {
<div data-testid="AppLayers-test">
<ToastMessage position={toast.POSITION.BOTTOM_RIGHT} theme={theme} />
<UnityControlContextProvider value={unityStore.unityInstanceStore.unityControlInst}>
<SectionedScreen />
<div id="sectioned-screen-container">
<SectionedScreen />
</div>
{renderUnity && <Widgets />}
<main id="main">
<div className="main-content">{children}</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import {FC, useEffect} from 'react';
import {observer} from 'mobx-react-lite';
import {useTheme} from 'styled-components';
import {ErrorBoundary, SectionPortal, Text, WindowPanel} from '@momentum-xyz/ui-kit';
import {
ErrorBoundary,
ScreenSectionsEnum,
SectionedScreenPortal,
Text,
WindowPanel
} from '@momentum-xyz/ui-kit';
import {useTranslation} from 'react-i18next';
import {toast} from 'react-toastify';
import {
Expand Down Expand Up @@ -98,9 +104,11 @@ const PluginInnerWrapper = ({
const {content, objectView} = plugin.usePlugin(pluginProps);

return !pluginLoader.isError ? (
<SectionPortal
<SectionedScreenPortal
data-testid="ScreenShareWidget-test"
section={pluginLoader.isExpanded ? 'left-top' : 'right-bottom'}
section={
pluginLoader.isExpanded ? ScreenSectionsEnum.TOP_LEFT : ScreenSectionsEnum.BOTTOM_RIGHT
}
maximized={pluginLoader.isExpanded}
>
{content ? (
Expand All @@ -121,7 +129,7 @@ const PluginInnerWrapper = ({
) : (
<Text text={t('errors.errorPluginContactDev')} size="l" />
)}
</SectionPortal>
</SectionedScreenPortal>
) : (
<Text text={t('errors.errorWhileLoadingPlugin')} size="l" />
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, {FC, useCallback, useEffect} from 'react';
import {observer} from 'mobx-react-lite';
import {SectionPortal, WindowPanel} from '@momentum-xyz/ui-kit';
import {ScreenSectionsEnum, SectionedScreenPortal, WindowPanel} from '@momentum-xyz/ui-kit';
import {useTranslation} from 'react-i18next';

import {useStore} from 'shared/hooks';
Expand Down Expand Up @@ -41,9 +41,11 @@ const ScreenShareWidget: FC = () => {
};

return (
<SectionPortal
<SectionedScreenPortal
data-testid="ScreenShareWidget-test"
section={screenShareStore.isExpanded ? 'left-top' : 'right-bottom'}
section={
screenShareStore.isExpanded ? ScreenSectionsEnum.TOP_LEFT : ScreenSectionsEnum.BOTTOM_RIGHT
}
maximized={screenShareStore.isExpanded}
>
<WindowPanel
Expand All @@ -63,7 +65,7 @@ const ScreenShareWidget: FC = () => {
<ScreenVideo videoTrack={localVideoTrack ? localVideoTrack : remoteVideoTrack} />
)}
</WindowPanel>
</SectionPortal>
</SectionedScreenPortal>
);
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,13 @@
import {rgba} from 'polished';
import styled from 'styled-components';

export const Modal = styled.div`
display: flex;
// margin: 10px;
// box-sizing: border-box;
// position: absolute;
// right: 0;
// top: 0;
// margin: 20px;
`;

export const Container = styled.div`
background: ${(props) => props.theme.bg && rgba(props.theme.bg, 0.75)};
border-radius: 10px;
overflow: hidden;
width: 280px;
height: 584px;
max-height: 584px;
height: 100%;
display: flex;
flex-direction: column;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import React, {FC, useEffect} from 'react';
import {Heading, IconSvg, SectionPortal, SvgButton} from '@momentum-xyz/ui-kit';
import {
Heading,
IconSvg,
ScreenSectionsEnum,
SectionedScreenPortal,
SvgButton
} from '@momentum-xyz/ui-kit';
import {observer} from 'mobx-react-lite';
import {useTranslation} from 'react-i18next';

Expand All @@ -11,10 +17,7 @@ import * as styled from './TextChatWidget.styled';
const TextChatWidget: FC = () => {
const {widgetsStore, sessionStore, unityStore} = useStore();
const {unityInstanceStore} = unityStore;
const {
textChatStore
// voiceChatStore
} = widgetsStore;
const {textChatStore} = widgetsStore;
const {streamChat} = textChatStore;

const {t} = useTranslation();
Expand All @@ -28,35 +31,30 @@ const TextChatWidget: FC = () => {
}, [sessionStore.user, sessionStore.userId, streamChat, unityStore.worldId]);

return (
<SectionPortal section="right-top">
{/* FIXME: Design discussion in order to avoid relation to VoiceChatStore */}
<styled.Modal
// style={{marginRight: voiceChatStore.dialog.isOpen ? '310px' : '20px'}}
>
<styled.Container>
<styled.Header>
<styled.HeaderItemsGroup>
<IconSvg name="groupChat" size="medium" />
<Heading label={t('labels.chat')} transform="uppercase" type="h2" />
</styled.HeaderItemsGroup>
<styled.HeaderItemsGroup>
<SvgButton iconName="close" size="medium" onClick={textChatStore.dialog.close} />
</styled.HeaderItemsGroup>
</styled.Header>

<styled.Body>
{streamChat.client && streamChat.currentChannel && (
<TextChat
client={streamChat.client}
channel={streamChat.currentChannel}
onFocus={() => unityInstanceStore.changeKeyboardControl(false)}
onBlur={() => unityInstanceStore.changeKeyboardControl(true)}
/>
)}
</styled.Body>
</styled.Container>
</styled.Modal>
</SectionPortal>
<SectionedScreenPortal section={ScreenSectionsEnum.TOP_RIGHT}>
<styled.Container>
<styled.Header>
<styled.HeaderItemsGroup>
<IconSvg name="groupChat" size="medium" />
<Heading label={t('labels.chat')} transform="uppercase" type="h2" />
</styled.HeaderItemsGroup>
<styled.HeaderItemsGroup>
<SvgButton iconName="close" size="medium" onClick={textChatStore.dialog.close} />
</styled.HeaderItemsGroup>
</styled.Header>

<styled.Body>
{streamChat.client && streamChat.currentChannel && (
<TextChat
client={streamChat.client}
channel={streamChat.currentChannel}
onFocus={() => unityInstanceStore.changeKeyboardControl(false)}
onBlur={() => unityInstanceStore.changeKeyboardControl(true)}
/>
)}
</styled.Body>
</styled.Container>
</SectionedScreenPortal>
);
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ export const Container = styled.div`
border-radius: 10px;
overflow: hidden;
width: 280px;
height: 584px;
max-height: 584px;
height: 100%;
display: flex;
flex-direction: column;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import React, {FC, useCallback, useEffect} from 'react';
import {observer} from 'mobx-react-lite';
import {useTranslation} from 'react-i18next';
import {Heading, IconSvg, SectionPortal, SvgButton} from '@momentum-xyz/ui-kit';
import {
Heading,
IconSvg,
ScreenSectionsEnum,
SectionedScreenPortal,
SvgButton
} from '@momentum-xyz/ui-kit';

import {useStore} from 'shared/hooks';

Expand All @@ -28,24 +34,22 @@ const VoiceChatWidget: FC = () => {
}, [agoraStore, agoraVoiceChatStore.hasJoined, voiceChatStore.dialog]);

return (
<SectionPortal section="right-top">
<styled.Modal>
<styled.Container>
<styled.Header>
<styled.HeaderItemsGroup>
<IconSvg name="microphoneOn" size="medium" />
<Heading label={t('labels.voiceChat')} transform="uppercase" type="h2" />
</styled.HeaderItemsGroup>
<styled.HeaderItemsGroup>
<SvgButton iconName="close" size="medium" onClick={handleClose} />
</styled.HeaderItemsGroup>
</styled.Header>
<styled.Body>
<VoiceChatPanel />
</styled.Body>
</styled.Container>
</styled.Modal>
</SectionPortal>
<SectionedScreenPortal section={ScreenSectionsEnum.TOP_RIGHT}>
<styled.Container>
<styled.Header>
<styled.HeaderItemsGroup>
<IconSvg name="microphoneOn" size="medium" />
<Heading label={t('labels.voiceChat')} transform="uppercase" type="h2" />
</styled.HeaderItemsGroup>
<styled.HeaderItemsGroup>
<SvgButton iconName="close" size="medium" onClick={handleClose} />
</styled.HeaderItemsGroup>
</styled.Header>
<styled.Body>
<VoiceChatPanel />
</styled.Body>
</styled.Container>
</SectionedScreenPortal>
);
};

Expand Down
9 changes: 9 additions & 0 deletions packages/app/src/static/styles/main.scss
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,15 @@ body {
z-index: 1;
}

#sectioned-screen-container {
width: 100%;
height: calc(100% - 50px);
position: absolute;
top: 0;
left: 0;
z-index: 1;
}

.noScrollIndicator {
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
Expand Down
1 change: 0 additions & 1 deletion packages/app/src/static/styles/variables.css
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,6 @@
--overlay-z-index: 40;
--base-z-index: 1;


/* MUSIC PLAYER */
--player-default-color: rgba(255, 255, 255, 0.5);

Expand Down
3 changes: 3 additions & 0 deletions packages/ui-kit/src/atoms/Portal/Portal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ const Portal: FC<PropsInterface> = ({children, className, parentId, maximized})
if (maximized) {
// set flex-grow to 1 to make it fill the parent
domContainer.current.style.flexGrow = '1';
} else {
// set flex-grow to 0 to make it not fill the parent
domContainer.current.style.flexGrow = '0';
}

useEffect(() => {
Expand Down
Loading

0 comments on commit 82bf81d

Please sign in to comment.