Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
35 changes: 5 additions & 30 deletions app/components-react/modals/onboarding/Splash.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,12 @@ import { Button } from 'antd';
import { $t } from 'services/i18n';
import { Services } from 'components-react/service-provider';
import styles from './Common.m.less';
import { DancingKevins, IOnboardingStepProps, useAuth } from './Onboarding';
import Translate from 'components-react/shared/Translate';
import { DancingKevins, IOnboardingStepProps } from './Onboarding';

export function Splash(p: IOnboardingStepProps) {
const { OnboardingV2Service, RecordingModeService } = Services;
const { OnboardingV2Service } = Services;

function startRecordingMode() {
RecordingModeService.actions.setRecordingMode(true);
RecordingModeService.actions.setUpRecordingFirstTimeSetup();
OnboardingV2Service.actions.takeStep();
}

const { SLIDLogin } = useAuth();

function login() {
// To account for backtracking
RecordingModeService.actions.setRecordingMode(false);
function getStarted() {
OnboardingV2Service.actions.takeStep();
}

Expand All @@ -37,24 +26,10 @@ export function Splash(p: IOnboardingStepProps) {
'Access all the tools you need, including overlays, alerts, automatic clips, sponsorships, and more',
)}
</span>
<Button onClick={SLIDLogin} type="primary" className={styles.bigButton}>
{$t('Create an account')}
&nbsp;
<i className="icon-pop-out-2" />
<Button onClick={getStarted} type="primary" className={styles.bigButton}>
{$t('Get Started')}
</Button>
<Translate
style={{ paddingTop: 24 }}
message="Already have an account? <link>Log In</link>"
>
<a onClick={login} slot="link" />
</Translate>
</div>
<Translate
style={{ paddingTop: 32 }}
message="Just looking to record? <link>Start here</link>"
>
<a onClick={startRecordingMode} slot="link" />
</Translate>
</div>
);
}
9 changes: 7 additions & 2 deletions app/components-react/pages/AILanding.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { useRealmObject } from 'components-react/hooks/realm';
import { Services } from 'components-react/service-provider';
import { SwitchInput } from 'components-react/shared/inputs';
import Scrollable from 'components-react/shared/Scrollable';
import { Modal, Tooltip } from 'antd';
import { message, Modal, Tooltip } from 'antd';
import React, { useEffect, useMemo, useState } from 'react';
import { EGame } from 'services/highlighter/models/ai-highlighter.models';
import { getConfigByGame } from 'services/highlighter/models/game-config.models';
Expand Down Expand Up @@ -84,6 +84,7 @@ export default function AILanding() {
const visionActions = VisionService.actions;
const visionState = useRealmObject(VisionService.state);

const isLoggedIn = useVuex(() => UserService.views.isLoggedIn);
const visionEnabledState = useRealmObject(VisionService.enabledState);
const enabled = visionEnabledState.isEnabled;

Expand All @@ -106,6 +107,10 @@ export default function AILanding() {
}, []);

