feat(generic-content): add sidekick type
Adds 'sidekick' type of generic content. This type allows rendering generic content in the sidekick panel, in addition to the existing 'main' type, which renders generic content over the presentation area. Each generic sidekick content set through plugins is automatically associated with a button in the navigation bar to toggle its panel.
This commit is contained in:
parent
95d823951f
commit
a1dee317f3
@ -0,0 +1,45 @@
|
||||
import React from 'react';
|
||||
import Styled from './styles';
|
||||
import { GenericSidekickContentProps } from '../types';
|
||||
import GenericContentItem from '../generic-content-item/component';
|
||||
import { layoutDispatch } from '/imports/ui/components/layout/context';
|
||||
import { PANELS, ACTIONS } from '/imports/ui/components/layout/enums';
|
||||
|
||||
|
||||
const GenericSidekickContent: React.FC<GenericSidekickContentProps> = ({
|
||||
renderFunction,
|
||||
genericContentId,
|
||||
genericContentLabel,
|
||||
}) => {
|
||||
const layoutContextDispatch = layoutDispatch();
|
||||
return (
|
||||
<Styled.Container
|
||||
data-test={genericContentId}
|
||||
>
|
||||
<Styled.Header
|
||||
leftButtonProps={{
|
||||
onClick: () => {
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_SIDEBAR_CONTENT_IS_OPEN,
|
||||
value: false,
|
||||
});
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_SIDEBAR_CONTENT_PANEL,
|
||||
value: PANELS.NONE,
|
||||
});
|
||||
},
|
||||
'data-test': `hide_${genericContentId}`,
|
||||
'aria-label': genericContentLabel,
|
||||
label: genericContentLabel,
|
||||
}}
|
||||
customRightButton={null}
|
||||
/>
|
||||
<GenericContentItem
|
||||
key={genericContentId}
|
||||
renderFunction={renderFunction}
|
||||
/>
|
||||
</Styled.Container>
|
||||
);
|
||||
};
|
||||
|
||||
export default GenericSidekickContent;
|
@ -0,0 +1,47 @@
|
||||
import React, { useContext } from 'react';
|
||||
import * as PluginSdk from 'bigbluebutton-html-plugin-sdk';
|
||||
import { GenericContentType } from 'bigbluebutton-html-plugin-sdk/dist/cjs/extensible-areas/generic-content/enums';
|
||||
import { PluginsContext } from '/imports/ui/components/components-data/plugin-context/context';
|
||||
import { PANELS } from '/imports/ui/components/layout/enums';
|
||||
import GenericSidekickContent from './component';
|
||||
import { GenericSidekickContentContainerProps } from '../types';
|
||||
import logger from '/imports/startup/client/logger';
|
||||
|
||||
|
||||
const GenericSidekickContentContainer: React.FC<GenericSidekickContentContainerProps> = (props: GenericSidekickContentContainerProps) => {
|
||||
const { genericSidekickContentId } = props;
|
||||
const genericSidekickContentIdIsolated = genericSidekickContentId.replace(PANELS.GENERIC_SIDEKICK_CONTENT, "");
|
||||
|
||||
const {
|
||||
pluginsExtensibleAreasAggregatedState,
|
||||
} = useContext(PluginsContext);
|
||||
let genericSidekickContentExtensibleArea = [] as PluginSdk.GenericSidekickContent[];
|
||||
|
||||
if (pluginsExtensibleAreasAggregatedState.genericContents) {
|
||||
const genericMainContent = pluginsExtensibleAreasAggregatedState.genericContents.filter((g) => g.type === GenericContentType.SIDEKICK_CONTENT) as PluginSdk.GenericSidekickContent[];
|
||||
genericSidekickContentExtensibleArea = [...genericMainContent];
|
||||
}
|
||||
|
||||
if (genericSidekickContentExtensibleArea.length === 0) return null;
|
||||
const pickedGenericSidekickContent = genericSidekickContentExtensibleArea.filter((gsc) => gsc.id === genericSidekickContentIdIsolated)[0];
|
||||
|
||||
if (!pickedGenericSidekickContent) {
|
||||
logger.error({
|
||||
logCode: 'generic_sidekick_content_not_found',
|
||||
extraInfo: {
|
||||
genericSidekickContentId,
|
||||
genericSidekickContentIdIsolated,
|
||||
},
|
||||
}, `Generic sidekick content with id ${genericSidekickContentIdIsolated} not found`);
|
||||
}
|
||||
|
||||
return (
|
||||
<GenericSidekickContent
|
||||
genericContentId={pickedGenericSidekickContent.id}
|
||||
renderFunction={pickedGenericSidekickContent.contentFunction}
|
||||
genericContentLabel={pickedGenericSidekickContent.name}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default GenericSidekickContentContainer;
|
@ -0,0 +1,33 @@
|
||||
import styled from 'styled-components';
|
||||
import {
|
||||
mdPaddingX,
|
||||
} from '/imports/ui/stylesheets/styled-components/general';
|
||||
import { colorWhite } from '/imports/ui/stylesheets/styled-components/palette';
|
||||
import { smallOnly } from '/imports/ui/stylesheets/styled-components/breakpoints';
|
||||
import CommonHeader from '/imports/ui/components/common/control-header/component';
|
||||
|
||||
const Container = styled.div`
|
||||
background-color: ${colorWhite};
|
||||
padding: ${mdPaddingX};
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
height: 100%;
|
||||
|
||||
@media ${smallOnly} {
|
||||
transform: none !important;
|
||||
&.no-padding {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const Header = styled(CommonHeader)`
|
||||
padding-bottom: .2rem;
|
||||
`;
|
||||
|
||||
export default {
|
||||
Container,
|
||||
Header,
|
||||
};
|
@ -12,3 +12,12 @@ export interface GenericMainContentProps {
|
||||
renderFunctionComponents: GenericMainContent[];
|
||||
}
|
||||
|
||||
export interface GenericSidekickContentContainerProps {
|
||||
genericSidekickContentId: string;
|
||||
}
|
||||
|
||||
export interface GenericSidekickContentProps {
|
||||
genericContentId: string;
|
||||
renderFunction: (element: HTMLElement) => void;
|
||||
genericContentLabel: string;
|
||||
}
|
||||
|
@ -133,5 +133,6 @@ export const PANELS = {
|
||||
SHARED_NOTES: 'shared-notes',
|
||||
TIMER: 'timer',
|
||||
WAITING_USERS: 'waiting-users',
|
||||
GENERIC_SIDEKICK_CONTENT: 'generic-sidekick-content',
|
||||
NONE: 'none',
|
||||
};
|
||||
|
@ -1,6 +1,7 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Resizable from 're-resizable';
|
||||
import { GenericContentType } from 'bigbluebutton-html-plugin-sdk/dist/cjs/extensible-areas/generic-content/enums';
|
||||
import { ACTIONS, PANELS } from '../layout/enums';
|
||||
import ChatContainer from '/imports/ui/components/chat/chat-graphql/component';
|
||||
import NotesContainer from '/imports/ui/components/notes/component';
|
||||
@ -11,6 +12,7 @@ import GuestUsersManagementPanel from '/imports/ui/components/waiting-users/wait
|
||||
import Styled from './styles';
|
||||
import ErrorBoundary from '/imports/ui/components/common/error-boundary/component';
|
||||
import FallbackView from '/imports/ui/components/common/fallback-errors/fallback-view/component';
|
||||
import GenericSidekickContentContainer from '/imports/ui/components/generic-content/generic-sidekick-content/container';
|
||||
|
||||
const propTypes = {
|
||||
top: PropTypes.number.isRequired,
|
||||
@ -157,6 +159,11 @@ const SidebarContent = (props) => {
|
||||
/>
|
||||
</Styled.Poll>
|
||||
)}
|
||||
{sidebarContentPanel.includes(PANELS.GENERIC_SIDEKICK_CONTENT) && (
|
||||
<GenericSidekickContentContainer
|
||||
genericSidekickContentId={sidebarContentPanel}
|
||||
/>
|
||||
)}
|
||||
</Resizable>
|
||||
);
|
||||
};
|
||||
|
@ -10,6 +10,8 @@ import UserPollsContainer from './user-polls/container';
|
||||
import BreakoutRoomContainer from './breakout-room/container';
|
||||
import { isChatEnabled } from '/imports/ui/services/features';
|
||||
import UserTitleContainer from '../user-list-graphql/user-participants-title/component';
|
||||
import { GenericSidekickContent } from 'bigbluebutton-html-plugin-sdk';
|
||||
import GenericSidekickContentNavButtonContainer from './generic-sidekick-content-button/container';
|
||||
|
||||
const propTypes = {
|
||||
currentUser: PropTypes.shape({
|
||||
@ -46,9 +48,10 @@ class UserContent extends PureComponent {
|
||||
{isTimerActive && <TimerContainer isModerator={currentUser?.role === ROLE_MODERATOR} />}
|
||||
{currentUser?.role === ROLE_MODERATOR ? (
|
||||
<GuestPanelOpenerContainer />
|
||||
) : null}
|
||||
) : null}
|
||||
<UserPollsContainer isPresenter={currentUser?.presenter} />
|
||||
<BreakoutRoomContainer />
|
||||
<GenericSidekickContentNavButtonContainer />
|
||||
<UserTitleContainer />
|
||||
<UserListParticipants compact={compact} />
|
||||
</Styled.Content>
|
||||
|
@ -0,0 +1,59 @@
|
||||
import React, { useContext } from 'react';
|
||||
import PluginSdk from 'bigbluebutton-html-plugin-sdk';
|
||||
import { GenericContentType } from 'bigbluebutton-html-plugin-sdk/dist/cjs/extensible-areas/generic-content/enums';
|
||||
import Icon from '/imports/ui/components/common/icon/component';
|
||||
import Styled from './styles';
|
||||
import { ACTIONS, PANELS } from '/imports/ui/components/layout/enums';
|
||||
import { PluginsContext } from '/imports/ui/components/components-data/plugin-context/context';
|
||||
|
||||
const GenericSidekickContentNavButton = ({ sidebarContentPanel, layoutContextDispatch }) => {
|
||||
const { pluginsExtensibleAreasAggregatedState } = useContext(PluginsContext);
|
||||
let genericSidekickContentExtensibleArea = [];
|
||||
|
||||
if (pluginsExtensibleAreasAggregatedState.genericContents) {
|
||||
const genericMainContent = pluginsExtensibleAreasAggregatedState.genericContents.filter((g) => g.type === GenericContentType.SIDEKICK_CONTENT);
|
||||
genericSidekickContentExtensibleArea = [...genericMainContent];
|
||||
}
|
||||
|
||||
const genericSidekickContentId = (id) => PANELS.GENERIC_SIDEKICK_CONTENT + id;
|
||||
|
||||
if (genericSidekickContentExtensibleArea.length === 0) return null;
|
||||
const genericSidekickContentNavButtons = genericSidekickContentExtensibleArea.map((gsc) => (
|
||||
<Styled.Section>
|
||||
<Styled.Container>
|
||||
<Styled.SmallTitle>
|
||||
{gsc.section}
|
||||
</Styled.SmallTitle>
|
||||
</Styled.Container>
|
||||
<Styled.ScrollableList>
|
||||
<Styled.List>
|
||||
<Styled.ListItem
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
active={sidebarContentPanel === genericSidekickContentId(gsc.id)}
|
||||
onClick={() => {
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_SIDEBAR_CONTENT_IS_OPEN,
|
||||
value: sidebarContentPanel !== genericSidekickContentId(gsc.id),
|
||||
});
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_SIDEBAR_CONTENT_PANEL,
|
||||
value: sidebarContentPanel === genericSidekickContentId(gsc.id)
|
||||
? PANELS.NONE
|
||||
: genericSidekickContentId(gsc.id),
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Icon iconName={gsc.buttonIcon} />
|
||||
<span>
|
||||
{gsc.name}
|
||||
</span>
|
||||
</Styled.ListItem>
|
||||
</Styled.List>
|
||||
</Styled.ScrollableList>
|
||||
</Styled.Section>
|
||||
));
|
||||
return <>{genericSidekickContentNavButtons}</>
|
||||
}
|
||||
|
||||
export default GenericSidekickContentNavButton;
|
@ -0,0 +1,22 @@
|
||||
import React from 'react';
|
||||
import GenericSidekickContentNavButton from './component';
|
||||
import { layoutSelectInput, layoutDispatch } from '/imports/ui/components/layout/context';
|
||||
|
||||
const GenericSidekickContentNavButtonContainer = (props) => {
|
||||
const sidebarContent = layoutSelectInput((i) => i.sidebarContent);
|
||||
const { sidebarContentPanel } = sidebarContent;
|
||||
const layoutContextDispatch = layoutDispatch();
|
||||
|
||||
return (
|
||||
<GenericSidekickContentNavButton {
|
||||
...{
|
||||
layoutContextDispatch,
|
||||
sidebarContentPanel,
|
||||
...props,
|
||||
}
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default GenericSidekickContentNavButtonContainer;
|
@ -0,0 +1,27 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
import Styled from '/imports/ui/components/user-list/styles';
|
||||
import StyledContent from '/imports/ui/components/user-list/user-list-content/styles';
|
||||
|
||||
const ListItem = styled(StyledContent.ListItem)`
|
||||
i{ left: 4px; }
|
||||
`;
|
||||
|
||||
const Section = styled(Styled.Messages)``;
|
||||
|
||||
const Container = styled(StyledContent.Container)``;
|
||||
|
||||
const SmallTitle = styled(Styled.SmallTitle)``;
|
||||
|
||||
const ScrollableList = styled(StyledContent.ScrollableList)``;
|
||||
|
||||
const List = styled(StyledContent.List)``;
|
||||
|
||||
export default {
|
||||
ListItem,
|
||||
Section,
|
||||
Container,
|
||||
SmallTitle,
|
||||
ScrollableList,
|
||||
List,
|
||||
};
|
Loading…
Reference in New Issue
Block a user