Compare commits
2 Commits
5719743b57
...
fdefdbd12e
| Author | SHA1 | Date | |
|---|---|---|---|
| fdefdbd12e | |||
| c474596339 |
@@ -4,7 +4,10 @@
|
|||||||
import * as styled from 'styled-components';
|
import * as styled from 'styled-components';
|
||||||
import {theme} from 'components/shared/theme';
|
import {theme} from 'components/shared/theme';
|
||||||
|
|
||||||
const {typography: {fontSizeL}, colors} = theme;
|
const {
|
||||||
|
typography: {fontSizeL},
|
||||||
|
colors
|
||||||
|
} = theme;
|
||||||
|
|
||||||
export const IconButton = styled.default.button`
|
export const IconButton = styled.default.button`
|
||||||
border: none;
|
border: none;
|
||||||
|
|||||||
@@ -4,7 +4,11 @@
|
|||||||
import * as styled from 'styled-components';
|
import * as styled from 'styled-components';
|
||||||
import {theme, Theme} from 'components/shared/theme';
|
import {theme, Theme} from 'components/shared/theme';
|
||||||
|
|
||||||
const {spacing, typography: {primaryFontSize}, colors} = theme;
|
const {
|
||||||
|
spacing,
|
||||||
|
typography: {primaryFontSize},
|
||||||
|
colors
|
||||||
|
} = theme;
|
||||||
const paddings = Theme.paddings;
|
const paddings = Theme.paddings;
|
||||||
|
|
||||||
export const RadioGroup = styled.default.div`
|
export const RadioGroup = styled.default.div`
|
||||||
|
|||||||
@@ -15,7 +15,17 @@ import {Capabilities} from 'components/create-token-components';
|
|||||||
import Theme from 'theme';
|
import Theme from 'theme';
|
||||||
|
|
||||||
const forkChannelOption = ['force', 'additive'];
|
const forkChannelOption = ['force', 'additive'];
|
||||||
const ForkChannelForm = ({setFormData, setIsValid, channelList, alias}: {setFormData: (data: any) => void; setIsValid: (isValid: boolean) => void; channelList: any[]; alias: string}): React.JSX.Element => {
|
const ForkChannelForm = ({
|
||||||
|
setFormData,
|
||||||
|
setIsValid,
|
||||||
|
channelList,
|
||||||
|
alias
|
||||||
|
}: {
|
||||||
|
setFormData: (data: any) => void;
|
||||||
|
setIsValid: (isValid: boolean) => void;
|
||||||
|
channelList: any[];
|
||||||
|
alias: string;
|
||||||
|
}): React.JSX.Element => {
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [tags, setTags] = useState<string[]>([]);
|
const [tags, setTags] = useState<string[]>([]);
|
||||||
const [forkOptions, setForkOptions] = useState<string[]>([]);
|
const [forkOptions, setForkOptions] = useState<string[]>([]);
|
||||||
|
|||||||
@@ -19,7 +19,15 @@ const keepStreamsTooltipMessage = 'Keeps the removed streams alive';
|
|||||||
const destroyRequired = 'destroy-required';
|
const destroyRequired = 'destroy-required';
|
||||||
const destroyRequiredTooltipMessage = 'Returns an error if destroying of a stream fails';
|
const destroyRequiredTooltipMessage = 'Returns an error if destroying of a stream fails';
|
||||||
const defaultReason = 'portal:killed';
|
const defaultReason = 'portal:killed';
|
||||||
const KillChannelForm = ({setFormData, setIsValid, alias}: {setFormData: (data: any) => void; setIsValid: (isValid: boolean) => void; alias: string}): React.JSX.Element => {
|
const KillChannelForm = ({
|
||||||
|
setFormData,
|
||||||
|
setIsValid,
|
||||||
|
alias
|
||||||
|
}: {
|
||||||
|
setFormData: (data: any) => void;
|
||||||
|
setIsValid: (isValid: boolean) => void;
|
||||||
|
alias: string;
|
||||||
|
}): React.JSX.Element => {
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [enteredAlias, setEnteredAlias] = useState('');
|
const [enteredAlias, setEnteredAlias] = useState('');
|
||||||
const [reason, setReason] = useState(defaultReason);
|
const [reason, setReason] = useState(defaultReason);
|
||||||
|
|||||||
@@ -50,7 +50,11 @@ export const Capabilities = ({
|
|||||||
allowMultiple={allowMultiple}
|
allowMultiple={allowMultiple}
|
||||||
label={label}
|
label={label}
|
||||||
labelColor={labelColor}
|
labelColor={labelColor}
|
||||||
labelIcon={capabilitiesSetTitle !== CapabilitiesType.Quality ? <NewTabLink link={documentationLinks.supportedStreamCapabilities} icon={faQuestionCircle} iconColor={iconColor} /> : undefined}
|
labelIcon={
|
||||||
|
capabilitiesSetTitle !== CapabilitiesType.Quality ? (
|
||||||
|
<NewTabLink link={documentationLinks.supportedStreamCapabilities} icon={faQuestionCircle} iconColor={iconColor} />
|
||||||
|
) : undefined
|
||||||
|
}
|
||||||
data={capabilitiesSet}
|
data={capabilitiesSet}
|
||||||
selectedItems={selectedItems}
|
selectedItems={selectedItems}
|
||||||
setSelectedItems={setSelectedItems}
|
setSelectedItems={setSelectedItems}
|
||||||
@@ -59,4 +63,4 @@ export const Capabilities = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Capabilities;
|
export default Capabilities;
|
||||||
|
|||||||
@@ -5,4 +5,4 @@ export {default as Capabilities} from './capabilities';
|
|||||||
export {default as ValidityTimeComponent} from './validity-time';
|
export {default as ValidityTimeComponent} from './validity-time';
|
||||||
export * from './validity-time';
|
export * from './validity-time';
|
||||||
export {default as LabelIconTooltip} from './label-icon-tooltip';
|
export {default as LabelIconTooltip} from './label-icon-tooltip';
|
||||||
export * from './styles';
|
export * from './styles';
|
||||||
|
|||||||
@@ -14,20 +14,13 @@ interface CapabilitiesProps {
|
|||||||
setSelectedItems: (items: IAdvancedSelectItem[]) => void;
|
setSelectedItems: (items: IAdvancedSelectItem[]) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Capabilities: React.FC<CapabilitiesProps> = ({
|
export const Capabilities: React.FC<CapabilitiesProps> = ({label, labelColor, iconColor, capabilitiesSetTitle, selectedItems, setSelectedItems}) => {
|
||||||
label,
|
|
||||||
labelColor,
|
|
||||||
iconColor,
|
|
||||||
capabilitiesSetTitle,
|
|
||||||
selectedItems,
|
|
||||||
setSelectedItems
|
|
||||||
}) => {
|
|
||||||
// Mock capabilities data - in a real app this would come from constants/capabilities
|
// Mock capabilities data - in a real app this would come from constants/capabilities
|
||||||
const mockCapabilities: IAdvancedSelectItem[] = [
|
const mockCapabilities: IAdvancedSelectItem[] = [
|
||||||
{ value: 'streaming', text: 'Streaming' },
|
{value: 'streaming', text: 'Streaming'},
|
||||||
{ value: 'recording', text: 'Recording' },
|
{value: 'recording', text: 'Recording'},
|
||||||
{ value: 'analytics', text: 'Analytics' },
|
{value: 'analytics', text: 'Analytics'},
|
||||||
{ value: 'transcoding', text: 'Transcoding' }
|
{value: 'transcoding', text: 'Transcoding'}
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -41,4 +34,4 @@ export const Capabilities: React.FC<CapabilitiesProps> = ({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -14,13 +14,9 @@ interface ILabelIconTooltip {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const LabelIconTooltip = ({message, icon, position}: ILabelIconTooltip): React.JSX.Element => (
|
export const LabelIconTooltip = ({message, icon, position}: ILabelIconTooltip): React.JSX.Element => (
|
||||||
<Tooltip
|
<Tooltip position={position || Position.Right} message={message} width={300}>
|
||||||
position={position || Position.Right}
|
|
||||||
message={message}
|
|
||||||
width={300}
|
|
||||||
>
|
|
||||||
<FontAwesomeIcon icon={icon || faQuestionCircle} />
|
<FontAwesomeIcon icon={icon || faQuestionCircle} />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
|
|
||||||
export default LabelIconTooltip;
|
export default LabelIconTooltip;
|
||||||
|
|||||||
@@ -7,10 +7,7 @@ import {theme} from 'components/shared/theme';
|
|||||||
import {RadioGroup} from 'components/buttons/radio-button/style';
|
import {RadioGroup} from 'components/buttons/radio-button/style';
|
||||||
// import Input from 'components/forms/Input';
|
// import Input from 'components/forms/Input';
|
||||||
|
|
||||||
const {
|
const {spacing, colors} = theme;
|
||||||
spacing,
|
|
||||||
colors
|
|
||||||
} = theme;
|
|
||||||
|
|
||||||
export const CreateTokenContainer = styled.default.div`
|
export const CreateTokenContainer = styled.default.div`
|
||||||
margin: 0.5rem 0 0;
|
margin: 0.5rem 0 0;
|
||||||
@@ -57,4 +54,4 @@ export const RowsWrapper = styled.default.div`
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -48,13 +48,9 @@ export const ValidityTimeComponent = ({currentValue, onChange}: IValidityTimeCom
|
|||||||
<Fragment>
|
<Fragment>
|
||||||
<Label text="Valid For:" color="white" />
|
<Label text="Valid For:" color="white" />
|
||||||
<RadioButtonContainer className="testId-expirationTime">
|
<RadioButtonContainer className="testId-expirationTime">
|
||||||
<RadioButtonGroup
|
<RadioButtonGroup items={Object.values(validityTimeOptions)} currentValue={currentValue} handleOnChange={onChange} />
|
||||||
items={Object.values(validityTimeOptions)}
|
|
||||||
currentValue={currentValue}
|
|
||||||
handleOnChange={onChange}
|
|
||||||
/>
|
|
||||||
</RadioButtonContainer>
|
</RadioButtonContainer>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
);
|
);
|
||||||
|
|
||||||
export default ValidityTimeComponent;
|
export default ValidityTimeComponent;
|
||||||
|
|||||||
@@ -7,13 +7,7 @@ import {Moment} from 'moment';
|
|||||||
import {AppStore} from 'store';
|
import {AppStore} from 'store';
|
||||||
import {getTimezoneAbbreviation, isoTimeFormat} from 'utility/date';
|
import {getTimezoneAbbreviation, isoTimeFormat} from 'utility/date';
|
||||||
|
|
||||||
export const DateComponent = ({
|
export const DateComponent = ({date, className}: {date: Moment; className?: string}): JSX.Element => {
|
||||||
date,
|
|
||||||
className
|
|
||||||
}: {
|
|
||||||
date: Moment;
|
|
||||||
className?: string;
|
|
||||||
}): JSX.Element => {
|
|
||||||
const preferredTimeFormat = useSelector((state: AppStore) => state.preferredTimeFormat.timeFormat);
|
const preferredTimeFormat = useSelector((state: AppStore) => state.preferredTimeFormat.timeFormat);
|
||||||
const isUTC = preferredTimeFormat === 'utc';
|
const isUTC = preferredTimeFormat === 'utc';
|
||||||
const utcDate = date.isValid() ? date.format(`${isoTimeFormat} UTC`) : '';
|
const utcDate = date.isValid() ? date.format(`${isoTimeFormat} UTC`) : '';
|
||||||
@@ -39,13 +33,8 @@ export const DateComponent = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<p
|
<p onMouseEnter={onMouseEnter} onMouseOut={onMouseOut} onBlur={onBlur} className={className}>
|
||||||
onMouseEnter={onMouseEnter}
|
|
||||||
onMouseOut={onMouseOut}
|
|
||||||
onBlur={onBlur}
|
|
||||||
className={className}
|
|
||||||
>
|
|
||||||
{currentDate}
|
{currentDate}
|
||||||
</p>
|
</p>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -48,12 +48,12 @@ export const Dropdown = (props: IDropdown): React.JSX.Element => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const isOutOfUpperView = menuItem.offsetTop < dropdownMenu.scrollTop;
|
const isOutOfUpperView = menuItem.offsetTop < dropdownMenu.scrollTop;
|
||||||
const isOutOfLowerView = (menuItem.offsetTop + menuItem.clientHeight) > (dropdownMenu.scrollTop + dropdownMenu.clientHeight);
|
const isOutOfLowerView = menuItem.offsetTop + menuItem.clientHeight > dropdownMenu.scrollTop + dropdownMenu.clientHeight;
|
||||||
|
|
||||||
if (isOutOfUpperView) {
|
if (isOutOfUpperView) {
|
||||||
dropdownMenu.scrollTop = menuItem.offsetTop;
|
dropdownMenu.scrollTop = menuItem.offsetTop;
|
||||||
} else if (isOutOfLowerView) {
|
} else if (isOutOfLowerView) {
|
||||||
dropdownMenu.scrollTop = (menuItem.offsetTop + menuItem.clientHeight) - dropdownMenu.clientHeight;
|
dropdownMenu.scrollTop = menuItem.offsetTop + menuItem.clientHeight - dropdownMenu.clientHeight;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -153,8 +153,8 @@ export const Dropdown = (props: IDropdown): React.JSX.Element => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const generateMenuOptions = () => {
|
const generateMenuOptions = () => {
|
||||||
return filteredItems.length
|
return filteredItems.length ? (
|
||||||
? filteredItems.slice(0, maxNumOfItemsShown).map((item, index) => {
|
filteredItems.slice(0, maxNumOfItemsShown).map((item, index) => {
|
||||||
return (
|
return (
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
ref={ref => {
|
ref={ref => {
|
||||||
@@ -162,17 +162,14 @@ export const Dropdown = (props: IDropdown): React.JSX.Element => {
|
|||||||
}}
|
}}
|
||||||
selected={selectedIndex === index}
|
selected={selectedIndex === index}
|
||||||
key={`dropdown-menu-item-${index}`}
|
key={`dropdown-menu-item-${index}`}
|
||||||
onClick={() => selectItem(index)}
|
onClick={() => selectItem(index)}>
|
||||||
>
|
|
||||||
{(item as any)[itemKey]}
|
{(item as any)[itemKey]}
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
: (
|
) : (
|
||||||
<DropdownMenuItem disabled={true}>
|
<DropdownMenuItem disabled={true}>No results found</DropdownMenuItem>
|
||||||
No results found
|
);
|
||||||
</DropdownMenuItem>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleInput = (event: React.ChangeEvent<HTMLInputElement>) => {
|
const handleInput = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
@@ -188,10 +185,7 @@ export const Dropdown = (props: IDropdown): React.JSX.Element => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownContainer>
|
<DropdownContainer>
|
||||||
<Label
|
<Label htmlFor="autocomplete" text={label} />
|
||||||
htmlFor="autocomplete"
|
|
||||||
text={label}
|
|
||||||
/>
|
|
||||||
<DropdownInput
|
<DropdownInput
|
||||||
onKeyDown={handleOnKeyDown}
|
onKeyDown={handleOnKeyDown}
|
||||||
showMenu={showDropdownMenu}
|
showMenu={showDropdownMenu}
|
||||||
@@ -209,4 +203,4 @@ export const Dropdown = (props: IDropdown): React.JSX.Element => {
|
|||||||
)}
|
)}
|
||||||
</DropdownContainer>
|
</DropdownContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -14,11 +14,13 @@ export const DropdownContainer = styled.default.div`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
export const DropdownInput = styled.default(Input)<{showMenu?: boolean}>`
|
export const DropdownInput = styled.default(Input)<{showMenu?: boolean}>`
|
||||||
${({showMenu}) => showMenu && styled.css`
|
${({showMenu}) =>
|
||||||
border-bottom-left-radius: 0px;
|
showMenu &&
|
||||||
border-bottom-right-radius: 0px;
|
styled.css`
|
||||||
border-bottom-width: 0;
|
border-bottom-left-radius: 0px;
|
||||||
`}
|
border-bottom-right-radius: 0px;
|
||||||
|
border-bottom-width: 0;
|
||||||
|
`}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const DropdownMenu = styled.default.div`
|
export const DropdownMenu = styled.default.div`
|
||||||
@@ -56,4 +58,4 @@ export const DropdownMenuItem = styled.default.div<{
|
|||||||
cursor: ${disabled ? 'text' : 'pointer'};
|
cursor: ${disabled ? 'text' : 'pointer'};
|
||||||
}
|
}
|
||||||
`}
|
`}
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -44,31 +44,29 @@ const StyledCheckbox = styled.div<{checked?: boolean}>`
|
|||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 1rem;
|
width: 1rem;
|
||||||
height: 1rem;
|
height: 1rem;
|
||||||
background: ${({checked}) => checked ? colors.red : colors.transparent};
|
background: ${({checked}) => (checked ? colors.red : colors.transparent)};
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
transition: all 150ms;
|
transition: all 150ms;
|
||||||
outline: none;
|
outline: none;
|
||||||
border: 1px solid ${({checked}) => checked ? 'none' : colors.gray400};
|
border: 1px solid ${({checked}) => (checked ? 'none' : colors.gray400)};
|
||||||
|
|
||||||
${Icon} {
|
${Icon} {
|
||||||
visibility: ${({checked}) => checked ? 'visible' : 'hidden'}
|
visibility: ${({checked}) => (checked ? 'visible' : 'hidden')};
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
const Checkbox = (
|
const Checkbox = (props: {
|
||||||
props: {
|
value: string;
|
||||||
value: string;
|
id?: string;
|
||||||
id?: string;
|
checked?: boolean;
|
||||||
checked?: boolean;
|
onChange: (event: ChangeEvent<HTMLInputElement> | MouseEvent<HTMLDivElement>) => void;
|
||||||
onChange: (event: ChangeEvent<HTMLInputElement> | MouseEvent<HTMLDivElement>) => void;
|
label?: string;
|
||||||
label?: string;
|
}): React.JSX.Element => {
|
||||||
}
|
|
||||||
): React.JSX.Element => {
|
|
||||||
const {checked, onChange, label, id} = props;
|
const {checked, onChange, label, id} = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
<CheckboxContainer>
|
<CheckboxContainer>
|
||||||
<HiddenCheckbox {...props}/>
|
<HiddenCheckbox {...props} />
|
||||||
<StyledCheckbox onClick={onChange} checked={checked}>
|
<StyledCheckbox onClick={onChange} checked={checked}>
|
||||||
<Icon viewBox="0 0 24 24">
|
<Icon viewBox="0 0 24 24">
|
||||||
<polyline points="20 6 9 17 4 12" />
|
<polyline points="20 6 9 17 4 12" />
|
||||||
@@ -80,4 +78,4 @@ const Checkbox = (
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Checkbox;
|
export default Checkbox;
|
||||||
|
|||||||
@@ -61,28 +61,14 @@ interface CheckboxProps {
|
|||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Checkbox: React.FC<CheckboxProps> = ({
|
const Checkbox: React.FC<CheckboxProps> = ({id, checked, onChange, label, value, disabled = false, className}) => {
|
||||||
id,
|
|
||||||
checked,
|
|
||||||
onChange,
|
|
||||||
label,
|
|
||||||
value,
|
|
||||||
disabled = false,
|
|
||||||
className
|
|
||||||
}) => {
|
|
||||||
return (
|
return (
|
||||||
<CheckboxContainer className={className}>
|
<CheckboxContainer className={className}>
|
||||||
<HiddenCheckbox
|
<HiddenCheckbox id={id} checked={checked} onChange={onChange} value={value} disabled={disabled} />
|
||||||
id={id}
|
|
||||||
checked={checked}
|
|
||||||
onChange={onChange}
|
|
||||||
value={value}
|
|
||||||
disabled={disabled}
|
|
||||||
/>
|
|
||||||
<StyledCheckbox checked={checked} />
|
<StyledCheckbox checked={checked} />
|
||||||
{label && <Label htmlFor={id}>{label}</Label>}
|
{label && <Label htmlFor={id}>{label}</Label>}
|
||||||
</CheckboxContainer>
|
</CheckboxContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Checkbox;
|
export default Checkbox;
|
||||||
|
|||||||
@@ -40,11 +40,7 @@ export const SearchInputWrapper = styled.default.div`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const Search = ({
|
export const Search = ({search, defaultValue = '', minLengthForSearch = 2}: ISearchInput): JSX.Element => {
|
||||||
search,
|
|
||||||
defaultValue = '',
|
|
||||||
minLengthForSearch = 2
|
|
||||||
}: ISearchInput): JSX.Element => {
|
|
||||||
const [currentSearchTerm, setCurrentSearchTerm] = useState<string>(defaultValue);
|
const [currentSearchTerm, setCurrentSearchTerm] = useState<string>(defaultValue);
|
||||||
const onChange = (event: ChangeEvent<HTMLInputElement>): void => {
|
const onChange = (event: ChangeEvent<HTMLInputElement>): void => {
|
||||||
setCurrentSearchTerm(event.target.value);
|
setCurrentSearchTerm(event.target.value);
|
||||||
@@ -74,4 +70,4 @@ export const Search = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Search;
|
export default Search;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/**
|
/**
|
||||||
* Copyright 2024 Phenix Real Time Solutions, Inc. Confidential and Proprietary. All Rights Reserved.
|
* Copyright 2024 Phenix Real Time Solutions, Inc. Confidential and Proprietary. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
export * from './indicators';
|
export * from './indicators';
|
||||||
|
|||||||
@@ -5,7 +5,23 @@ import {JSX} from 'react';
|
|||||||
import {LoadingWheel as Loader} from 'components/loaders';
|
import {LoadingWheel as Loader} from 'components/loaders';
|
||||||
import {SingleStreamSymbol, OfflineSymbol, MultipleStreamSymbol, Indicator} from './style';
|
import {SingleStreamSymbol, OfflineSymbol, MultipleStreamSymbol, Indicator} from './style';
|
||||||
|
|
||||||
export const OfflineIndicator = (): JSX.Element => <Indicator><OfflineSymbol /></Indicator>;
|
export const OfflineIndicator = (): JSX.Element => (
|
||||||
export const SingleStreamIndicator = (): JSX.Element => <Indicator className="single-stream-indicator"><SingleStreamSymbol className="testId-singleStreamIndicator" /></Indicator>;
|
<Indicator>
|
||||||
export const MultiStreamIndicator = (): JSX.Element => <Indicator className="multi-stream-indicator"><MultipleStreamSymbol className="testId-multiStreamIndicator" /></Indicator>;
|
<OfflineSymbol />
|
||||||
export const LoadingIndicator = (): JSX.Element => <Indicator><Loader size="medium" /></Indicator>;
|
</Indicator>
|
||||||
|
);
|
||||||
|
export const SingleStreamIndicator = (): JSX.Element => (
|
||||||
|
<Indicator className="single-stream-indicator">
|
||||||
|
<SingleStreamSymbol className="testId-singleStreamIndicator" />
|
||||||
|
</Indicator>
|
||||||
|
);
|
||||||
|
export const MultiStreamIndicator = (): JSX.Element => (
|
||||||
|
<Indicator className="multi-stream-indicator">
|
||||||
|
<MultipleStreamSymbol className="testId-multiStreamIndicator" />
|
||||||
|
</Indicator>
|
||||||
|
);
|
||||||
|
export const LoadingIndicator = (): JSX.Element => (
|
||||||
|
<Indicator>
|
||||||
|
<Loader size="medium" />
|
||||||
|
</Indicator>
|
||||||
|
);
|
||||||
|
|||||||
@@ -40,4 +40,4 @@ export const MultipleStreamSymbol = styled.default(SingleStreamSymbol)`
|
|||||||
height: 1rem;
|
height: 1rem;
|
||||||
margin-left: -2px;
|
margin-left: -2px;
|
||||||
box-shadow: 9px 0 0 0 #08BD0B;
|
box-shadow: 9px 0 0 0 #08BD0B;
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -14,11 +14,7 @@ interface IPublishingStateIndicator {
|
|||||||
idKey: string;
|
idKey: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const PublishingStateIndicator = ({
|
const PublishingStateIndicator = ({row, publishingStateKey, idKey}: IPublishingStateIndicator): JSX.Element => {
|
||||||
row,
|
|
||||||
publishingStateKey,
|
|
||||||
idKey
|
|
||||||
}: IPublishingStateIndicator): JSX.Element => {
|
|
||||||
const id = row[idKey];
|
const id = row[idKey];
|
||||||
const publishingState = useSelector((state: AppStore) => publishingStateKey && state[publishingStateKey as keyof AppStore]?.publishingState);
|
const publishingState = useSelector((state: AppStore) => publishingStateKey && state[publishingStateKey as keyof AppStore]?.publishingState);
|
||||||
const rowPublishingState = publishingState.find((record: Record<string, any>) => record[idKey] === id);
|
const rowPublishingState = publishingState.find((record: Record<string, any>) => record[idKey] === id);
|
||||||
@@ -38,4 +34,4 @@ const PublishingStateIndicator = ({
|
|||||||
return <SingleStreamIndicator />;
|
return <SingleStreamIndicator />;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default PublishingStateIndicator;
|
export default PublishingStateIndicator;
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
export * from '../forms/label';
|
export * from '../forms/label';
|
||||||
|
|||||||
@@ -70,7 +70,11 @@ const Modal = (props: IModal): React.JSX.Element => {
|
|||||||
<CloseButton onClick={close} />
|
<CloseButton onClick={close} />
|
||||||
{children}
|
{children}
|
||||||
<ModalButtonsContaier>
|
<ModalButtonsContaier>
|
||||||
{submitButton.onClick && <ConfirmButton {...submitButton} onClick={submitButton.onClick}>{submitButton.label}</ConfirmButton>}
|
{submitButton.onClick && (
|
||||||
|
<ConfirmButton {...submitButton} onClick={submitButton.onClick}>
|
||||||
|
{submitButton.label}
|
||||||
|
</ConfirmButton>
|
||||||
|
)}
|
||||||
{!props.cancelButton ||
|
{!props.cancelButton ||
|
||||||
(!cancelButton.disabled && (
|
(!cancelButton.disabled && (
|
||||||
<ConfirmButton className="testId-cancel" {...cancelButton} onClick={handleCancel}>
|
<ConfirmButton className="testId-cancel" {...cancelButton} onClick={handleCancel}>
|
||||||
|
|||||||
@@ -2,4 +2,4 @@
|
|||||||
* Copyright 2024 Phenix Real Time Solutions, Inc. Confidential and Proprietary. All Rights Reserved.
|
* Copyright 2024 Phenix Real Time Solutions, Inc. Confidential and Proprietary. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
export * from './pagination';
|
export * from './pagination';
|
||||||
export {default as Pagination} from './pagination';
|
export {default as Pagination} from './pagination';
|
||||||
|
|||||||
@@ -2,12 +2,7 @@
|
|||||||
* Copyright 2024 Phenix Real Time Solutions, Inc. Confidential and Proprietary. All Rights Reserved.
|
* Copyright 2024 Phenix Real Time Solutions, Inc. Confidential and Proprietary. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
import {Fragment} from 'react';
|
import {Fragment} from 'react';
|
||||||
import {
|
import {PaginationWrapper, ItemRange, PaginationContainer, PageButton} from './style';
|
||||||
PaginationWrapper,
|
|
||||||
ItemRange,
|
|
||||||
PaginationContainer,
|
|
||||||
PageButton
|
|
||||||
} from './style';
|
|
||||||
|
|
||||||
interface IPagination {
|
interface IPagination {
|
||||||
currentPageNumber: number;
|
currentPageNumber: number;
|
||||||
@@ -27,8 +22,8 @@ export const Pagination = (props: IPagination): JSX.Element => {
|
|||||||
setCurrentPage(page);
|
setCurrentPage(page);
|
||||||
};
|
};
|
||||||
|
|
||||||
const minimumItemBoundPerPage = ((currentPageNumber - 1) * itemsPerPage) + 1;
|
const minimumItemBoundPerPage = (currentPageNumber - 1) * itemsPerPage + 1;
|
||||||
const maximumItemBoundPerPage = (currentPageNumber) * itemsPerPage;
|
const maximumItemBoundPerPage = currentPageNumber * itemsPerPage;
|
||||||
const generateButtons = (bounds = 1): JSX.Element[] => {
|
const generateButtons = (bounds = 1): JSX.Element[] => {
|
||||||
const buttonToShow = maxNumberOfButtonsToShow > totalNumberOfPages ? totalNumberOfPages : maxNumberOfButtonsToShow;
|
const buttonToShow = maxNumberOfButtonsToShow > totalNumberOfPages ? totalNumberOfPages : maxNumberOfButtonsToShow;
|
||||||
const buttons = [];
|
const buttons = [];
|
||||||
@@ -36,7 +31,7 @@ export const Pagination = (props: IPagination): JSX.Element => {
|
|||||||
|
|
||||||
if (currentPageNumber <= bounds) {
|
if (currentPageNumber <= bounds) {
|
||||||
count = 1;
|
count = 1;
|
||||||
} else if ((currentPageNumber + bounds) > totalNumberOfPages) {
|
} else if (currentPageNumber + bounds > totalNumberOfPages) {
|
||||||
count = currentPageNumber - (buttonToShow - (totalNumberOfPages - currentPageNumber + 1));
|
count = currentPageNumber - (buttonToShow - (totalNumberOfPages - currentPageNumber + 1));
|
||||||
} else {
|
} else {
|
||||||
count = currentPageNumber - bounds;
|
count = currentPageNumber - bounds;
|
||||||
@@ -44,10 +39,7 @@ export const Pagination = (props: IPagination): JSX.Element => {
|
|||||||
|
|
||||||
for (let y = 0; y < buttonToShow; y++) {
|
for (let y = 0; y < buttonToShow; y++) {
|
||||||
buttons.push(
|
buttons.push(
|
||||||
<PageButton
|
<PageButton key={`page-button-${y}`} onClick={() => setPage(count + y)} active={currentPageNumber === count + y}>
|
||||||
key={`page-button-${y}`}
|
|
||||||
onClick={() => setPage(count + y)}
|
|
||||||
active={currentPageNumber === count + y}>
|
|
||||||
{count + y}
|
{count + y}
|
||||||
</PageButton>
|
</PageButton>
|
||||||
);
|
);
|
||||||
@@ -60,28 +52,29 @@ export const Pagination = (props: IPagination): JSX.Element => {
|
|||||||
<PaginationContainer className="pagination-container">
|
<PaginationContainer className="pagination-container">
|
||||||
{numberOfItems ? (
|
{numberOfItems ? (
|
||||||
<PaginationWrapper>
|
<PaginationWrapper>
|
||||||
{(currentPageNumber >= LowerBoundLimit) && (
|
{currentPageNumber >= LowerBoundLimit && (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
{currentPageNumber > 2 && totalNumberOfPages !== 3 && <PageButton onClick={() => setPage(1)}>1</PageButton>}
|
{currentPageNumber > 2 && totalNumberOfPages !== 3 && <PageButton onClick={() => setPage(1)}>1</PageButton>}
|
||||||
{(currentPageNumber > LowerBoundLimit) && currentPageNumber !== 3 && <p>...</p>}
|
{currentPageNumber > LowerBoundLimit && currentPageNumber !== 3 && <p>...</p>}
|
||||||
</Fragment>
|
</Fragment>
|
||||||
)}
|
)}
|
||||||
{generateButtons()}
|
{generateButtons()}
|
||||||
{(currentPageNumber <= totalNumberOfPages - higherBoundLimit) && (
|
{currentPageNumber <= totalNumberOfPages - higherBoundLimit && (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
{(currentPageNumber < totalNumberOfPages - higherBoundLimit) && currentPageNumber + 2 !== totalNumberOfPages && <p>...</p>}
|
{currentPageNumber < totalNumberOfPages - higherBoundLimit && currentPageNumber + 2 !== totalNumberOfPages && <p>...</p>}
|
||||||
{currentPageNumber + 1 !== totalNumberOfPages && totalNumberOfPages !== 3 && <PageButton onClick={() => setPage(totalNumberOfPages)}>
|
{currentPageNumber + 1 !== totalNumberOfPages && totalNumberOfPages !== 3 && (
|
||||||
{totalNumberOfPages}
|
<PageButton onClick={() => setPage(totalNumberOfPages)}>{totalNumberOfPages}</PageButton>
|
||||||
</PageButton>}
|
)}
|
||||||
</Fragment>
|
</Fragment>
|
||||||
)}
|
)}
|
||||||
</PaginationWrapper>
|
</PaginationWrapper>
|
||||||
) : null}
|
) : null}
|
||||||
<ItemRange>
|
<ItemRange>
|
||||||
{numberOfItems > 0 ? minimumItemBoundPerPage : 0} - {maximumItemBoundPerPage > numberOfItems ? numberOfItems : maximumItemBoundPerPage} of {numberOfItems} {itemText ? itemText : `channels`}
|
{numberOfItems > 0 ? minimumItemBoundPerPage : 0} - {maximumItemBoundPerPage > numberOfItems ? numberOfItems : maximumItemBoundPerPage} of{' '}
|
||||||
|
{numberOfItems} {itemText ? itemText : `channels`}
|
||||||
</ItemRange>
|
</ItemRange>
|
||||||
</PaginationContainer>
|
</PaginationContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Pagination;
|
export default Pagination;
|
||||||
|
|||||||
@@ -4,12 +4,7 @@
|
|||||||
import * as styled from 'styled-components';
|
import * as styled from 'styled-components';
|
||||||
import {theme, paddings} from 'components/shared/theme';
|
import {theme, paddings} from 'components/shared/theme';
|
||||||
|
|
||||||
const {
|
const {colors, fontSizeS, spacing, primaryThemeColor} = theme;
|
||||||
colors,
|
|
||||||
fontSizeS,
|
|
||||||
spacing,
|
|
||||||
primaryThemeColor
|
|
||||||
} = theme;
|
|
||||||
|
|
||||||
export const PaginationContainer = styled.default.div`
|
export const PaginationContainer = styled.default.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -32,7 +27,7 @@ export const ItemRange = styled.default.div`
|
|||||||
color: ${colors.lightBlue};
|
color: ${colors.lightBlue};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const PageButton = styled.default.button <{active?: boolean}>`
|
export const PageButton = styled.default.button<{active?: boolean}>`
|
||||||
margin: 0 ${paddings.xsmall} ${paddings.xsmall} 0;
|
margin: 0 ${paddings.xsmall} ${paddings.xsmall} 0;
|
||||||
font-size: ${fontSizeS};
|
font-size: ${fontSizeS};
|
||||||
border: 1px solid ${primaryThemeColor};
|
border: 1px solid ${primaryThemeColor};
|
||||||
@@ -40,6 +35,6 @@ export const PageButton = styled.default.button <{active?: boolean}>`
|
|||||||
height: 28px;
|
height: 28px;
|
||||||
width: 36px;
|
width: 36px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
background-color: ${({active}) => active ? primaryThemeColor : 'transparent'};
|
background-color: ${({active}) => (active ? primaryThemeColor : 'transparent')};
|
||||||
color: ${({active}) => active ? colors.white : primaryThemeColor};
|
color: ${({active}) => (active ? colors.white : primaryThemeColor)};
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -3,4 +3,4 @@
|
|||||||
*/
|
*/
|
||||||
export {default as RestrictedTextWithLabel} from './restricted-text-with-label';
|
export {default as RestrictedTextWithLabel} from './restricted-text-with-label';
|
||||||
export {default as RestrictedText} from './restricted-text';
|
export {default as RestrictedText} from './restricted-text';
|
||||||
export * from './restricted-text';
|
export * from './restricted-text';
|
||||||
|
|||||||
@@ -14,16 +14,12 @@ interface IRestrictedTextWithLabel {
|
|||||||
labelIcon?: JSX.Element;
|
labelIcon?: JSX.Element;
|
||||||
}
|
}
|
||||||
|
|
||||||
const RestrictedTextWithLabel = ({
|
const RestrictedTextWithLabel = ({label, text, labelIcon, isLink = false, linkClassName = ''}: IRestrictedTextWithLabel): JSX.Element => {
|
||||||
label,
|
|
||||||
text,
|
|
||||||
labelIcon,
|
|
||||||
isLink = false,
|
|
||||||
linkClassName = ''
|
|
||||||
}: IRestrictedTextWithLabel): JSX.Element => {
|
|
||||||
return (
|
return (
|
||||||
<RestrictedRowWrapper>
|
<RestrictedRowWrapper>
|
||||||
<RestrictedRowLabel>{label} {labelIcon}</RestrictedRowLabel>
|
<RestrictedRowLabel>
|
||||||
|
{label} {labelIcon}
|
||||||
|
</RestrictedRowLabel>
|
||||||
<RestrictedRowText>
|
<RestrictedRowText>
|
||||||
<RestrictedText text={text} isLink={isLink} linkClassName={linkClassName} />
|
<RestrictedText text={text} isLink={isLink} linkClassName={linkClassName} />
|
||||||
</RestrictedRowText>
|
</RestrictedRowText>
|
||||||
@@ -31,4 +27,4 @@ const RestrictedTextWithLabel = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default RestrictedTextWithLabel;
|
export default RestrictedTextWithLabel;
|
||||||
|
|||||||
@@ -66,21 +66,12 @@ export const RestrictedText = ({
|
|||||||
const handleShowValueChange = () => setShowValue(!showValue);
|
const handleShowValueChange = () => setShowValue(!showValue);
|
||||||
const CurrentTextView = (): JSX.Element => {
|
const CurrentTextView = (): JSX.Element => {
|
||||||
if (isLink && !linkValue) {
|
if (isLink && !linkValue) {
|
||||||
return (
|
return <NewTabLink link={text} className={linkClassName} text={text} />;
|
||||||
<NewTabLink
|
|
||||||
link={text}
|
|
||||||
className={linkClassName}
|
|
||||||
text={text}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isLink && linkValue) {
|
if (isLink && linkValue) {
|
||||||
return (
|
return (
|
||||||
<Link
|
<Link to={linkValue} className={linkClassName}>
|
||||||
to={linkValue}
|
|
||||||
className={linkClassName}
|
|
||||||
>
|
|
||||||
{text}
|
{text}
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
@@ -104,12 +95,10 @@ export const RestrictedText = ({
|
|||||||
className={`${isLink ? 'testId-permalink' : ''} testId-viewDetails`}
|
className={`${isLink ? 'testId-permalink' : ''} testId-viewDetails`}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{hasCopyOption && (
|
{hasCopyOption && <CopyIconButton text={text} displayText={false} className="testId-copyDetails" />}
|
||||||
<CopyIconButton text={text} displayText={false} className="testId-copyDetails" />
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</RestrictedDiv>
|
</RestrictedDiv>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default RestrictedText;
|
export default RestrictedText;
|
||||||
|
|||||||
@@ -5,11 +5,7 @@ import * as styled from 'styled-components';
|
|||||||
|
|
||||||
import Theme from 'theme';
|
import Theme from 'theme';
|
||||||
|
|
||||||
const {
|
const {colors, typography, spacing} = Theme;
|
||||||
colors,
|
|
||||||
typography,
|
|
||||||
spacing,
|
|
||||||
} = Theme;
|
|
||||||
|
|
||||||
export const RestrictedDiv = styled.default.div`
|
export const RestrictedDiv = styled.default.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -93,4 +89,4 @@ export const RestrictedRowText = styled.default.div`
|
|||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
font-size: ${typography.fontSizeS};
|
font-size: ${typography.fontSizeS};
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/**
|
/**
|
||||||
* Copyright 2024 Phenix Real Time Solutions, Inc. Confidential and Proprietary. All Rights Reserved.
|
* Copyright 2024 Phenix Real Time Solutions, Inc. Confidential and Proprietary. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
export {default as TableScreenHeader} from './table-screen-header';
|
export {default as TableScreenHeader} from './table-screen-header';
|
||||||
|
|||||||
@@ -34,4 +34,4 @@ export const HeaderTitle = styled.default.div`
|
|||||||
p {
|
p {
|
||||||
color: ${colors.gray200};
|
color: ${colors.gray200};
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -2,11 +2,7 @@
|
|||||||
* Copyright 2024 Phenix Real Time Solutions, Inc. Confidential and Proprietary. All Rights Reserved.
|
* Copyright 2024 Phenix Real Time Solutions, Inc. Confidential and Proprietary. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
import {JSX} from 'react';
|
import {JSX} from 'react';
|
||||||
import {
|
import {ITableWithLoadMoreHeader, ITableWithPaginationHeader, TableHeaderKey} from 'interfaces/tableProps';
|
||||||
ITableWithLoadMoreHeader,
|
|
||||||
ITableWithPaginationHeader,
|
|
||||||
TableHeaderKey
|
|
||||||
} from 'interfaces/tableProps';
|
|
||||||
import {ScreenHeader, ScreenHeaderControls, HeaderControlWrapper, HeaderTitle} from './style';
|
import {ScreenHeader, ScreenHeaderControls, HeaderControlWrapper, HeaderTitle} from './style';
|
||||||
|
|
||||||
type ScreenHeaderProps = ITableWithLoadMoreHeader | ITableWithPaginationHeader;
|
type ScreenHeaderProps = ITableWithLoadMoreHeader | ITableWithPaginationHeader;
|
||||||
@@ -18,12 +14,7 @@ export interface ITableScreenHeader {
|
|||||||
renderControl: (screenHeader: ScreenHeaderProps, key: TableHeaderKey) => JSX.Element | null;
|
renderControl: (screenHeader: ScreenHeaderProps, key: TableHeaderKey) => JSX.Element | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TableScreenHeader = ({
|
export const TableScreenHeader = ({title, subtitle = '', screenHeader, renderControl}: ITableScreenHeader): JSX.Element => {
|
||||||
title,
|
|
||||||
subtitle = '',
|
|
||||||
screenHeader,
|
|
||||||
renderControl
|
|
||||||
}: ITableScreenHeader): JSX.Element => {
|
|
||||||
return (
|
return (
|
||||||
<ScreenHeader className="table-header">
|
<ScreenHeader className="table-header">
|
||||||
<HeaderTitle>
|
<HeaderTitle>
|
||||||
@@ -32,17 +23,13 @@ export const TableScreenHeader = ({
|
|||||||
</HeaderTitle>
|
</HeaderTitle>
|
||||||
<ScreenHeaderControls>
|
<ScreenHeaderControls>
|
||||||
{Object.keys(screenHeader).map(key => {
|
{Object.keys(screenHeader).map(key => {
|
||||||
const headerControl = screenHeader[key].render
|
const headerControl = screenHeader[key].render ? screenHeader[key].render(key) : renderControl(screenHeader, key as TableHeaderKey);
|
||||||
? screenHeader[key].render(key)
|
|
||||||
: renderControl(screenHeader, key as TableHeaderKey);
|
|
||||||
|
|
||||||
return headerControl ? (
|
return headerControl ? <HeaderControlWrapper key={key}>{headerControl}</HeaderControlWrapper> : null;
|
||||||
<HeaderControlWrapper key={key}>{headerControl}</HeaderControlWrapper>
|
|
||||||
) : null;
|
|
||||||
})}
|
})}
|
||||||
</ScreenHeaderControls>
|
</ScreenHeaderControls>
|
||||||
</ScreenHeader>
|
</ScreenHeader>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default TableScreenHeader;
|
export default TableScreenHeader;
|
||||||
|
|||||||
@@ -3,4 +3,4 @@
|
|||||||
*/
|
*/
|
||||||
export * from 'components/table';
|
export * from 'components/table';
|
||||||
export {default as TableWithPagination} from './table-with-pagination';
|
export {default as TableWithPagination} from './table-with-pagination';
|
||||||
export {default as Pagination} from '../pagination/pagination';
|
export {default as Pagination} from '../pagination/pagination';
|
||||||
|
|||||||
@@ -3,20 +3,13 @@
|
|||||||
*/
|
*/
|
||||||
import {useEffect, useState} from 'react';
|
import {useEffect, useState} from 'react';
|
||||||
|
|
||||||
import {
|
import {DataRowType, Table, TableHeaderKey, ITable, ITableWithPaginationHeader, ITableSort} from 'components/table';
|
||||||
DataRowType,
|
|
||||||
Table,
|
|
||||||
TableHeaderKey,
|
|
||||||
ITable,
|
|
||||||
ITableWithPaginationHeader,
|
|
||||||
ITableSort
|
|
||||||
} from 'components/table';
|
|
||||||
import {isEqual} from 'utility/validators';
|
import {isEqual} from 'utility/validators';
|
||||||
import SearchInput from 'components/forms/SearchInput';
|
import SearchInput from 'components/forms/SearchInput';
|
||||||
import {compare} from 'utility/sort';
|
import {compare} from 'utility/sort';
|
||||||
import {TableScreenHeader} from 'components/table-screen-header/table-screen-header';
|
import {TableScreenHeader} from 'components/table-screen-header/table-screen-header';
|
||||||
import {AddButton} from 'components/buttons/icon-buttons';
|
import {AddButton} from 'components/buttons/icon-buttons';
|
||||||
import {Pagination} from 'components/pagination/pagination' ;
|
import {Pagination} from 'components/pagination/pagination';
|
||||||
import {Select} from 'components/ui/select';
|
import {Select} from 'components/ui/select';
|
||||||
|
|
||||||
import {useSort, useSearch} from 'utility/custom-hooks';
|
import {useSort, useSearch} from 'utility/custom-hooks';
|
||||||
@@ -128,9 +121,7 @@ const TableWithPagination = ({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const start = (currentPageNumber - 1) * rowsCount;
|
const start = (currentPageNumber - 1) * rowsCount;
|
||||||
const sortedData = sortData
|
const sortedData = sortData ? filteredData.sort((a, b) => compare(a, b, sortData.sortDirection, sortData.sortColumn)) : filteredData;
|
||||||
? filteredData.sort((a, b) => compare(a, b, sortData.sortDirection, sortData.sortColumn))
|
|
||||||
: filteredData;
|
|
||||||
const newCurrentData = sortedData.slice(start, start + rowsCount);
|
const newCurrentData = sortedData.slice(start, start + rowsCount);
|
||||||
|
|
||||||
if (!isEqual(newCurrentData, currentData)) {
|
if (!isEqual(newCurrentData, currentData)) {
|
||||||
@@ -177,20 +168,8 @@ const TableWithPagination = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<TableScreenHeader
|
<TableScreenHeader title={title} screenHeader={screenHeader} renderControl={renderControl} />
|
||||||
title={title}
|
<Table columns={columns} data={currentData} sortColumn={sortColumn} sortDirection={sortDirection} style={style} sort={sort} errorMessage={errorMessage} />
|
||||||
screenHeader={screenHeader}
|
|
||||||
renderControl={renderControl}
|
|
||||||
/>
|
|
||||||
<Table
|
|
||||||
columns={columns}
|
|
||||||
data={currentData}
|
|
||||||
sortColumn={sortColumn}
|
|
||||||
sortDirection={sortDirection}
|
|
||||||
style={style}
|
|
||||||
sort={sort}
|
|
||||||
errorMessage={errorMessage}
|
|
||||||
/>
|
|
||||||
<Pagination
|
<Pagination
|
||||||
currentPageNumber={currentPageNumber}
|
currentPageNumber={currentPageNumber}
|
||||||
setCurrentPage={setCurrentPageNumber}
|
setCurrentPage={setCurrentPageNumber}
|
||||||
@@ -202,4 +181,4 @@ const TableWithPagination = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default TableWithPagination;
|
export default TableWithPagination;
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export const Table = styled.default.table`
|
|||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
table-layout: fixed;
|
table-layout: fixed;
|
||||||
background-color: ${Theme.blackWithOpacity};
|
background-color: ${Theme.colors.gray900};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const Thead = styled.default.thead`
|
export const Thead = styled.default.thead`
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
export * from '../constants/capabilities';
|
export * from '../constants/capabilities';
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
export * from '../constants/error-messages';
|
export * from '../constants/error-messages';
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
export * from '../constants/links';
|
export * from '../constants/links';
|
||||||
|
|||||||
@@ -1,209 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright 2024 Phenix Real Time Solutions, Inc. Confidential and Proprietary. All Rights Reserved.
|
|
||||||
*/
|
|
||||||
import {useState, useEffect, useRef} from 'react';
|
|
||||||
|
|
||||||
import {IChannel} from 'interfaces';
|
|
||||||
import {Label} from 'components/label';
|
|
||||||
|
|
||||||
import {DropdownContainer, DropdownInput, DropdownMenu, DropdownMenuItem} from './style';
|
|
||||||
|
|
||||||
const keys = ['ArrowDown', 'ArrowUp', 'Enter', 'Escape'];
|
|
||||||
|
|
||||||
interface IDropdown {
|
|
||||||
itemKey: string;
|
|
||||||
searchTerm?: string;
|
|
||||||
onSelect: (key: string) => void;
|
|
||||||
items: IChannel[];
|
|
||||||
label: string;
|
|
||||||
name: string;
|
|
||||||
className?: string;
|
|
||||||
}
|
|
||||||
const maxNumOfItemsShown = 30;
|
|
||||||
|
|
||||||
export const Dropdown = (props: IDropdown): JSX.Element => {
|
|
||||||
const {itemKey, searchTerm, onSelect, items, label, name, className} = props;
|
|
||||||
const [showDropdownMenu, setShowDropdownMenu] = useState(false);
|
|
||||||
const [input, setInput] = useState(searchTerm || '');
|
|
||||||
const [filteredItems, setFilteredItems] = useState(items);
|
|
||||||
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
||||||
const allItemsRef = useRef([]);
|
|
||||||
const parentElementRef = useRef(null);
|
|
||||||
const scrollSelectedItemInView = index => {
|
|
||||||
if (!parentElementRef.current) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const dropdownMenu = parentElementRef.current;
|
|
||||||
const menuItems = allItemsRef.current;
|
|
||||||
|
|
||||||
if (!menuItems) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const menuItem = menuItems[index];
|
|
||||||
|
|
||||||
if (!menuItem) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const isOutOfUpperView = menuItem.offsetTop < dropdownMenu.scrollTop;
|
|
||||||
const isOutOfLowerView = (menuItem.offsetTop + menuItem.clientHeight) > (dropdownMenu.scrollTop + dropdownMenu.clientHeight);
|
|
||||||
|
|
||||||
if (isOutOfUpperView) {
|
|
||||||
dropdownMenu.scrollTop = menuItem.offsetTop;
|
|
||||||
} else if (isOutOfLowerView) {
|
|
||||||
dropdownMenu.scrollTop = (menuItem.offsetTop + menuItem.clientHeight) - dropdownMenu.clientHeight;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const setSelectInput = value => {
|
|
||||||
setInput(value);
|
|
||||||
onSelect(value);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleClickOutside = event => {
|
|
||||||
if (!parentElementRef.current || parentElementRef.current.contains(event.target)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!filteredItems.length) {
|
|
||||||
setSelectInput('');
|
|
||||||
}
|
|
||||||
|
|
||||||
setShowDropdownMenu(false);
|
|
||||||
onSelect(input);
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
scrollSelectedItemInView(selectedIndex);
|
|
||||||
}, [selectedIndex]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
document.addEventListener('click', handleClickOutside, true);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
document.removeEventListener('click', handleClickOutside, true);
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const filteredItems = items.filter(item => {
|
|
||||||
if (item[itemKey].toLowerCase().indexOf(input.toLowerCase()) > -1) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (filteredItems.length > 0) {
|
|
||||||
setSelectedIndex(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
setFilteredItems(filteredItems);
|
|
||||||
}, [input, items, itemKey]);
|
|
||||||
|
|
||||||
const selectItemInFocusBy = offset => {
|
|
||||||
const lastIndex = filteredItems.length - 1;
|
|
||||||
const nextIndex = selectedIndex + offset;
|
|
||||||
|
|
||||||
if (nextIndex > lastIndex) {
|
|
||||||
setSelectedIndex(0);
|
|
||||||
} else if (nextIndex < 0) {
|
|
||||||
setSelectedIndex(lastIndex);
|
|
||||||
} else {
|
|
||||||
setSelectedIndex(nextIndex);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleOnKeyDown = event => {
|
|
||||||
if (keys.indexOf(event.key) === -1) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const [arrDown, arrUp, enter, escape] = keys;
|
|
||||||
const moves = {
|
|
||||||
[arrDown]: 1,
|
|
||||||
[arrUp]: -1
|
|
||||||
};
|
|
||||||
const move = moves[event.key];
|
|
||||||
|
|
||||||
if (move !== undefined) {
|
|
||||||
event.preventDefault();
|
|
||||||
selectItemInFocusBy(move);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.key === enter) {
|
|
||||||
if (filteredItems[selectedIndex]) {
|
|
||||||
setSelectInput(filteredItems[selectedIndex][itemKey]);
|
|
||||||
setShowDropdownMenu(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.key === escape) {
|
|
||||||
event.preventDefault();
|
|
||||||
setShowDropdownMenu(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const selectItem = (index: number) => {
|
|
||||||
setSelectInput(filteredItems[index][itemKey]);
|
|
||||||
setSelectedIndex(index);
|
|
||||||
setShowDropdownMenu(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const generateMenuOptions = () => {
|
|
||||||
return filteredItems.length
|
|
||||||
? filteredItems.slice(0, maxNumOfItemsShown).map((item, index) => {
|
|
||||||
return (
|
|
||||||
<DropdownMenuItem
|
|
||||||
ref={ref => allItemsRef.current[index] = ref}
|
|
||||||
selected={selectedIndex === index}
|
|
||||||
key={`dropdown-menu-item-${index}`}
|
|
||||||
onClick={() => selectItem(index)}
|
|
||||||
>
|
|
||||||
{item[itemKey]}
|
|
||||||
</DropdownMenuItem>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
: (
|
|
||||||
<DropdownMenuItem disabled={true}>
|
|
||||||
No results found
|
|
||||||
</DropdownMenuItem>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleInput = ({target}) => {
|
|
||||||
setInput(target.value);
|
|
||||||
|
|
||||||
if (!showDropdownMenu) {
|
|
||||||
setShowDropdownMenu(true);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const toggleDropdownMenu = () => setShowDropdownMenu(!showDropdownMenu);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<DropdownContainer>
|
|
||||||
<Label
|
|
||||||
htmlFor="autocomplete"
|
|
||||||
text={label}
|
|
||||||
/>
|
|
||||||
<DropdownInput
|
|
||||||
onKeyDown={handleOnKeyDown}
|
|
||||||
showMenu={showDropdownMenu}
|
|
||||||
autoComplete="off"
|
|
||||||
name={name}
|
|
||||||
onChange={handleInput}
|
|
||||||
value={input}
|
|
||||||
onClick={toggleDropdownMenu}
|
|
||||||
className={className}
|
|
||||||
/>
|
|
||||||
{showDropdownMenu && (
|
|
||||||
<DropdownMenu ref={parentElementRef} className="testId-generatedMenuOptions">
|
|
||||||
{generateMenuOptions()}
|
|
||||||
</DropdownMenu>
|
|
||||||
)}
|
|
||||||
</DropdownContainer>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright 2024 Phenix Real Time Solutions, Inc. Confidential and Proprietary. All Rights Reserved.
|
|
||||||
*/
|
|
||||||
import * as styled from 'styled-components';
|
|
||||||
import Input from 'components/forms/Input';
|
|
||||||
import Theme from 'theme';
|
|
||||||
|
|
||||||
const {spacing, colors, typography, primaryBorderRadius} = Theme;
|
|
||||||
|
|
||||||
export const DropdownContainer = styled.default.div`
|
|
||||||
width: 100%;
|
|
||||||
position: relative;
|
|
||||||
margin: ${spacing.xSmall} 0;
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const DropdownInput = styled.default(Input)<{showMenu?: boolean}>`
|
|
||||||
${({showMenu}) => showMenu && styled.css`
|
|
||||||
border-bottom-left-radius: 0px;
|
|
||||||
border-bottom-right-radius: 0px;
|
|
||||||
border-bottom-width: 0;
|
|
||||||
`}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const DropdownMenu = styled.default.div`
|
|
||||||
width: 100%;
|
|
||||||
overflow-x: hidden;
|
|
||||||
overflow-y: auto;
|
|
||||||
top: calc(100% - 4px);
|
|
||||||
position: absolute;
|
|
||||||
height: auto;
|
|
||||||
max-height: 210px;
|
|
||||||
border: 1px solid ${colors.gray400};
|
|
||||||
border-top-width: 0;
|
|
||||||
z-index: 4;
|
|
||||||
background-color: ${colors.white};
|
|
||||||
border-bottom-left-radius: ${primaryBorderRadius};
|
|
||||||
border-bottom-right-radius: ${primaryBorderRadius};
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const DropdownMenuItem = styled.default.div<{
|
|
||||||
active?: boolean;
|
|
||||||
disabled?: boolean;
|
|
||||||
selected?: boolean;
|
|
||||||
}>`
|
|
||||||
line-height: ${spacing.large};
|
|
||||||
padding: ${spacing.xsmall} ${spacing.medium};
|
|
||||||
font-size: ${typography.fontSizeS};
|
|
||||||
word-wrap: break-word;
|
|
||||||
${({active, selected, disabled}) => styled.css`
|
|
||||||
background-color: ${active || selected ? colors.gray300 : 'transparent'};
|
|
||||||
font-weight: ${selected ? 'bold' : 'normal'};
|
|
||||||
color: ${disabled ? colors.gray500 : colors.gray900}
|
|
||||||
|
|
||||||
:hover{
|
|
||||||
background-color: ${disabled ? colors.white : colors.gray300};
|
|
||||||
cursor: ${disabled ? 'text' : 'pointer'};
|
|
||||||
}
|
|
||||||
`}
|
|
||||||
`;
|
|
||||||
@@ -3,9 +3,8 @@ body {
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
background: #f8f9fa;
|
background: #f8f9fa;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
font-family:
|
||||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
-apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
|
||||||
sans-serif;
|
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright 2024 Phenix Real Time Solutions, Inc. Confidential and Proprietary. All Rights Reserved.
|
|
||||||
*/
|
|
||||||
export * from './indicators';
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright 2024 Phenix Real Time Solutions, Inc. Confidential and Proprietary. All Rights Reserved.
|
|
||||||
*/
|
|
||||||
import React from 'react';
|
|
||||||
import {LoadingWheel as Loader} from 'components';
|
|
||||||
import {SingleStreamSymbol, OfflineSymbol, MultipleStreamSymbol, Indicator} from './style';
|
|
||||||
|
|
||||||
export const OfflineIndicator = (): React.JSX.Element => (
|
|
||||||
<Indicator>
|
|
||||||
<OfflineSymbol />
|
|
||||||
</Indicator>
|
|
||||||
);
|
|
||||||
export const SingleStreamIndicator = (): React.JSX.Element => (
|
|
||||||
<Indicator className="single-stream-indicator">
|
|
||||||
<SingleStreamSymbol className="testId-singleStreamIndicator" />
|
|
||||||
</Indicator>
|
|
||||||
);
|
|
||||||
export const MultiStreamIndicator = (): React.JSX.Element => (
|
|
||||||
<Indicator className="multi-stream-indicator">
|
|
||||||
<MultipleStreamSymbol className="testId-multiStreamIndicator" />
|
|
||||||
</Indicator>
|
|
||||||
);
|
|
||||||
export const LoadingIndicator = (): React.JSX.Element => (
|
|
||||||
<Indicator>
|
|
||||||
<Loader size={'medium'} className="loading-wheel" />
|
|
||||||
</Indicator>
|
|
||||||
);
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright 2024 Phenix Real Time Solutions, Inc. Confidential and Proprietary. All Rights Reserved.
|
|
||||||
*/
|
|
||||||
import * as styled from 'styled-components';
|
|
||||||
|
|
||||||
export const Indicator = styled.default.div`
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
/* Target LoadingWheel component by class name */
|
|
||||||
.loading-wheel {
|
|
||||||
flex: 0;
|
|
||||||
margin: 0;
|
|
||||||
|
|
||||||
& span {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const IndicatorOutline = styled.default.div`
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
border-radius: 50%;
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const OfflineSymbol = styled.default(IndicatorOutline)`
|
|
||||||
border: 3px solid #707070;
|
|
||||||
background-color: transparent;
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const SingleStreamSymbol = styled.default(IndicatorOutline)`
|
|
||||||
background-color: #08BD0B;
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const MultipleStreamSymbol = styled.default(SingleStreamSymbol)`
|
|
||||||
width: 1rem;
|
|
||||||
height: 1rem;
|
|
||||||
margin-left: -2px;
|
|
||||||
box-shadow: 9px 0 0 0 #08BD0B;
|
|
||||||
`;
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright 2024 Phenix Real Time Solutions, Inc. Confidential and Proprietary. All Rights Reserved.
|
|
||||||
*/
|
|
||||||
import {JSX} from 'react';
|
|
||||||
import {useSelector} from 'react-redux';
|
|
||||||
import {AppStore} from 'store';
|
|
||||||
import {SingleStreamIndicator, OfflineIndicator, MultiStreamIndicator, LoadingIndicator} from './indicators';
|
|
||||||
|
|
||||||
interface IPublishingStateIndicator {
|
|
||||||
row: any;
|
|
||||||
publishingStateKey: string;
|
|
||||||
idKey: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const PublishingStateIndicator = ({row, publishingStateKey, idKey}: IPublishingStateIndicator): JSX.Element => {
|
|
||||||
const id = row[idKey] as string;
|
|
||||||
const publishingState = useSelector((state: AppStore) => publishingStateKey && state[publishingStateKey as keyof AppStore]?.publishingState);
|
|
||||||
const rowPublishingState = publishingState.find((record: any) => record[idKey] === id);
|
|
||||||
|
|
||||||
if (!publishingState) {
|
|
||||||
return <LoadingIndicator />;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!rowPublishingState) {
|
|
||||||
return <LoadingIndicator />;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!rowPublishingState.isOnline) {
|
|
||||||
return <OfflineIndicator />;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rowPublishingState.multipleStreams) {
|
|
||||||
return <MultiStreamIndicator />;
|
|
||||||
}
|
|
||||||
|
|
||||||
return <SingleStreamIndicator />;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default PublishingStateIndicator;
|
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import LoggerFactory from './logger/LoggerFactory';
|
import LoggerFactory from './logger/LoggerFactory';
|
||||||
import ILogger from './logger/LoggerInterface';
|
import ILogger from './logger/LoggerInterface';
|
||||||
import PlatformDetectionService from './PlatformDetection.service';
|
import PlatformDetectionService from './PlatformDetection.service';
|
||||||
import { AuthenticationResponse, PhenixWebSocket } from './net/websockets/PhenixWebSocket';
|
import {AuthenticationResponse, PhenixWebSocket} from './net/websockets/PhenixWebSocket';
|
||||||
import { PhenixWebSocketMessage } from './net/websockets/PhenixWebSocketMessage';
|
import {PhenixWebSocketMessage} from './net/websockets/PhenixWebSocketMessage';
|
||||||
import UserStoreService from './user-store';
|
import UserStoreService from './user-store';
|
||||||
|
|
||||||
//TEMPORARY
|
//TEMPORARY
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* Copyright 2024 Phenix Real Time Solutions, Inc. Confidential and Proprietary. All Rights Reserved.
|
* Copyright 2024 Phenix Real Time Solutions, Inc. Confidential and Proprietary. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
import { Channel, Channels } from '@techniker-me/pcast-api';
|
import {Channel, Channels} from '@techniker-me/pcast-api';
|
||||||
import PCastApiService from './PCastApi.service';
|
import PCastApiService from './PCastApi.service';
|
||||||
import LoggerFactory from './logger/LoggerFactory';
|
import LoggerFactory from './logger/LoggerFactory';
|
||||||
|
|
||||||
@@ -48,12 +48,10 @@ export interface KillChannelParams {
|
|||||||
class ChannelService {
|
class ChannelService {
|
||||||
private phenixChannelService: PhenixChannelService | null = null;
|
private phenixChannelService: PhenixChannelService | null = null;
|
||||||
|
|
||||||
|
|
||||||
public async initializeWithPCastApi(channels: Channels) {
|
public async initializeWithPCastApi(channels: Channels) {
|
||||||
try {
|
try {
|
||||||
// Wait for PCastApiService to be initialized
|
// Wait for PCastApiService to be initialized
|
||||||
this.phenixChannelService = channels;
|
this.phenixChannelService = channels;
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Failed to initialize ChannelService', error);
|
logger.error('Failed to initialize ChannelService', error);
|
||||||
}
|
}
|
||||||
@@ -65,11 +63,11 @@ class ChannelService {
|
|||||||
async listChannels(): Promise<Channel[]> {
|
async listChannels(): Promise<Channel[]> {
|
||||||
try {
|
try {
|
||||||
logger.debug('Fetching channel list');
|
logger.debug('Fetching channel list');
|
||||||
|
|
||||||
if (!this.phenixChannelService) {
|
if (!this.phenixChannelService) {
|
||||||
return PCastApiService.channels.list();
|
return PCastApiService.channels.list();
|
||||||
}
|
}
|
||||||
|
|
||||||
return await this.phenixChannelService.list();
|
return await this.phenixChannelService.list();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Failed to list channels', error);
|
logger.error('Failed to list channels', error);
|
||||||
@@ -83,7 +81,7 @@ class ChannelService {
|
|||||||
async createChannel(params: CreateChannelParams): Promise<Channel> {
|
async createChannel(params: CreateChannelParams): Promise<Channel> {
|
||||||
try {
|
try {
|
||||||
logger.debug('Creating channel', params);
|
logger.debug('Creating channel', params);
|
||||||
|
|
||||||
const channelData = {
|
const channelData = {
|
||||||
name: params.name,
|
name: params.name,
|
||||||
alias: params.alias,
|
alias: params.alias,
|
||||||
@@ -95,7 +93,7 @@ class ChannelService {
|
|||||||
if (!this.phenixChannelService) {
|
if (!this.phenixChannelService) {
|
||||||
return PCastApiService.channels.create(name, description, options);
|
return PCastApiService.channels.create(name, description, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
return await this.phenixChannelService.create(channelData);
|
return await this.phenixChannelService.create(channelData);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Failed to create channel', error);
|
logger.error('Failed to create channel', error);
|
||||||
@@ -109,19 +107,19 @@ class ChannelService {
|
|||||||
async updateChannel(params: UpdateChannelParams): Promise<Channel> {
|
async updateChannel(params: UpdateChannelParams): Promise<Channel> {
|
||||||
try {
|
try {
|
||||||
logger.debug('Updating channel', params);
|
logger.debug('Updating channel', params);
|
||||||
|
|
||||||
const updateData = {
|
const updateData = {
|
||||||
...params.name && { name: params.name },
|
...(params.name && {name: params.name}),
|
||||||
...params.alias && { alias: params.alias },
|
...(params.alias && {alias: params.alias}),
|
||||||
...params.description && { description: params.description },
|
...(params.description && {description: params.description}),
|
||||||
...params.tags && { tags: params.tags },
|
...(params.tags && {tags: params.tags}),
|
||||||
...params.capabilities && { capabilities: params.capabilities }
|
...(params.capabilities && {capabilities: params.capabilities})
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!this.phenixChannelService) {
|
if (!this.phenixChannelService) {
|
||||||
return PCastApiService.channels.update(params.channelId, updateData);
|
return PCastApiService.channels.update(params.channelId, updateData);
|
||||||
}
|
}
|
||||||
|
|
||||||
return await this.phenixChannelService.update(params.channelId, updateData);
|
return await this.phenixChannelService.update(params.channelId, updateData);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Failed to update channel', error);
|
logger.error('Failed to update channel', error);
|
||||||
@@ -135,11 +133,11 @@ class ChannelService {
|
|||||||
async deleteChannel(params: DeleteChannelParams): Promise<void> {
|
async deleteChannel(params: DeleteChannelParams): Promise<void> {
|
||||||
try {
|
try {
|
||||||
logger.debug('Deleting channel', params);
|
logger.debug('Deleting channel', params);
|
||||||
|
|
||||||
if (!this.phenixChannelService) {
|
if (!this.phenixChannelService) {
|
||||||
return PCastApiService.channels.delete(params.channelId);
|
return PCastApiService.channels.delete(params.channelId);
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.phenixChannelService.delete(params.channelId);
|
await this.phenixChannelService.delete(params.channelId);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Failed to delete channel', error);
|
logger.error('Failed to delete channel', error);
|
||||||
@@ -153,7 +151,7 @@ class ChannelService {
|
|||||||
async forkChannel(params: ForkChannelParams): Promise<void> {
|
async forkChannel(params: ForkChannelParams): Promise<void> {
|
||||||
try {
|
try {
|
||||||
logger.debug('Forking channel', params);
|
logger.debug('Forking channel', params);
|
||||||
|
|
||||||
const forkData = {
|
const forkData = {
|
||||||
destinationChannelId: params.destinationChannelId,
|
destinationChannelId: params.destinationChannelId,
|
||||||
streamCapabilities: params.streamCapabilities || [],
|
streamCapabilities: params.streamCapabilities || [],
|
||||||
@@ -165,7 +163,7 @@ class ChannelService {
|
|||||||
if (!this.phenixChannelService) {
|
if (!this.phenixChannelService) {
|
||||||
return PCastApiService.channels.fork(params.sourceChannelId, forkData);
|
return PCastApiService.channels.fork(params.sourceChannelId, forkData);
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.phenixChannelService.fork(params.sourceChannelId, forkData);
|
await this.phenixChannelService.fork(params.sourceChannelId, forkData);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Failed to fork channel', error);
|
logger.error('Failed to fork channel', error);
|
||||||
@@ -179,7 +177,7 @@ class ChannelService {
|
|||||||
async killChannel(params: KillChannelParams): Promise<void> {
|
async killChannel(params: KillChannelParams): Promise<void> {
|
||||||
try {
|
try {
|
||||||
logger.debug('Killing channel', params);
|
logger.debug('Killing channel', params);
|
||||||
|
|
||||||
const killData = {
|
const killData = {
|
||||||
reason: params.reason || 'portal:killed',
|
reason: params.reason || 'portal:killed',
|
||||||
options: params.options || []
|
options: params.options || []
|
||||||
@@ -188,7 +186,7 @@ class ChannelService {
|
|||||||
if (!this.phenixChannelService) {
|
if (!this.phenixChannelService) {
|
||||||
return PCastApiService.channels.kill(params.channelId, killData);
|
return PCastApiService.channels.kill(params.channelId, killData);
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.phenixChannelService.kill(params.channelId, killData);
|
await this.phenixChannelService.kill(params.channelId, killData);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Failed to kill channel', error);
|
logger.error('Failed to kill channel', error);
|
||||||
@@ -201,12 +199,12 @@ class ChannelService {
|
|||||||
*/
|
*/
|
||||||
async getChannel(channelId: string): Promise<Channel | null> {
|
async getChannel(channelId: string): Promise<Channel | null> {
|
||||||
try {
|
try {
|
||||||
logger.debug('Getting channel', { channelId });
|
logger.debug('Getting channel', {channelId});
|
||||||
|
|
||||||
if (!this.phenixChannelService) {
|
if (!this.phenixChannelService) {
|
||||||
return PCastApiService.channels.get(channelId);
|
return PCastApiService.channels.get(channelId);
|
||||||
}
|
}
|
||||||
|
|
||||||
return await this.phenixChannelService.get(channelId);
|
return await this.phenixChannelService.get(channelId);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Failed to get channel', error);
|
logger.error('Failed to get channel', error);
|
||||||
@@ -219,12 +217,12 @@ class ChannelService {
|
|||||||
*/
|
*/
|
||||||
async getPublisherCount(channelId: string): Promise<number> {
|
async getPublisherCount(channelId: string): Promise<number> {
|
||||||
try {
|
try {
|
||||||
logger.debug('Getting publisher count', { channelId });
|
logger.debug('Getting publisher count', {channelId});
|
||||||
|
|
||||||
if (!this.phenixChannelService) {
|
if (!this.phenixChannelService) {
|
||||||
return PCastApiService.channels.getPublisherCount(channelId);
|
return PCastApiService.channels.getPublisherCount(channelId);
|
||||||
}
|
}
|
||||||
|
|
||||||
return await this.phenixChannelService.getPublisherCount(channelId);
|
return await this.phenixChannelService.getPublisherCount(channelId);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Failed to get publisher count', error);
|
logger.error('Failed to get publisher count', error);
|
||||||
@@ -246,4 +244,4 @@ export const killChannel = channelService.killChannel.bind(channelService);
|
|||||||
export const getChannel = channelService.getChannel.bind(channelService);
|
export const getChannel = channelService.getChannel.bind(channelService);
|
||||||
export const getPublisherCount = channelService.getPublisherCount.bind(channelService);
|
export const getPublisherCount = channelService.getPublisherCount.bind(channelService);
|
||||||
|
|
||||||
export default channelService;
|
export default channelService;
|
||||||
|
|||||||
@@ -188,4 +188,4 @@ export default class PlatformDetectionService {
|
|||||||
|
|
||||||
return match ? match[1] : null;
|
return match ? match[1] : null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,8 +30,7 @@ const initialState: ChannelsPublishingState = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Selectors
|
// Selectors
|
||||||
export const channelsPublishingSelector = (state: RootState): ChannelsPublishingState =>
|
export const channelsPublishingSelector = (state: RootState): ChannelsPublishingState => state.channelsPublishing;
|
||||||
state.channelsPublishing;
|
|
||||||
|
|
||||||
export const selectChannelsPublishingState = createSelector(
|
export const selectChannelsPublishingState = createSelector(
|
||||||
[channelsPublishingSelector],
|
[channelsPublishingSelector],
|
||||||
@@ -45,8 +44,7 @@ export const selectChannelsPublishingLoading = createSelector(
|
|||||||
|
|
||||||
export const selectChannelPublishingState = createSelector(
|
export const selectChannelPublishingState = createSelector(
|
||||||
[selectChannelsPublishingState, (_: RootState, channelId: string) => channelId],
|
[selectChannelsPublishingState, (_: RootState, channelId: string) => channelId],
|
||||||
(publishingStates: ChannelPublishingState[], channelId: string) =>
|
(publishingStates: ChannelPublishingState[], channelId: string) => publishingStates.find(state => state.channelId === channelId)
|
||||||
publishingStates.find(state => state.channelId === channelId)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Async thunks
|
// Async thunks
|
||||||
@@ -108,7 +106,7 @@ const channelsPublishingSlice = createSlice({
|
|||||||
name: 'channelsPublishing',
|
name: 'channelsPublishing',
|
||||||
initialState,
|
initialState,
|
||||||
reducers: {
|
reducers: {
|
||||||
clearPublishingState: (state) => {
|
clearPublishingState: state => {
|
||||||
state.publishingState = [];
|
state.publishingState = [];
|
||||||
state.error = null;
|
state.error = null;
|
||||||
},
|
},
|
||||||
@@ -116,10 +114,8 @@ const channelsPublishingSlice = createSlice({
|
|||||||
state.error = action.payload;
|
state.error = action.payload;
|
||||||
},
|
},
|
||||||
updateChannelState: (state, action: PayloadAction<ChannelPublishingState>) => {
|
updateChannelState: (state, action: PayloadAction<ChannelPublishingState>) => {
|
||||||
const index = state.publishingState.findIndex(
|
const index = state.publishingState.findIndex(item => item.channelId === action.payload.channelId);
|
||||||
item => item.channelId === action.payload.channelId
|
|
||||||
);
|
|
||||||
|
|
||||||
if (index >= 0) {
|
if (index >= 0) {
|
||||||
state.publishingState[index] = action.payload;
|
state.publishingState[index] = action.payload;
|
||||||
} else {
|
} else {
|
||||||
@@ -127,9 +123,9 @@ const channelsPublishingSlice = createSlice({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
extraReducers: (builder) => {
|
extraReducers: builder => {
|
||||||
builder
|
builder
|
||||||
.addCase(fetchChannelsPublishingState.pending, (state) => {
|
.addCase(fetchChannelsPublishingState.pending, state => {
|
||||||
state.isLoading = true;
|
state.isLoading = true;
|
||||||
state.error = null;
|
state.error = null;
|
||||||
})
|
})
|
||||||
@@ -144,10 +140,8 @@ const channelsPublishingSlice = createSlice({
|
|||||||
state.error = action.payload as string;
|
state.error = action.payload as string;
|
||||||
})
|
})
|
||||||
.addCase(updateChannelPublishingState.fulfilled, (state, action) => {
|
.addCase(updateChannelPublishingState.fulfilled, (state, action) => {
|
||||||
const index = state.publishingState.findIndex(
|
const index = state.publishingState.findIndex(item => item.channelId === action.payload.channelId);
|
||||||
item => item.channelId === action.payload.channelId
|
|
||||||
);
|
|
||||||
|
|
||||||
if (index >= 0) {
|
if (index >= 0) {
|
||||||
state.publishingState[index] = action.payload;
|
state.publishingState[index] = action.payload;
|
||||||
} else {
|
} else {
|
||||||
@@ -161,4 +155,4 @@ const channelsPublishingSlice = createSlice({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const {clearPublishingState, setPublishingError, updateChannelState} = channelsPublishingSlice.actions;
|
export const {clearPublishingState, setPublishingError, updateChannelState} = channelsPublishingSlice.actions;
|
||||||
export default channelsPublishingSlice.reducer;
|
export default channelsPublishingSlice.reducer;
|
||||||
|
|||||||
@@ -9,118 +9,85 @@ import {IChannelsState} from '../slices/Channels.slice';
|
|||||||
// Selectors
|
// Selectors
|
||||||
export const channelsSelector = (state: RootState): IChannelsState => state.channels;
|
export const channelsSelector = (state: RootState): IChannelsState => state.channels;
|
||||||
|
|
||||||
export const selectChannelList = createSelector(
|
export const selectChannelList = createSelector([channelsSelector], (channels: IChannelsState) => channels.channels);
|
||||||
[channelsSelector],
|
|
||||||
(channels: IChannelsState) => channels.channels
|
|
||||||
);
|
|
||||||
|
|
||||||
export const selectChannelsLoading = createSelector(
|
export const selectChannelsLoading = createSelector([channelsSelector], (channels: IChannelsState) => channels.isLoading);
|
||||||
[channelsSelector],
|
|
||||||
(channels: IChannelsState) => channels.isLoading
|
|
||||||
);
|
|
||||||
|
|
||||||
export const selectChannelsError = createSelector(
|
export const selectChannelsError = createSelector([channelsSelector], (channels: IChannelsState) => channels.error);
|
||||||
[channelsSelector],
|
|
||||||
(channels: IChannelsState) => channels.error
|
|
||||||
);
|
|
||||||
|
|
||||||
export const selectSelectedChannel = createSelector(
|
export const selectSelectedChannel = createSelector([channelsSelector], (channels: IChannelsState) => channels.selectedChannel);
|
||||||
[channelsSelector],
|
|
||||||
(channels: IChannelsState) => channels.selectedChannel
|
|
||||||
);
|
|
||||||
|
|
||||||
// Async thunks for channel operations
|
// Async thunks for channel operations
|
||||||
export const listChannels = createAsyncThunk(
|
export const listChannels = createAsyncThunk('channels/listChannels', async (_, {rejectWithValue}) => {
|
||||||
'channels/listChannels',
|
try {
|
||||||
async (_, {rejectWithValue}) => {
|
const channels = await channelService.listChannels();
|
||||||
try {
|
return channels;
|
||||||
const channels = await channelService.listChannels();
|
} catch (error) {
|
||||||
return channels;
|
return rejectWithValue(error instanceof Error ? error.message : 'Failed to fetch channels');
|
||||||
} catch (error) {
|
|
||||||
return rejectWithValue(error instanceof Error ? error.message : 'Failed to fetch channels');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
});
|
||||||
|
|
||||||
export const createChannelThunk = createAsyncThunk(
|
export const createChannelThunk = createAsyncThunk('channels/createChannel', async (params: CreateChannelParams, {rejectWithValue, dispatch}) => {
|
||||||
'channels/createChannel',
|
try {
|
||||||
async (params: CreateChannelParams, {rejectWithValue, dispatch}) => {
|
const newChannel = await channelService.createChannel(params);
|
||||||
try {
|
// Refresh the channel list after creation
|
||||||
const newChannel = await channelService.createChannel(params);
|
dispatch(listChannels());
|
||||||
// Refresh the channel list after creation
|
return newChannel;
|
||||||
dispatch(listChannels());
|
} catch (error) {
|
||||||
return newChannel;
|
return rejectWithValue(error instanceof Error ? error.message : 'Failed to create channel');
|
||||||
} catch (error) {
|
|
||||||
return rejectWithValue(error instanceof Error ? error.message : 'Failed to create channel');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
});
|
||||||
|
|
||||||
export const deleteChannelThunk = createAsyncThunk(
|
export const deleteChannelThunk = createAsyncThunk('channels/deleteChannel', async (params: DeleteChannelParams, {rejectWithValue, dispatch}) => {
|
||||||
'channels/deleteChannel',
|
try {
|
||||||
async (params: DeleteChannelParams, {rejectWithValue, dispatch}) => {
|
await channelService.deleteChannel(params);
|
||||||
try {
|
// Refresh the channel list after deletion
|
||||||
await channelService.deleteChannel(params);
|
dispatch(listChannels());
|
||||||
// Refresh the channel list after deletion
|
return params.channelId;
|
||||||
dispatch(listChannels());
|
} catch (error) {
|
||||||
return params.channelId;
|
return rejectWithValue(error instanceof Error ? error.message : 'Failed to delete channel');
|
||||||
} catch (error) {
|
|
||||||
return rejectWithValue(error instanceof Error ? error.message : 'Failed to delete channel');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
});
|
||||||
|
|
||||||
export const forkChannelThunk = createAsyncThunk(
|
export const forkChannelThunk = createAsyncThunk('channels/forkChannel', async (params: ForkChannelParams, {rejectWithValue, dispatch}) => {
|
||||||
'channels/forkChannel',
|
try {
|
||||||
async (params: ForkChannelParams, {rejectWithValue, dispatch}) => {
|
await channelService.forkChannel(params);
|
||||||
try {
|
// Refresh the channel list after forking
|
||||||
await channelService.forkChannel(params);
|
dispatch(listChannels());
|
||||||
// Refresh the channel list after forking
|
return params;
|
||||||
dispatch(listChannels());
|
} catch (error) {
|
||||||
return params;
|
return rejectWithValue(error instanceof Error ? error.message : 'Failed to fork channel');
|
||||||
} catch (error) {
|
|
||||||
return rejectWithValue(error instanceof Error ? error.message : 'Failed to fork channel');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
});
|
||||||
|
|
||||||
export const killChannelThunk = createAsyncThunk(
|
export const killChannelThunk = createAsyncThunk('channels/killChannel', async (params: KillChannelParams, {rejectWithValue, dispatch}) => {
|
||||||
'channels/killChannel',
|
try {
|
||||||
async (params: KillChannelParams, {rejectWithValue, dispatch}) => {
|
await channelService.killChannel(params);
|
||||||
try {
|
// Refresh the channel list after killing
|
||||||
await channelService.killChannel(params);
|
dispatch(listChannels());
|
||||||
// Refresh the channel list after killing
|
return params.channelId;
|
||||||
dispatch(listChannels());
|
} catch (error) {
|
||||||
return params.channelId;
|
return rejectWithValue(error instanceof Error ? error.message : 'Failed to kill channel');
|
||||||
} catch (error) {
|
|
||||||
return rejectWithValue(error instanceof Error ? error.message : 'Failed to kill channel');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
});
|
||||||
|
|
||||||
export const getChannelThunk = createAsyncThunk(
|
export const getChannelThunk = createAsyncThunk('channels/getChannel', async (channelId: string, {rejectWithValue}) => {
|
||||||
'channels/getChannel',
|
try {
|
||||||
async (channelId: string, {rejectWithValue}) => {
|
const channel = await channelService.getChannel(channelId);
|
||||||
try {
|
return channel;
|
||||||
const channel = await channelService.getChannel(channelId);
|
} catch (error) {
|
||||||
return channel;
|
return rejectWithValue(error instanceof Error ? error.message : 'Failed to get channel');
|
||||||
} catch (error) {
|
|
||||||
return rejectWithValue(error instanceof Error ? error.message : 'Failed to get channel');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
});
|
||||||
|
|
||||||
export const getPublisherCountThunk = createAsyncThunk(
|
export const getPublisherCountThunk = createAsyncThunk('channels/getPublisherCount', async (channelId: string, {rejectWithValue}) => {
|
||||||
'channels/getPublisherCount',
|
try {
|
||||||
async (channelId: string, {rejectWithValue}) => {
|
const count = await channelService.getPublisherCount(channelId);
|
||||||
try {
|
return {channelId, count};
|
||||||
const count = await channelService.getPublisherCount(channelId);
|
} catch (error) {
|
||||||
return {channelId, count};
|
return rejectWithValue(error instanceof Error ? error.message : 'Failed to get publisher count');
|
||||||
} catch (error) {
|
|
||||||
return rejectWithValue(error instanceof Error ? error.message : 'Failed to get publisher count');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
});
|
||||||
|
|
||||||
// Export all actions and selectors
|
// Export all actions and selectors
|
||||||
export * from '../slices/Channels.slice';
|
export * from '../slices/Channels.slice';
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ export enum StoreScreensType {
|
|||||||
ChannelDetail = 'channelDetail',
|
ChannelDetail = 'channelDetail',
|
||||||
Settings = 'settings',
|
Settings = 'settings',
|
||||||
Login = 'login',
|
Login = 'login',
|
||||||
Channels = "Channels"
|
Channels = 'Channels'
|
||||||
}
|
}
|
||||||
|
|
||||||
// Screen state interface
|
// Screen state interface
|
||||||
@@ -35,25 +35,13 @@ const initialState: ScreenState = {
|
|||||||
// Selectors
|
// Selectors
|
||||||
export const screensSelector = (state: RootState): ScreenState => state.screens;
|
export const screensSelector = (state: RootState): ScreenState => state.screens;
|
||||||
|
|
||||||
export const selectCurrentScreen = createSelector(
|
export const selectCurrentScreen = createSelector([screensSelector], (screens: ScreenState) => screens.currentScreen);
|
||||||
[screensSelector],
|
|
||||||
(screens: ScreenState) => screens.currentScreen
|
|
||||||
);
|
|
||||||
|
|
||||||
export const selectScreenProps = createSelector(
|
export const selectScreenProps = createSelector([screensSelector], (screens: ScreenState) => screens.screenProps);
|
||||||
[screensSelector],
|
|
||||||
(screens: ScreenState) => screens.screenProps
|
|
||||||
);
|
|
||||||
|
|
||||||
export const selectPreviousScreen = createSelector(
|
export const selectPreviousScreen = createSelector([screensSelector], (screens: ScreenState) => screens.previousScreen);
|
||||||
[screensSelector],
|
|
||||||
(screens: ScreenState) => screens.previousScreen
|
|
||||||
);
|
|
||||||
|
|
||||||
export const selectNavigationHistory = createSelector(
|
export const selectNavigationHistory = createSelector([screensSelector], (screens: ScreenState) => screens.navigationHistory);
|
||||||
[screensSelector],
|
|
||||||
(screens: ScreenState) => screens.navigationHistory
|
|
||||||
);
|
|
||||||
|
|
||||||
// Slice
|
// Slice
|
||||||
const screensSlice = createSlice({
|
const screensSlice = createSlice({
|
||||||
@@ -63,7 +51,7 @@ const screensSlice = createSlice({
|
|||||||
setCurrentScreen: (state, action: PayloadAction<StoreScreensType>) => {
|
setCurrentScreen: (state, action: PayloadAction<StoreScreensType>) => {
|
||||||
state.previousScreen = state.currentScreen;
|
state.previousScreen = state.currentScreen;
|
||||||
state.currentScreen = action.payload;
|
state.currentScreen = action.payload;
|
||||||
|
|
||||||
// Add to navigation history (keep last 10)
|
// Add to navigation history (keep last 10)
|
||||||
state.navigationHistory.push(action.payload);
|
state.navigationHistory.push(action.payload);
|
||||||
if (state.navigationHistory.length > 10) {
|
if (state.navigationHistory.length > 10) {
|
||||||
@@ -82,28 +70,28 @@ const screensSlice = createSlice({
|
|||||||
navigateToScreen: (state, action: PayloadAction<{screen: StoreScreensType; props?: ScreenProps}>) => {
|
navigateToScreen: (state, action: PayloadAction<{screen: StoreScreensType; props?: ScreenProps}>) => {
|
||||||
state.previousScreen = state.currentScreen;
|
state.previousScreen = state.currentScreen;
|
||||||
state.currentScreen = action.payload.screen;
|
state.currentScreen = action.payload.screen;
|
||||||
|
|
||||||
if (action.payload.props) {
|
if (action.payload.props) {
|
||||||
state.screenProps = action.payload.props;
|
state.screenProps = action.payload.props;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add to navigation history
|
// Add to navigation history
|
||||||
state.navigationHistory.push(action.payload.screen);
|
state.navigationHistory.push(action.payload.screen);
|
||||||
if (state.navigationHistory.length > 10) {
|
if (state.navigationHistory.length > 10) {
|
||||||
state.navigationHistory.shift();
|
state.navigationHistory.shift();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
navigateBack: (state) => {
|
navigateBack: state => {
|
||||||
if (state.previousScreen) {
|
if (state.previousScreen) {
|
||||||
const temp = state.currentScreen;
|
const temp = state.currentScreen;
|
||||||
state.currentScreen = state.previousScreen;
|
state.currentScreen = state.previousScreen;
|
||||||
state.previousScreen = temp;
|
state.previousScreen = temp;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
clearScreenProps: (state) => {
|
clearScreenProps: state => {
|
||||||
state.screenProps = {};
|
state.screenProps = {};
|
||||||
},
|
},
|
||||||
resetNavigation: (state) => {
|
resetNavigation: state => {
|
||||||
state.currentScreen = StoreScreensType.Login;
|
state.currentScreen = StoreScreensType.Login;
|
||||||
state.previousScreen = null;
|
state.previousScreen = null;
|
||||||
state.screenProps = {};
|
state.screenProps = {};
|
||||||
@@ -112,14 +100,6 @@ const screensSlice = createSlice({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export const {
|
export const {setCurrentScreen, setScreenProps, updateScreenProps, navigateToScreen, navigateBack, clearScreenProps, resetNavigation} = screensSlice.actions;
|
||||||
setCurrentScreen,
|
|
||||||
setScreenProps,
|
|
||||||
updateScreenProps,
|
|
||||||
navigateToScreen,
|
|
||||||
navigateBack,
|
|
||||||
clearScreenProps,
|
|
||||||
resetNavigation
|
|
||||||
} = screensSlice.actions;
|
|
||||||
|
|
||||||
export default screensSlice.reducer;
|
export default screensSlice.reducer;
|
||||||
|
|||||||
@@ -56,13 +56,13 @@ export const authenticateRequestMiddleware: Middleware = store => next => async
|
|||||||
try {
|
try {
|
||||||
console.log('[authenticateRequest] Attempting auto-authentication');
|
console.log('[authenticateRequest] Attempting auto-authentication');
|
||||||
// Use the Redux thunk to properly update the state
|
// Use the Redux thunk to properly update the state
|
||||||
const authResult = await store.dispatch(authenticateCredentialsThunk({ applicationId, secret }) as any);
|
const authResult = await store.dispatch(authenticateCredentialsThunk({applicationId, secret}) as any);
|
||||||
|
|
||||||
if (authResult.type.endsWith('/rejected') || authResult.payload === 'Authentication failed') {
|
if (authResult.type.endsWith('/rejected') || authResult.payload === 'Authentication failed') {
|
||||||
console.log('[authenticateRequest] Authentication failed');
|
console.log('[authenticateRequest] Authentication failed');
|
||||||
return next(setUnauthorized());
|
return next(setUnauthorized());
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('[authenticateRequest] Auto-authentication successful, proceeding with action');
|
console.log('[authenticateRequest] Auto-authentication successful, proceeding with action');
|
||||||
return next(action);
|
return next(action);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -4,4 +4,4 @@
|
|||||||
|
|
||||||
// Re-export from the action module for backwards compatibility
|
// Re-export from the action module for backwards compatibility
|
||||||
export {StoreScreensType} from '../action/screens';
|
export {StoreScreensType} from '../action/screens';
|
||||||
export type {ScreenState, ScreenProps} from '../action/screens';
|
export type {ScreenState, ScreenProps} from '../action/screens';
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ export const selectChannelList = createSelector([selectChannels], channels => ch
|
|||||||
export const fetchChannelList = createAsyncThunk('channels/fetchChannelList', async (_, {rejectWithValue}) => {
|
export const fetchChannelList = createAsyncThunk('channels/fetchChannelList', async (_, {rejectWithValue}) => {
|
||||||
try {
|
try {
|
||||||
return PCastApiService.channels.list();
|
return PCastApiService.channels.list();
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return rejectWithValue(error);
|
return rejectWithValue(error);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ export class Theme {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static get background() {
|
static get background() {
|
||||||
return Theme
|
return Theme;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright 2024 Phenix Real Time Solutions, Inc. Confidential and Proprietary. All Rights Reserved.
|
|
||||||
*/
|
|
||||||
import {createContext} from 'react';
|
|
||||||
|
|
||||||
export const ViewContext = createContext('');
|
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* Copyright 2024 Phenix Real Time Solutions, Inc. Confidential and Proprietary. All Rights Reserved.
|
* Copyright 2024 Phenix Real Time Solutions, Inc. Confidential and Proprietary. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
export * from './contexts';
|
|
||||||
export * from './date';
|
export * from './date';
|
||||||
export * from './sort';
|
export * from './sort';
|
||||||
export * from './validators';
|
export * from './validators';
|
||||||
|
|||||||
@@ -18,9 +18,11 @@ import {columns} from './columns-config';
|
|||||||
import {CreateChannelModal} from './create-channel';
|
import {CreateChannelModal} from './create-channel';
|
||||||
|
|
||||||
const POLLING_INTERVAL = 5000; // 5 seconds
|
const POLLING_INTERVAL = 5000; // 5 seconds
|
||||||
const ChannelListLoading = () => <Main>
|
const ChannelListLoading = () => (
|
||||||
<Loader />
|
<Main>
|
||||||
</Main>
|
<Loader />
|
||||||
|
</Main>
|
||||||
|
);
|
||||||
|
|
||||||
export const ChannelList = (): React.JSX.Element => {
|
export const ChannelList = (): React.JSX.Element => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
@@ -37,7 +39,6 @@ export const ChannelList = (): React.JSX.Element => {
|
|||||||
// Memoized columns to prevent unnecessary re-renders
|
// Memoized columns to prevent unnecessary re-renders
|
||||||
const channelsColumns = React.useMemo(() => ({...columns}), []);
|
const channelsColumns = React.useMemo(() => ({...columns}), []);
|
||||||
|
|
||||||
|
|
||||||
// Load channels on component mount
|
// Load channels on component mount
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
dispatch(listChannels());
|
dispatch(listChannels());
|
||||||
@@ -64,14 +65,17 @@ export const ChannelList = (): React.JSX.Element => {
|
|||||||
}, [dispatch, channels.length, isFetching]);
|
}, [dispatch, channels.length, isFetching]);
|
||||||
|
|
||||||
// Memoized screen header to prevent unnecessary re-renders
|
// Memoized screen header to prevent unnecessary re-renders
|
||||||
const screenHeader: ITableWithPaginationHeader = React.useMemo(() => ({
|
const screenHeader: ITableWithPaginationHeader = React.useMemo(
|
||||||
[TableHeaderKey.Search]: {},
|
() => ({
|
||||||
[TableHeaderKey.AddRow]: {
|
[TableHeaderKey.Search]: {},
|
||||||
openAddRowModal: () => {
|
[TableHeaderKey.AddRow]: {
|
||||||
setCreateChannelModalOpened(true);
|
openAddRowModal: () => {
|
||||||
|
setCreateChannelModalOpened(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}),
|
||||||
}), []);
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
// Callback for handling search and sort changes (no-op since TableWithPagination handles internally)
|
// Callback for handling search and sort changes (no-op since TableWithPagination handles internally)
|
||||||
const changeScreenProps = useCallback((_data: Partial<ITableSortSearch>) => {
|
const changeScreenProps = useCallback((_data: Partial<ITableSortSearch>) => {
|
||||||
@@ -90,10 +94,7 @@ export const ChannelList = (): React.JSX.Element => {
|
|||||||
<Body className="table-container">
|
<Body className="table-container">
|
||||||
<Error>
|
<Error>
|
||||||
{(channelListErrorMessages as Record<string, string>)[error] || error}
|
{(channelListErrorMessages as Record<string, string>)[error] || error}
|
||||||
<button
|
<button onClick={() => dispatch(listChannels())} style={{marginLeft: '10px', padding: '5px 10px'}}>
|
||||||
onClick={() => dispatch(listChannels())}
|
|
||||||
style={{ marginLeft: '10px', padding: '5px 10px' }}
|
|
||||||
>
|
|
||||||
Retry
|
Retry
|
||||||
</button>
|
</button>
|
||||||
</Error>
|
</Error>
|
||||||
@@ -104,37 +105,31 @@ export const ChannelList = (): React.JSX.Element => {
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Body className="table-container">
|
<Body className="table-container">
|
||||||
{isFetching ? <ChannelListLoading /> : channels.length === 0 ? (
|
{isFetching ? (
|
||||||
<Main>
|
<ChannelListLoading />
|
||||||
<div style={{ textAlign: 'center', padding: '2rem' }}>
|
) : channels.length === 0 ? (
|
||||||
<h3>No channels found</h3>
|
<Main>
|
||||||
<p>Get started by creating your first channel.</p>
|
<div style={{textAlign: 'center', padding: '2rem'}}>
|
||||||
<button
|
<h3>No channels found</h3>
|
||||||
onClick={() => setCreateChannelModalOpened(true)}
|
<p>Get started by creating your first channel.</p>
|
||||||
style={{ padding: '10px 20px', marginTop: '1rem' }}
|
<button onClick={() => setCreateChannelModalOpened(true)} style={{padding: '10px 20px', marginTop: '1rem'}}>
|
||||||
>
|
Create Channel
|
||||||
Create Channel
|
</button>
|
||||||
</button>
|
</div>
|
||||||
</div>
|
</Main>
|
||||||
</Main>
|
) : (
|
||||||
) : (
|
<TableWithPagination
|
||||||
<TableWithPagination
|
title="Channels"
|
||||||
title="Channels"
|
screenHeader={screenHeader}
|
||||||
screenHeader={screenHeader}
|
columns={channelsColumns}
|
||||||
columns={channelsColumns}
|
data={channels as any[]}
|
||||||
data={channels as any[]}
|
paginationItemText="channels"
|
||||||
paginationItemText="channels"
|
changeSortProps={changeScreenProps}
|
||||||
changeSortProps={changeScreenProps}
|
changeSearch={changeScreenProps}
|
||||||
changeSearch={changeScreenProps}
|
/>
|
||||||
/>
|
)}
|
||||||
)}
|
|
||||||
</Body>
|
</Body>
|
||||||
{isCreateChannelModalOpened && (
|
{isCreateChannelModalOpened && <CreateChannelModal getChannelList={refreshChannelList} setCreateChannelModalOpened={setCreateChannelModalOpened} />}
|
||||||
<CreateChannelModal
|
|
||||||
getChannelList={refreshChannelList}
|
|
||||||
setCreateChannelModalOpened={setCreateChannelModalOpened}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user