Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion app/components-react/root/LiveDock.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -430,7 +430,11 @@ function LiveDock() {
placement="right"
autoAdjustOverflow={false}
>
<i onClick={() => ctrl.showEditStreamInfo()} className="icon-edit" />
<i
data-name="edit-stream"
onClick={() => ctrl.showEditStreamInfo()}
className="icon-edit"
/>
</Tooltip>
)}
{hasLiveDockFeature('view-stream') && isStreaming && (
Expand Down
37 changes: 36 additions & 1 deletion app/components-react/shared/DisplaySelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,24 @@ export default function DisplaySelector(p: IDisplaySelectorProps) {
canDualStream,
updateCustomDestinationDisplayAndSaveSettings,
updatePlatformDisplayAndSaveSettings,
isLiveOutputEditingEnabled,
isUpdateMode,
isLive,
} = useGoLiveSettings().extend(module => ({
get canDualStream() {
if (!p.platform) return false;
if (module.isLiveOutputEditingEnabled) return false;
return module.getCanDualStream(p.platform);
},

get isLive(): boolean {
return (
module.isUpdateMode &&
module.isLiveOutputEditingEnabled &&
!!module.isTargetLive(p.platform ?? p.index)
);
},

get display(): TDisplayOutput {
const defaultDisplay = p.platform
? module.settings.platforms[p.platform]?.display
Expand Down Expand Up @@ -58,6 +70,29 @@ export default function DisplaySelector(p: IDisplaySelectorProps) {
},
];

if (isLive) {
// A live target cannot change display without restarting its stream, so offer only the
// display it is already using and explain how to change it
const activeDisplay =
defaultDisplays.find(option => option.value === display) ?? defaultDisplays[0];

return [
{
...activeDisplay,
disabled: true,
tooltip: $t(
'Go offline to change orientation, then select a new resolution and go live again',
),
},
];
}

if (isUpdateMode) {
// Don't show Dual stream option in the Edit Stream window because it is not compatible with
// live output editing, which is the only time the display toggles are shown in the update window
return defaultDisplays;
}

if (canDualStream) {
const tooltip = p?.platform
? $t('Stream both horizontally and vertically to %{platform}', {
Expand All @@ -77,7 +112,7 @@ export default function DisplaySelector(p: IDisplaySelectorProps) {
}

return defaultDisplays;
}, [canDualStream]);
}, [canDualStream, isLiveOutputEditingEnabled, isUpdateMode, isLive, display, p.platform]);

const onChange = useCallback(
(val: string) => {
Expand Down
4 changes: 4 additions & 0 deletions app/components-react/shared/inputs/RadioInput.m.less
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@
color: var(--icon-toggle-active);
transition: color 0.3s ease-in-out;
}

i.disabled {
opacity: 0.7;
}
}

:global(.ant-radio) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -223,4 +223,10 @@
border-radius: 8px;
}

// .highlighter-banner___36EFl .ant-switch-handle::before
.dismissable {
display: flex;
justify-content: flex-end;
width: 100%;
padding: 10px;
text-decoration: underline;
}
49 changes: 35 additions & 14 deletions app/components-react/windows/go-live/AiHighlighterToggle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { SwitchInput } from 'components-react/shared/inputs/SwitchInput';
import React, { useEffect, useState, memo } from 'react';
import styles from './AiHighlighterToggle.m.less';
import { Services } from 'components-react/service-provider';
import * as remote from '@electron/remote';
import { useDebounce, useVuex } from 'components-react/hooks';
import { DownOutlined, UpOutlined } from '@ant-design/icons';
import { Alert, Button } from 'antd';
Expand All @@ -15,17 +14,30 @@ import { EAvailableFeatures } from 'services/incremental-rollout';
import { promptAction } from 'components-react/modals';
import InputWrapper from 'components-react/shared/inputs/InputWrapper';
import Translate from 'components-react/shared/Translate';
import { EDismissable } from 'services/dismissables';

export default function AiHighlighterToggle({ cardIsExpanded }: { cardIsExpanded: boolean }) {
export default function AiHighlighterToggle({
cardIsExpanded,
isUpdateMode,
}: {
cardIsExpanded: boolean;
isUpdateMode?: boolean;
}) {
//TODO M: Probably good way to integrate the highlighter in to GoLiveSettings
const { HighlighterService, StreamingService, IncrementalRolloutService } = Services;
const {
HighlighterService,
StreamingService,
IncrementalRolloutService,
DismissablesService,
} = Services;
const {
useHighlighter,
highlighterVersion,
isVerticalRecording,
isVerticalReplayBuffer,
outputDisplay,
gameName,
shouldShow,
} = useVuex(() => {
return {
useHighlighter: HighlighterService.views.useAiHighlighter,
Expand All @@ -34,6 +46,7 @@ export default function AiHighlighterToggle({ cardIsExpanded }: { cardIsExpanded
isVerticalReplayBuffer: StreamingService.views.isVerticalReplayBuffer,
outputDisplay: StreamingService.views.outputDisplay,
gameName: StreamingService.views.gameName,
shouldShow: DismissablesService.views.shouldShow(EDismissable.HighlighterBanner),
};
});

Expand All @@ -47,8 +60,8 @@ export default function AiHighlighterToggle({ cardIsExpanded }: { cardIsExpanded
const supportedGame = isGameSupported(gameName);
setGameIsSupported(!!supportedGame);
if (supportedGame) {
setIsExpanded(true);
setGameConfig(getConfigByGame(supportedGame));
if (!isUpdateMode) setIsExpanded(true);
} else {
setGameConfig(null);
}
Expand Down Expand Up @@ -83,19 +96,16 @@ export default function AiHighlighterToggle({ cardIsExpanded }: { cardIsExpanded
}

function getInitialExpandedState() {
if (gameIsSupported) {
return true;
} else {
if (useHighlighter) {
return true;
} else {
return cardIsExpanded;
}
}
if (isUpdateMode) return false;
if (gameIsSupported) return true;
if (useHighlighter) return true;
return cardIsExpanded;
}
const initialExpandedState = getInitialExpandedState();
const [isExpanded, setIsExpanded] = useState(initialExpandedState);

const showHighlighterBanner = shouldShow || !isUpdateMode;

const toggleHighlighter = useDebounce(300, handleToggleHighlighter);

function handleToggleHighlighter() {
Expand Down Expand Up @@ -160,7 +170,7 @@ export default function AiHighlighterToggle({ cardIsExpanded }: { cardIsExpanded

return (
<div>
{gameIsSupported ? (
{gameIsSupported && showHighlighterBanner ? (
<div
key={'aiSelector'}
data-name="ai-highlighter-selector"
Expand Down Expand Up @@ -363,6 +373,17 @@ export default function AiHighlighterToggle({ cardIsExpanded }: { cardIsExpanded
)}
</>
)}
{isUpdateMode && (
<div className={styles.dismissable}>
<a
onClick={() =>
DismissablesService.actions.dismiss(EDismissable.HighlighterBanner)
}
>
{$t('Do not ask again')}
</a>
Comment thread
michelinewu marked this conversation as resolved.
</div>
)}
</div>
</div>
) : (
Expand Down
14 changes: 13 additions & 1 deletion app/components-react/windows/go-live/CommonPlatformFields.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,15 @@ export const CommonPlatformFields = InputComponent((rawProps: IProps) => {
? view.supports('description', [p.platform as TPlatform])
: view.supports('description');

// Only the shared instance can run out of platforms to write to, and only while live, where
// `updateCommonFields` skips any platform using its own title. Once every enabled platform has
// opted out, editing the shared title changes nothing.
const titleDisabled =
!p.platform &&
view.isMidStreamMode &&
view.enabledPlatforms.length > 0 &&
!view.platformsWithoutCustomFields.length;

const fields = p.value;

const height = useMemo(() => {
Expand Down Expand Up @@ -121,7 +130,10 @@ export const CommonPlatformFields = InputComponent((rawProps: IProps) => {
$t('Title')
)
}
required={true}
// A disabled input cannot be corrected, so it must not be able to fail validation. Each
// platform using its own title validates that title in its own section.
required={!titleDisabled}
disabled={titleDisabled}
max={maxCharacters}
min={minCharacters}
layout={p.layout}
Expand Down
58 changes: 43 additions & 15 deletions app/components-react/windows/go-live/GoLive.m.less
Original file line number Diff line number Diff line change
Expand Up @@ -116,32 +116,19 @@ button.bottom {
text-align: right;
}

.banner-wrapper {
.info-banner-wrapper {
flex: 1;
display: flex;
flex-direction: row;
align-items: flex-start;
}

.banner {
.info-banner {
margin-right: 5px;
height: 32px !important;
width: unset !important;
}

.info-banner-wrapper {
flex: 1;
display: flex;
flex-direction: row;
align-items: flex-start;

:global(.info-banner) {
margin-right: 5px;
height: 32px;
width: unset;
}
}

.primary-chat {
border: 0px;
padding-bottom: 5px;
Expand Down Expand Up @@ -206,6 +193,12 @@ button.bottom {

.confirm-btn {
width: 141.25px;

&& {
display: inline-flex;
align-items: center;
justify-content: center;
}
}

.footer-content {
Expand Down Expand Up @@ -294,3 +287,38 @@ button.bottom {
flex-direction: row;
align-items: center;
}

.update-btn-tooltip {
margin-left: 8px;
display: flex;
}

.spinner {
height: 20px;
width: 20px;
}

.update-btn {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
gap: 8px;
line-height: 1;
}

.ultra-icon {
background: linear-gradient(
123.53deg,
#2de8b0 25.56%,
#cbe953 60.27%,
#ffab48 79.52%,
#ff5151 96.69%
) !important;
background-clip: text !important;
color: transparent !important;
}

.section-title {
margin-top: 15px;
}
25 changes: 18 additions & 7 deletions app/components-react/windows/go-live/GoLiveError.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,15 @@ export default function GoLiveError() {
return renderSettingsUpdateError(error);
case 'RESTREAM_DISABLED':
case 'RESTREAM_SETUP_FAILED':
case 'RESTREAM_UPDATE_FAILED':
case 'RESTREAM_INVALID_CONFIG':
case 'RESTREAM_STREAM_KEY_MISSING':
case 'RESTREAM_STREAM_KEY_FETCH_FAILED':
case 'RESTREAM_DISPLAY_SETUP_FAILED':
case 'RESTREAM_ADD_TARGETS_FAILED':
case 'RESTREAM_NO_ACTIVE_TARGETS':
case 'RESTREAM_REMOVE_TARGET_NOT_FOUND':
case 'RESTREAM_REMOVE_TARGETS_FAILED':
return renderRestreamError(error);
case 'DUAL_OUTPUT_RESTREAM_DISABLED':
case 'DUAL_OUTPUT_SETUP_FAILED':
Expand Down Expand Up @@ -268,14 +277,16 @@ export default function GoLiveError() {
]
: error.details.split('\n');

// Leave the message to `MessageLayout`, which falls back to the error's own message. Each
// restream failure has its own error type, so the headline names what actually went wrong
// instead of repeating the same generic line for every one of them.
return (
<MessageLayout
error={error}
hasButton={true}
message={$t(
'Please try again. If the issue persists, you can stream directly to a single platform instead or click the button below to bypass and go live.',
)}
>
<MessageLayout error={error} hasButton={true}>
<p>
{$t(
'Please try again. If the issue persists, you can stream directly to a single platform instead or click the button below to bypass and go live.',
)}
</p>
{`${$t('Issues')}:`}
<ul>
{details.map((detail: string, index: number) => (
Expand Down
Loading
Loading