function onToggleAiClick(isEnabled?: boolean) {
if (!isLoggedIn) {
message.error($t('Please log in to use Streamlabs AI.'), 3);
return;
}
const newIsEnabled = isEnabled ?? !enabled;
trackEvent('enabled', { enabled: String(newIsEnabled) });
visionActions.setIsEnabled(newIsEnabled);
Expand Down Expand Up @@ -263,7 +268,7 @@ export default function AILanding() {
>
<SwitchInput
label={$t('Turn On AI')}
disabled={visionState.isStarting}
disabled={!isLoggedIn || visionState.isStarting}
value={enabled}
/>
</div>
Expand Down
13 changes: 9 additions & 4 deletions app/components-react/windows/settings/AISettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import React, { useEffect, useMemo } from 'react';
import { $t } from 'services/i18n/index';
import { VisionProcess, VisionService, VisionState } from 'services/vision';
import { ObsSettingsSection } from './ObsSettings';
import { useVuex } from 'components-react/hooks';

type VisionStatus = 'running' | 'starting' | 'updating' | 'stopped';

Expand Down Expand Up @@ -39,6 +40,7 @@ function VisionInstalling(props: { percent: number; isUpdate: boolean }) {
}

type VisionInfoProps = {
isLoggedIn: boolean;
status: VisionStatus;
enabled: boolean;
starting: boolean;
Expand All @@ -56,6 +58,7 @@ type VisionInfoProps = {
};

function VisionInfo({
isLoggedIn,
status,
enabled,
starting,
Expand All @@ -80,7 +83,7 @@ function VisionInfo({
<div style={{ marginBottom: 16 }}>
<SwitchInput
label={$t('Turn On AI')}
disabled={starting}
disabled={!isLoggedIn || starting}
value={enabled}
onChange={() => setIsEnabled(!enabled)}
/>
Expand Down Expand Up @@ -116,7 +119,7 @@ function VisionInfo({
<div style={{ marginBottom: 6 }}>{$t('Active Process')}</div>
<Select
style={{ minWidth: 240 }}
disabled={!enabled || !isRunning}
disabled={!isLoggedIn || !enabled || !isRunning}
value={isRunning ? activeProcessId : undefined}
onFocus={() => isRunning && requestAvailableProcesses()}
onChange={val => activateProcess(val, selectedGame)}
Expand All @@ -134,7 +137,7 @@ function VisionInfo({
<div style={{ marginBottom: 6 }}>{$t('Selected Game')}</div>
<Select
style={{ minWidth: 240 }}
disabled={!enabled || !isRunning}
disabled={!isLoggedIn || !enabled || !isRunning}
value={selectedGame}
onChange={val => {
console.log('Changing game to: ', val);
Expand All @@ -159,9 +162,10 @@ function openLink(url: string) {
}

export function AISettings() {
const { UsageStatisticsService, VisionService } = Services;
const { UsageStatisticsService, UserService, VisionService } = Services;
const actions = VisionService.actions;
const state = useRealmObject(VisionService.state);
const isLoggedIn = useVuex(() => UserService.views.isLoggedIn);

const visionEnabledState = useRealmObject(VisionService.enabledState);
const enabled = visionEnabledState.isEnabled;
Expand Down Expand Up @@ -198,6 +202,7 @@ export function AISettings() {
return (
<div>
<VisionInfo
isLoggedIn={isLoggedIn}
status={getStatusText(state)}
enabled={enabled}
starting={state.isStarting}
Expand Down
1 change: 1 addition & 0 deletions app/i18n/en-US/ai.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"Active Process": "Active Process",
"Selected Game": "Selected Game",
"There was an error installing Streamlabs AI.": "There was an error installing Streamlabs AI.",
"Please log in to use Streamlabs AI.": "Please log in to use Streamlabs AI.",
"Streamlabs Desktop Support": "Streamlabs Desktop Support",
"Streamlabs Desktop Support — approval needed": "Streamlabs Desktop Support — approval needed",
"How can we help you today?": "How can we help you today?",
Expand Down
4 changes: 1 addition & 3 deletions app/i18n/en-US/onboarding.json
Original file line number Diff line number Diff line change
Expand Up @@ -129,9 +129,7 @@
"Don't have an account? <link>Create one</link>": "Don't have an account? <link>Create one</link>",
"Welcome to Streamlabs Desktop": "Welcome to Streamlabs Desktop",
"Access all the tools you need, including overlays, alerts, automatic clips, sponsorships, and more": "Access all the tools you need, including overlays, alerts, automatic clips, sponsorships, and more",
"Create an account": "Create an account",
"Already have an account? <link>Log In</link>": "Already have an account? <link>Log In</link>",
"Just looking to record? <link>Start here</link>": "Just looking to record? <link>Start here</link>",
"Get Started": "Get Started",
"Choose Your Overlay": "Choose Your Overlay",
"Browse All Overlays": "Browse All Overlays",
"AI-powered reactions to in-game events": "AI-powered reactions to in-game events",
Expand Down
15 changes: 13 additions & 2 deletions app/services/onboarding/onboarding-v2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,8 +151,10 @@ class OnboardingPath {
if (this.singletonPath) return;
const fromCurrentStep = {
[EOnboardingSteps.Splash]: () => {
if (modifiers.recordingMode) return { name: EOnboardingSteps.RecordingLogin };
return { name: EOnboardingSteps.Login };
return {
name: modifiers.recordingMode ? EOnboardingSteps.RecordingLogin : EOnboardingSteps.Login,
isSkippable: modifiers.loggedIn,
};
Comment on lines +154 to +157
},
[EOnboardingSteps.RecordingLogin]: () => {
if (modifiers.obsInstalled) return { name: EOnboardingSteps.OBSImport };
Expand Down Expand Up @@ -263,6 +265,10 @@ export class OnboardingV2Service extends Service {
}

showOnboardingIfNecessary() {
if (Utils.env.SLD_TESTS_SKIP_ONBOARDING) {
this.appService.setOnboarded(true);
return;
}
if (!Utils.env.SLD_FORCE_ONBOARDING_STEP && localStorage.getItem(this.localStorageKey)) {
return;
}
Expand All @@ -277,12 +283,17 @@ export class OnboardingV2Service extends Service {
);

if (isValidStep) {
console.log('Forcing onboarding step:', Utils.env.SLD_FORCE_ONBOARDING_STEP);
this.initalizeView({
startingStep: { name: Utils.env.SLD_FORCE_ONBOARDING_STEP as EOnboardingSteps },
isSingleton: true,
});
return;
}

console.log('Unknown step, forcing full onboarding', Utils.env.SLD_FORCE_ONBOARDING_STEP);
} else {
console.log('Starting onboarding flow');
}

this.initalizeView({
Expand Down
3 changes: 3 additions & 0 deletions app/services/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ export interface IEnv {
HIGHLIGHTER_LOCAL_SETUP_PATH?: string;
PRODUCT_UPDATES: boolean;
AVATAR_ENV?: 'production' | 'staging' | 'local';

// Test automation variables
SLD_TESTS_SKIP_ONBOARDING?: 'true';
}

export default class Utils {
Expand Down
18 changes: 17 additions & 1 deletion app/services/vision/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,18 @@ export class VisionService extends Service {
return this.stop();
});

if (this.enabledState.isEnabled) {
this.userService.userLogin.subscribe(() => {
if (this.enabledState.isEnabled) {
void this.ensureRunning();
}
});

this.userService.userLogout.subscribe(() => {
this.log('Vision is not supported for logged-out users, stopping.');
void this.stop();
});

if (this.userService.isLoggedIn && this.enabledState.isEnabled) {
void this.ensureRunning();
}
}
Expand Down Expand Up @@ -223,6 +234,11 @@ export class VisionService extends Service {
return;
}

if (!this.userService.isLoggedIn) {
this.log('Vision is not supported for logged-out users.');
return;
}
Comment thread
Copilot marked this conversation as resolved.
Comment thread
Copilot marked this conversation as resolved.

const { isEnabled } = this.enabledState;

this.log('ensureRunning(): ' + JSON.stringify({ isEnabled, debugMode }));
Expand Down
47 changes: 43 additions & 4 deletions test/helpers/modules/onboarding.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,53 @@
import { useMainWindow, clickWhenDisplayed, isDisplayed } from './core';
import { sleep } from '../sleep';
import { TExecutionContext } from '../webdriver';
import { logIn } from '../webdriver/user';
import {
clickIfDisplayed,
clickWhenDisplayed,
focusMain,
isDisplayed,
useMainWindow,
waitForDisplayed,
} from './core';

export async function skipOnboarding() {
// eslint-disable-next-line react-hooks/rules-of-hooks
await useMainWindow(async () => {
// Onboarding doesn't reappear on app restarts, which some tests require
const onboardingAppeared = await isDisplayed('a=Log In', { timeout: 10000 });
const onboardingAppeared = await isDisplayed('button=Get Started', { timeout: 10000 });
if (onboardingAppeared) {
await clickWhenDisplayed('a=Log In', { timeout: 5000 });
await clickWhenDisplayed('button=Skip', { timeout: 5000 });
await clickWhenDisplayed('button=Get Started', { timeout: 5000 });
Comment thread
wesrupert marked this conversation as resolved.
await clickWhenDisplayed('button=Skip', { timeout: 5000 });
}
});
}

/**
* Helper function to go through the onboarding flow through the login step
* @param t Test execution context
* @param newUser Whether the user is a new user
*/
export async function advancePastOnboardingLogin(t: TExecutionContext, newUser = true) {
await focusMain();

if (!(await isDisplayed('h1=Welcome to Streamlabs Desktop'))) {
t.fail('Onboarding welcome page not shown');
return;
}
await clickWhenDisplayed('button=Get Started', { timeout: 5000 });

// Complete login
await isDisplayed('button=Twitch');
const user = await logIn(t, 'twitch', { prime: false }, false, true, newUser);
await sleep(1000);

// We seem to skip the login step after login internally.
// Navigate back to onboarding and re-check if the user can skip the login step.
await clickIfDisplayed('button=Back');
await waitForDisplayed('h1=Welcome to Streamlabs Desktop');
await clickWhenDisplayed('button=Get Started', { timeout: 5000 });
await isDisplayed('button=Twitch');
await clickIfDisplayed('button=Skip');

return user;
}
7 changes: 3 additions & 4 deletions test/helpers/webdriver/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,7 @@ import {
testFn,
waitForElectronInstancesExist,
} from './runner-utils';
import { skipOnboarding } from '../modules/onboarding';
import {
clickButton,
clickIfDisplayed,
closeWindow,
focusChild,
Expand Down Expand Up @@ -277,6 +275,9 @@ export function useWebdriver(options: ITestRunnerOptions = {}) {
app = t.context.app = new Application({
port: CHROMEDRIVER_PORT,
logLevel: CHROMEDRIVER_DEBUG ? 'debug' : 'silent',
runnerEnv: {
SLD_TESTS_SKIP_ONBOARDING: options.skipOnboarding ? 'true' : '',
},
Comment on lines +278 to +280
capabilities: {
browserName: 'chrome',
'goog:chromeOptions': {
Expand Down Expand Up @@ -342,8 +343,6 @@ export function useWebdriver(options: ITestRunnerOptions = {}) {
// tests will want to skip this flow, so we do it automatically.
await waitForLoader();

if (options.skipOnboarding) await skipOnboarding();

// disable the popups that prevents context menu to be shown
const client = await getApiClient();
const dismissablesService = client.getResource<DismissablesService>('DismissablesService');
Expand Down
Loading
Loading