Skip to content

Commit

Permalink
Twitch studio importer (#5010)
Browse files Browse the repository at this point in the history
* Twitch studio importer

* sizing fixes and error handling

* handle missing default data

* add import disclaimer modal

* fix video base res settings
  • Loading branch information
andycreeth committed May 24, 2024
1 parent b7565fb commit b90956b
Show file tree
Hide file tree
Showing 9 changed files with 525 additions and 36 deletions.
3 changes: 3 additions & 0 deletions app/app-services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export { ExternalApiLimitsService } from 'services/api/external-api-limits';
export { SourcesService, Source } from 'services/sources';
export { Scene, SceneItem, SceneItemFolder, ScenesService } from 'services/scenes';
export { ObsImporterService } from 'services/obs-importer';
export { TwitchStudioImporterService } from 'services/ts-importer';
export { ClipboardService } from 'services/clipboard';
export { AudioService, AudioSource } from 'services/audio';
export { HostsService, UrlService } from 'services/hosts';
Expand Down Expand Up @@ -203,6 +204,7 @@ import { MarkersService } from 'services/markers';
import { SharedStorageService } from 'services/integrations/shared-storage';
import { RealmService } from 'services/realm';
import { InstagramService } from 'services/platforms/instagram';
import { TwitchStudioImporterService } from 'services/ts-importer';

export const AppServices = {
AppService,
Expand Down Expand Up @@ -262,6 +264,7 @@ export const AppServices = {
LayoutService,
ProjectorService,
ObsImporterService,
TwitchStudioImporterService,
DefaultHardwareService,
AutoConfigService,
MacPermissionsService,
Expand Down
6 changes: 5 additions & 1 deletion app/components-react/pages/onboarding/Common.m.less
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,16 @@
margin-bottom: 0;
}

svg {
svg, i {
position: absolute;
bottom: -50%;
right: 0;
opacity: 0.25;
}

i {
color: #000;
}
}

.onboarding-button {
Expand Down
2 changes: 1 addition & 1 deletion app/components-react/pages/onboarding/FreshOrImport.m.less
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
.option-container {
display: flex;
justify-content: space-between;
width: 600px;
width: 900px;
}

.footer {
Expand Down
17 changes: 15 additions & 2 deletions app/components-react/pages/onboarding/FreshOrImport.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,12 @@ import styles from './FreshOrImport.m.less';
import commonStyles from './Common.m.less';
import ObsSvg from './ObsSvg';
import { OnboardingModule } from './Onboarding';
import PlatformLogo from 'components-react/shared/PlatformLogo';
import { Services } from 'components-react/service-provider';

export function FreshOrImport() {
const { setImportFromObs, next } = useModule(OnboardingModule);
const { setImportFromObs, next, setImportFromTwitch } = useModule(OnboardingModule);
const { TwitchStudioImporterService } = Services;

const optionsMetadata = [
{
Expand All @@ -25,6 +28,16 @@ export function FreshOrImport() {
next();
},
},
{
title: $t('Import from Twitch Studio'),
color: '--twitch',
description: $t('Import your scenes and sources from Twitch Studio.'),
image: <PlatformLogo platform="twitch" size={150} />,
onClick: async () => {
setImportFromTwitch();
next();
},
},
{
title: $t('Start Fresh'),
color: '--teal',
Expand All @@ -43,7 +56,7 @@ export function FreshOrImport() {
<img src={$i('images/onboarding/splash.png')} />
</div>
<div className={styles.contentContainer}>
<h1 className={styles.title}>{$t('1-Click Import from OBS')}</h1>
<h1 className={styles.title}>{$t('1-Click Import')}</h1>
<div className={styles.optionContainer}>
{optionsMetadata.map(data => (
<Tooltip title={data.description} placement="bottom" key={data.title}>
Expand Down
59 changes: 45 additions & 14 deletions app/components-react/pages/onboarding/ObsImport.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,15 @@ import { $t } from 'services/i18n';
import commonStyles from './Common.m.less';
import styles from './ObsImport.m.less';
import { OnboardingModule } from './Onboarding';
import { ObsImporterService } from 'app-services';

export function ObsImport() {
const { importing, percent } = useModule(ObsImportModule);
const { importing, percent, isObs } = useModule(ObsImportModule);

return (
<div style={{ width: '100%' }}>
<h1 className={commonStyles.titleContainer}>
{$t('Importing Your Existing Settings From OBS')}
{$t(`Importing Your Existing Settings From ${isObs ? 'OBS' : 'Twitch Studio'}`)}
</h1>
{!importing && <PreImport />}
{importing && (
Expand All @@ -29,7 +30,7 @@ export function ObsImport() {
<KevinSvg />
<div>
{$t(
'While we import your settings and scenes from OBS Studio, check out these great features unique to Streamlabs',
'While we import your settings and scenes, check out these great features unique to Streamlabs',
)}
</div>
</div>
Expand All @@ -40,7 +41,9 @@ export function ObsImport() {

function PreImport() {
const { setProcessing, next } = useModule(OnboardingModule);
const { profiles, selectedProfile, setSelectedProfile, startImport } = useModule(ObsImportModule);
const { profiles, selectedProfile, setSelectedProfile, startImport, isObs } = useModule(
ObsImportModule,
);

return (
<div>
Expand All @@ -61,8 +64,24 @@ function PreImport() {
<button
className={commonStyles.optionCard}
style={{ margin: 'auto', marginTop: 24 }}
onClick={() => {
onClick={async () => {
setProcessing(true);

if (!isObs) {
await alertAsync({
title: $t('Twitch Studio Import'),
content: (
<p>
{$t(
'Importing from Twitch Studio is an experimental feature under active development. Some source types are unable to be imported, and not all settings will be carried over.',
)}
</p>
),
okText: $t('Start'),
okType: 'primary',
});
}

startImport()
.then(() => {
setProcessing(false);
Expand All @@ -72,7 +91,7 @@ function PreImport() {
setProcessing(false);
alertAsync(
$t(
'Something went wrong while importing from OBS Studio. Please try again or skip to the next step.',
'Something went wrong while importing. Please try again or skip to the next step.',
),
);
});
Expand Down Expand Up @@ -134,23 +153,35 @@ class ObsImportModule {
});

init() {
// Intentionally synchronous
const profiles = this.ObsImporterService.getProfiles();
this.setProfiles(profiles);
this.setSelectedProfile(profiles[0] ?? null);
if (this.isObs) {
const service = this.importService as ObsImporterService;
// Intentionally synchronous
const profiles = service.getProfiles();
this.setProfiles(profiles);
this.setSelectedProfile(profiles[0] ?? null);
}
}

get importService() {
if (this.isObs) {
return Services.ObsImporterService;
} else {
return Services.TwitchStudioImporterService;
}
}

get ObsImporterService() {
return Services.ObsImporterService;
get isObs() {
return Services.OnboardingService.state.importedFrom === 'obs';
}

startImport() {
if (this.state.importing) return Promise.resolve();
if (!this.state.selectedProfile) return Promise.resolve();
if (this.isObs && !this.state.selectedProfile) return Promise.resolve();

this.setImporting(true);

return this.ObsImporterService.load(this.state.selectedProfile)
return this.importService
.load(this.state.selectedProfile!)
.then(r => {
this.setImporting(false);
return r;
Expand Down
6 changes: 5 additions & 1 deletion app/components-react/pages/onboarding/Onboarding.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,11 @@ export class OnboardingModule {
}

setImportFromObs() {
this.OnboardingService.setObsImport(true);
this.OnboardingService.setImport('obs');
}

setImportFromTwitch() {
this.OnboardingService.setImport('twitch');
}

setStreamerKnowledgeMode(mode: StreamerKnowledgeMode | null) {
Expand Down
40 changes: 25 additions & 15 deletions app/services/onboarding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
isBeginnerOrIntermediateOrUnselected,
isIntermediateOrAdvancedOrUnselected,
} from './onboarding/knowledge-mode';
import { TwitchStudioImporterService } from './ts-importer';
export { StreamerKnowledgeMode } from './onboarding/knowledge-mode';

enum EOnboardingSteps {
Expand Down Expand Up @@ -70,20 +71,24 @@ export const ONBOARDING_STEPS = () => ({
component: 'FreshOrImport' as const,
hideButton: true,
isPreboarding: true,
cond: ({ isObsInstalled, recordingModeEnabled }: OnboardingStepContext) =>
isObsInstalled && !recordingModeEnabled,
cond: ({
isObsInstalled,
isTwitchStudioInstalled,
recordingModeEnabled,
}: OnboardingStepContext) =>
(isObsInstalled || isTwitchStudioInstalled) && !recordingModeEnabled,
},
[EOnboardingSteps.ObsImport]: {
component: 'ObsImport' as const,
hideButton: true,
label: $t('Import'),
cond: ({ importedFromObs, isObsInstalled }: OnboardingStepContext) =>
importedFromObs && isObsInstalled,
cond: ({ importedFrom, isObsInstalled, isTwitchStudioInstalled }: OnboardingStepContext) =>
importedFrom && (isObsInstalled || isTwitchStudioInstalled),
},
[EOnboardingSteps.HardwareSetup]: {
component: 'HardwareSetup' as const,
label: $t('Set Up Mic and Webcam'),
cond: ({ importedFromObs }: OnboardingStepContext) => !importedFromObs,
cond: ({ importedFrom }: OnboardingStepContext) => !importedFrom,
isSkippable: true,
},
[EOnboardingSteps.ThemeSelector]: {
Expand All @@ -93,12 +98,12 @@ export const ONBOARDING_STEPS = () => ({
cond: ({
isLoggedIn,
existingSceneCollections,
importedFromObs,
importedFrom,
recordingModeEnabled,
platformSupportsThemes,
}: OnboardingStepContext) =>
!existingSceneCollections &&
!importedFromObs &&
!importedFrom &&
!recordingModeEnabled &&
((isLoggedIn && platformSupportsThemes) || !isLoggedIn),
isSkippable: true,
Expand Down Expand Up @@ -132,8 +137,9 @@ export interface OnboardingStepContext {
isPartialSLAuth: boolean;
existingSceneCollections: boolean;
isObsInstalled: boolean;
isTwitchStudioInstalled: boolean;
recordingModeEnabled: boolean;
importedFromObs: boolean;
importedFrom: 'obs' | 'twitch';
isLoggedIn: boolean;
isUltra: boolean;
platformSupportsThemes: boolean;
Expand Down Expand Up @@ -171,12 +177,14 @@ interface IOnboardingOptions {

interface IOnboardingServiceState {
options: IOnboardingOptions;
importedFromObs: boolean;
importedFrom: 'obs' | 'twitch';
existingSceneCollections: boolean;
streamerKnowledgeMode: StreamerKnowledgeMode | null;
}

class OnboardingViews extends ViewHandler<IOnboardingServiceState> {
@Inject() twitchStudioImporterService: TwitchStudioImporterService;

get singletonStep(): IOnboardingStep {
if (this.state.options.isLogin) {
if (this.getServiceViews(UserService).isPartialSLAuth) {
Expand All @@ -194,18 +202,20 @@ class OnboardingViews extends ViewHandler<IOnboardingServiceState> {
get steps() {
const userViews = this.getServiceViews(UserService);
const isOBSinstalled = this.getServiceViews(ObsImporterService).isOBSinstalled();
const isTwitchStudioInstalled = this.twitchStudioImporterService.isTwitchStudioIntalled();
const recordingModeEnabled = this.getServiceViews(RecordingModeService).isRecordingModeEnabled;

const streamerKnowledgeMode = this.streamerKnowledgeMode;

const { existingSceneCollections, importedFromObs } = this.state;
const { existingSceneCollections, importedFrom } = this.state;
const { isLoggedIn, isPrime: isUltra } = userViews;

const ctx: OnboardingStepContext = {
streamerKnowledgeMode,
recordingModeEnabled,
existingSceneCollections,
importedFromObs,
importedFrom,
isTwitchStudioInstalled,
isLoggedIn,
isUltra,
isObsInstalled: isOBSinstalled,
Expand Down Expand Up @@ -313,7 +323,7 @@ export class OnboardingService extends StatefulService<IOnboardingServiceState>
isHardware: false,
isImport: false,
},
importedFromObs: false,
importedFrom: null,
existingSceneCollections: false,
streamerKnowledgeMode: null,
};
Expand All @@ -333,8 +343,8 @@ export class OnboardingService extends StatefulService<IOnboardingServiceState>
}

@mutation()
SET_OBS_IMPORTED(val: boolean) {
this.state.importedFromObs = val;
SET_OBS_IMPORTED(val: 'obs' | 'twitch') {
this.state.importedFrom = val;
}

@mutation()
Expand Down Expand Up @@ -383,7 +393,7 @@ export class OnboardingService extends StatefulService<IOnboardingServiceState>
this.setExistingCollections();
}

setObsImport(val: boolean) {
setImport(val: 'obs' | 'twitch') {
this.SET_OBS_IMPORTED(val);
}

Expand Down
4 changes: 2 additions & 2 deletions app/services/scene-collections/scene-collections.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ interface ISceneCollectionInternalCreateOptions extends ISceneCollectionCreateOp
/** A function that can be used to set up some state.
* This should really only be used by the OBS importer.
*/
setupFunction?: () => boolean;
setupFunction?: () => boolean | Promise<boolean>;

auto?: boolean;
}
Expand Down Expand Up @@ -225,7 +225,7 @@ export class SceneCollectionsService extends Service implements ISceneCollection
await this.setActiveCollection(id);
if (options.needsRename) this.stateService.SET_NEEDS_RENAME(id);

if (options.setupFunction && options.setupFunction()) {
if (options.setupFunction && (await options.setupFunction())) {
// Do nothing
} else {
this.setupEmptyCollection();
Expand Down
Loading

0 comments on commit b90956b

Please sign in to comment.