From fcc690c4293f8402f700b49a2957641c89fe9e70 Mon Sep 17 00:00:00 2001 From: Sean Beyer Date: Mon, 18 Dec 2023 13:58:45 -0800 Subject: [PATCH 01/51] Port LiveDock Component --- app/components-react/root/LiveDock.m.less | 178 ++++++++++ app/components-react/root/LiveDock.tsx | 405 ++++++++++++++++++++++ app/components/LiveDock.vue | 308 ---------------- app/components/LiveDock.vue.ts | 309 ----------------- 4 files changed, 583 insertions(+), 617 deletions(-) create mode 100644 app/components-react/root/LiveDock.m.less create mode 100644 app/components-react/root/LiveDock.tsx delete mode 100644 app/components/LiveDock.vue delete mode 100644 app/components/LiveDock.vue.ts diff --git a/app/components-react/root/LiveDock.m.less b/app/components-react/root/LiveDock.m.less new file mode 100644 index 000000000000..7868dc9bca2b --- /dev/null +++ b/app/components-react/root/LiveDock.m.less @@ -0,0 +1,178 @@ +@import '../../styles/index'; + +.live-dock { + padding-left: 16px; + position: relative; + z-index: 1000; + width: 28%; + box-sizing: border-box; + border-left: 1px solid var(--border); + + &.can-animate { + transition: width 300ms; + } + + &.live-dock--left { + padding-left: 0; + padding-right: 16px; + border-right: 1px solid var(--border); + background-color: var(--section); + } + + @media (max-width: 1070px) { + display: none; + } +} + +.live-dock--left { + .live-dock-chevron { + right: 0; + left: auto; + } + + .live-dock-expanded-contents { + border-right: 1px solid var(--border); + border-left: none; + } +} + +.live-dock.collapsed { + width: 20px !important; + padding: 0; + + .live-dock-chevron { + .center(); + + border: none; + } +} + +.live-dock-chevron { + width: 16px; + height: 20px; + position: absolute; + top: 0; + left: 0; + border-bottom: 1px solid var(--border); + cursor: pointer; + + i { + .center(); + + font-size: 12px; + + &.icon-right { + transform: translate(-50%, -50%) rotate(-90deg); + } + } +} + +.live-dock-end-stream { + margin-left: 10px; +} + +.live-dock-header { + .margin-bottom(); + + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; +} + +.live-dock-text { + .weight(@medium); + + margin: 0 2px 0 4px; +} + +.live-dock-expanded-contents { + display: flex; + flex-direction: column; + height: 100%; + padding: 16px; + border-left: 1px solid var(--border); +} + +.live-dock-info { + .margin-bottom(); + + display: flex; + justify-content: space-between; + + .live-dock-platform-tools { + a { + padding: 0 8px; + } + } +} + +.live-dock-viewer-count { + .flex(); + .flex--center(); + + i { + .margin-right(); + } + + .live-dock-viewer-count-toggle { + opacity: 0; + cursor: pointer; + } + + &:hover { + .live-dock-viewer-count-toggle { + opacity: 1; + } + } +} + +.live-dock-viewer-count__count { + padding-right: 3px; +} + +.live-dock-chat { + .flex(); + .flex--column(); + .flex--grow(); +} + +.live-dock-chat--offline { + height: 100%; +} + +.live-dock-chat__img--offline { + .flex(); + .flex--center(); + .flex--column(); + + width: 60%; + margin-bottom: 16px; +} + +.live-dock-pulse { + width: 10px; + height: 10px; + border-radius: 50%; + background: var(--warning); + margin: 0 8px; + box-shadow: 0 0 0 rgba(252, 62, 63, 0.4); + + &.live-dock-offline { + background: var(--icon); + animation: none; + } +} + +.live-dock-platform-tools { + .flex(); +} + +.live-dock-chat-apps__popout { + .padding(); + .cursor--pointer(); +} + +.live-dock-platform-app-webview { + .flex--grow(); +} diff --git a/app/components-react/root/LiveDock.tsx b/app/components-react/root/LiveDock.tsx new file mode 100644 index 000000000000..8a9a91585ee2 --- /dev/null +++ b/app/components-react/root/LiveDock.tsx @@ -0,0 +1,405 @@ +import React, { useEffect, useMemo } from 'react'; +import * as remote from '@electron/remote'; +import cx from 'classnames'; +import Animation from 'rc-animate'; +// import Tabs, { ITab } from 'components/Tabs.vue'; +import { initStore, useController } from 'components-react/hooks/zustand'; +import { EStreamingState } from 'services/streaming'; +import { EAppPageSlot, ILoadedApp } from 'services/platform-apps'; +import { EPlatform, TPlatform, getPlatformService } from 'services/platforms'; +import { $t } from 'services/i18n'; +import { Services } from '../service-provider'; +import Chat from './Chat'; +import styles from './LiveDock.m.less'; +import Tooltip from 'components-react/shared/Tooltip'; + +const LiveDockCtx = React.createContext(null); + +class LiveDockController { + private streamingService = Services.StreamingService; + private youtubeService = Services.YoutubeService; + private facebookService = Services.FacebookService; + private trovoService = Services.TrovoService; + private userService = Services.UserService; + private customizationService = Services.CustomizationService; + private platformAppsService = Services.PlatformAppsService; + private appService = Services.AppService; + private chatService = Services.ChatService; + private windowsService = Services.WindowsService; + private restreamService = Services.RestreamService; + + store = initStore({ elapsedStreamTime: '', canAnimate: false, slot: EAppPageSlot.Chat }); + + // Safe getter/setter prevents getting stuck on the chat + // for an app that was unloaded. + private underlyingSelectedChat = 'default'; + + get selectedChat() { + if ( + this.underlyingSelectedChat === 'default' && + this.isPlatform('twitter') && + this.isRestreaming + ) { + return 'restream'; + } + if (this.underlyingSelectedChat === 'default') return 'default'; + if (this.underlyingSelectedChat === 'restream') { + if (this.restreamService.shouldGoLiveWithRestream) return 'restream'; + return 'default'; + } + return this.chatApps.find(app => app.id === this.underlyingSelectedChat) + ? this.underlyingSelectedChat + : 'default'; + } + + get applicationLoading() { + return this.appService.state.loading; + } + + get streamingStatus() { + return this.streamingService.state.streamingStatus; + } + + get isStreaming() { + return this.streamingService.isStreaming; + } + + get collapsed() { + return this.customizationService.state.livedockCollapsed; + } + + get liveText() { + if (this.streamingStatus === EStreamingState.Live) return 'Live'; + if (this.streamingStatus === EStreamingState.Starting) return 'Starting'; + if (this.streamingStatus === EStreamingState.Ending) return 'Ending'; + if (this.streamingStatus === EStreamingState.Reconnecting) return 'Reconnecting'; + return 'Offline'; + } + + get platform() { + return this.userService.platform?.type; + } + + get offlineImageSrc() { + const mode = this.customizationService.isDarkTheme ? 'night' : 'day'; + return require(`../../media/images/sleeping-kevin-${mode}.png`); + } + + get hideViewerCount() { + return this.customizationService.state.hideViewerCount; + } + + get liveDockSize() { + return this.customizationService.state.livedockSize; + } + + get viewerCount() { + if (this.hideViewerCount) { + return 'viewers hidden'; + } + return this.streamingService.views.viewerCount.toString(); + } + + get hideStyleBlockers() { + return this.windowsService.state.main.hideStyleBlockers; + } + + get hasChatTabs() { + return this.chatTabs.length > 1; + } + + get showDefaultPlatformChat() { + return this.selectedChat === 'default'; + } + + get restreamChatUrl() { + return this.restreamService.chatUrl; + } + + get chatApps(): ILoadedApp[] { + return this.platformAppsService.enabledApps.filter(app => { + return !!app.manifest.pages.find(page => { + return page.slot === EAppPageSlot.Chat; + }); + }); + } + + get chatTabs(): ITab[] { + if (!this.userService.state.auth) return []; + const tabs: ITab[] = [ + { + name: getPlatformService(this.userService.state.auth.primaryPlatform).displayName, + value: 'default', + }, + ].concat( + this.chatApps + .filter(app => !app.poppedOutSlots.includes(this.store.slot)) + .map(app => { + return { + name: app.manifest.name, + value: app.id, + }; + }), + ); + if (this.restreamService.shouldGoLiveWithRestream) { + tabs.push({ + name: $t('Multistream'), + value: 'restream', + }); + } + if (this.userService.state.auth.primaryPlatform === 'twitter') { + // Twitter is the only primary platform without a chat + return tabs.slice(1); + } + return tabs; + } + + get isRestreaming() { + return this.restreamService.shouldGoLiveWithRestream; + } + + get isPopOutAllowed() { + if (this.showDefaultPlatformChat) return false; + if (this.selectedChat === 'restream') return false; + const chatPage = this.platformAppsService.views + .getApp(this.selectedChat) + .manifest.pages.find(page => page.slot === EAppPageSlot.Chat); + if (!chatPage) return false; + // Default result is true + return chatPage.allowPopout == null ? true : chatPage.allowPopout; + } + + get canEditChannelInfo(): boolean { + // Twitter doesn't support editing title after going live + if (this.isPlatform('twitter') && !this.isRestreaming) return false; + return ( + this.streamingService.views.isMidStreamMode || + this.userService.state.auth?.primaryPlatform === 'twitch' + ); + } + + getElapsedStreamTime() { + return this.streamingService.formattedDurationInCurrentStreamingState; + } + + isPlatform(platforms: TPlatform | TPlatform[]) { + if (!this.platform) return false; + if (Array.isArray(platforms)) return platforms.includes(this.platform); + return this.platform === platforms; + } + + openPlatformStream() { + let url = ''; + if (this.platform === 'youtube') url = this.youtubeService.streamPageUrl; + if (this.platform === 'facebook') url = this.facebookService.streamPageUrl; + if (this.platform === 'trovo') url = this.trovoService.streamPageUrl; + remote.shell.openExternal(url); + } + + openPlatformDash() { + let url = ''; + if (this.platform === 'youtube') url = this.youtubeService.dashboardUrl; + if (this.platform === 'facebook') url = this.facebookService.streamDashboardUrl; + remote.shell.openExternal(url); + } + + refreshChat() { + if (this.selectedChat === 'default') { + this.chatService.refreshChat(); + return; + } + if (this.selectedChat === 'restream') { + this.restreamService.refreshChat(); + return; + } + this.platformAppsService.refreshApp(this.selectedChat); + } + + popOut() { + this.platformAppsService.popOutAppPage(this.selectedChat, this.store.slot); + this.underlyingSelectedChat = 'default'; + } + + setCollapsed(livedockCollapsed: boolean) { + this.store.setState(s => { + s.canAnimate = true; + }); + this.windowsService.actions.updateStyleBlockers('main', true); + this.customizationService.actions.setSettings({ livedockCollapsed }); + setTimeout(() => { + this.store.setState(s => { + s.canAnimate = false; + }); + this.windowsService.actions.updateStyleBlockers('main', false); + }, 300); + } + + toggleViewerCount() { + this.customizationService.setHiddenViewerCount( + !this.customizationService.state.hideViewerCount, + ); + } + + showEditStreamInfo() { + this.streamingService.actions.showEditStream(); + } +} + +export default function LiveDockWithContext(p: { onLeft?: boolean }) { + const controller = useMemo(() => new LiveDockController(), []); + return ( + + + + ); +} + +function LiveDock(p: { onLeft: boolean } = { onLeft: false }) { + const ctrl = useController(LiveDockCtx); + + useEffect(() => { + const elapsedInterval = window.setInterval(() => { + if (ctrl.streamingStatus === EStreamingState.Live) { + ctrl.store.setState(s => { + s.elapsedStreamTime = ctrl.getElapsedStreamTime(); + }); + } else { + ctrl.store.setState(s => { + s.elapsedStreamTime = ''; + }); + } + }, 100); + + return () => clearInterval(elapsedInterval); + }, []); + + useEffect(() => { + if (ctrl.streamingStatus === EStreamingState.Starting && ctrl.collapsed) { + ctrl.setCollapsed(false); + } + }, [ctrl.streamingStatus]); + + // controlRoomTooltip = $t('Go to YouTube Live Dashboard'); + // liveProducerTooltip = $t('Go to the Facebook Live Producer Dashboard'); + + function toggleCollapsed() { + ctrl.collapsed ? ctrl.setCollapsed(false) : ctrl.setCollapsed(true); + } + + const { collapsed, isPlatform, isStreaming } = ctrl; + + return ( +
+
+ +
+ + {!collapsed && ( +
+
+
+
+ {ctrl.liveText} + {ctrl.store.elapsedStreamTime} +
+
+ ctrl.toggleViewerCount()} + /> + {ctrl.viewerCount} + {Number(ctrl.viewerCount) >= 0 && {$t('viewers')}} +
+
+ +
+
+ {ctrl.canEditChannelInfo && ( + + ctrl.showEditStreamInfo()} className="icon-edit" /> + + )} + {isPlatform(['youtube', 'facebook', 'trovo']) && isStreaming && ( + + ctrl.openPlatformStream()} className="icon-studio" /> + + )} + {isPlatform(['youtube', 'facebook']) && isStreaming && ( + + ctrl.openPlatformDash()} className="icon-settings" /> + + )} +
+
+ {(isPlatform(['twitch', 'trovo', 'facebook']) || + (isPlatform(['youtube', 'twitter']) && isStreaming)) && ( + ctrl.refreshChat()}>{$t('Refresh Chat')} + )} +
+
+ {!ctrl.hideStyleBlockers && + (isPlatform(['twitch', 'trovo']) || + (isStreaming && isPlatform(['youtube', 'facebook', 'twitter']))) && ( +
+ {ctrl.hasChatTabs && ( +
+ {/* */} + {ctrl.isPopOutAllowed && ( + + ctrl.popOut()} + /> + + )} +
+ )} + {/* */} + {!ctrl.applicationLoading && !collapsed && ( + + )} + {!['default', 'restream'].includes(ctrl.selectedChat) && ( + + )} +
+ )} + {(!ctrl.platform || + (isPlatform(['youtube', 'facebook', 'twitter']) && !isStreaming)) && ( +
+ + {!ctrl.hideStyleBlockers && {$t('Your chat is currently offline')}} +
+ )} +
+ )} + +
+ ); +} diff --git a/app/components/LiveDock.vue b/app/components/LiveDock.vue deleted file mode 100644 index 5e179a8319bf..000000000000 --- a/app/components/LiveDock.vue +++ /dev/null @@ -1,308 +0,0 @@ - - - - - diff --git a/app/components/LiveDock.vue.ts b/app/components/LiveDock.vue.ts deleted file mode 100644 index dc78908a26cd..000000000000 --- a/app/components/LiveDock.vue.ts +++ /dev/null @@ -1,309 +0,0 @@ -import Vue from 'vue'; -import { Component, Prop, Watch } from 'vue-property-decorator'; -import { Chat, PlatformAppPageView } from 'components/shared/ReactComponentList'; -import { StreamingService, EStreamingState } from '../services/streaming'; -import { Inject } from 'services/core/injector'; -import { UserService } from '../services/user'; -import { CustomizationService } from 'services/customization'; -import { $t } from 'services/i18n'; -import { PlatformAppsService, EAppPageSlot, ILoadedApp } from 'services/platform-apps'; -import ListInput from 'components/shared/inputs/ListInput.vue'; -import { AppService } from 'services/app'; -import Tabs, { ITab } from 'components/Tabs.vue'; -import { ChatService } from 'services/chat'; -import { WindowsService } from 'services/windows'; -import { FacebookService, RestreamService, TrovoService, YoutubeService } from 'app-services'; -import { getPlatformService } from 'services/platforms'; -import * as remote from '@electron/remote'; - -@Component({ - components: { - Chat, - ListInput, - PlatformAppPageView, - Tabs, - }, -}) -export default class LiveDock extends Vue { - @Inject() streamingService: StreamingService; - @Inject() youtubeService: YoutubeService; - @Inject() facebookService: FacebookService; - @Inject() trovoService: TrovoService; - @Inject() userService: UserService; - @Inject() customizationService: CustomizationService; - @Inject() platformAppsService: PlatformAppsService; - @Inject() appService: AppService; - @Inject() chatService: ChatService; - @Inject() windowsService: WindowsService; - @Inject() restreamService: RestreamService; - - @Prop({ default: false }) - onLeft: boolean; - - elapsedStreamTime = ''; - elapsedInterval: number; - canAnimate = false; - - slot = EAppPageSlot.Chat; - - // Safe getter/setter prevents getting stuck on the chat - // for an app that was unloaded. - underlyingSelectedChat = 'default'; - - get selectedChat() { - if (this.underlyingSelectedChat === 'default' && this.isTwitter && this.isRestreaming) { - return 'restream'; - } - - if (this.underlyingSelectedChat === 'default') return 'default'; - if (this.underlyingSelectedChat === 'restream') { - if (this.restreamService.shouldGoLiveWithRestream) return 'restream'; - return 'default'; - } - - return this.chatApps.find(app => app.id === this.underlyingSelectedChat) - ? this.underlyingSelectedChat - : 'default'; - } - - set selectedChat(val: string) { - this.underlyingSelectedChat = val; - } - - viewStreamTooltip = $t('View your live stream in a web browser'); - editStreamInfoTooltip = $t('Edit your stream title and description'); - controlRoomTooltip = $t('Go to YouTube Live Dashboard'); - liveProducerTooltip = $t('Go to the Facebook Live Producer Dashboard'); - - mounted() { - this.elapsedInterval = window.setInterval(() => { - if (this.streamingStatus === EStreamingState.Live) { - this.elapsedStreamTime = this.getElapsedStreamTime(); - } else { - this.elapsedStreamTime = ''; - } - }, 100); - } - - get applicationLoading() { - return this.appService.state.loading; - } - - beforeDestroy() { - clearInterval(this.elapsedInterval); - } - - get streamingStatus() { - return this.streamingService.state.streamingStatus; - } - - @Watch('streamingStatus') - onStreamingStatusChange() { - if (this.streamingStatus === EStreamingState.Starting) { - this.setCollapsed(false); - } - } - - getElapsedStreamTime() { - return this.streamingService.formattedDurationInCurrentStreamingState; - } - - get collapsed() { - return this.customizationService.state.livedockCollapsed; - } - - setCollapsed(livedockCollapsed: boolean) { - this.canAnimate = true; - this.windowsService.actions.updateStyleBlockers('main', true); - this.customizationService.actions.setSettings({ livedockCollapsed }); - setTimeout(() => { - this.canAnimate = false; - this.windowsService.actions.updateStyleBlockers('main', false); - }, 300); - } - - get isStreaming() { - return this.streamingService.isStreaming; - } - - get liveText() { - if (this.streamingStatus === EStreamingState.Live) return 'Live'; - if (this.streamingStatus === EStreamingState.Starting) return 'Starting'; - if (this.streamingStatus === EStreamingState.Ending) return 'Ending'; - if (this.streamingStatus === EStreamingState.Reconnecting) return 'Reconnecting'; - return 'Offline'; - } - - get viewerCount() { - if (this.hideViewerCount) { - return 'viewers hidden'; - } - - return this.streamingService.views.viewerCount.toString(); - } - - get offlineImageSrc() { - const mode = this.customizationService.isDarkTheme ? 'night' : 'day'; - return require(`../../media/images/sleeping-kevin-${mode}.png`); - } - - showEditStreamInfo() { - this.streamingService.actions.showEditStream(); - } - - openYoutubeStreamUrl() { - remote.shell.openExternal(this.youtubeService.streamPageUrl); - } - - openYoutubeControlRoom() { - remote.shell.openExternal(this.youtubeService.dashboardUrl); - } - - openFBStreamUrl() { - remote.shell.openExternal(this.facebookService.streamPageUrl); - } - - openFBStreamDashboardUrl() { - remote.shell.openExternal(this.facebookService.streamDashboardUrl); - } - - openTrovoStreamUrl() { - remote.shell.openExternal(this.trovoService.streamPageUrl); - } - - get isTwitch() { - return this.userService.platform.type === 'twitch'; - } - - get isYoutube() { - return this.userService.platform.type === 'youtube'; - } - - get isFacebook() { - return this.userService.platform.type === 'facebook'; - } - - get isTrovo() { - return this.userService.platform.type === 'trovo'; - } - - get isTwitter() { - return this.userService.platform.type === 'twitter'; - } - - get hideViewerCount() { - return this.customizationService.state.hideViewerCount; - } - - get liveDockSize() { - return this.customizationService.state.livedockSize; - } - - toggleViewerCount() { - this.customizationService.setHiddenViewerCount( - !this.customizationService.state.hideViewerCount, - ); - } - - refreshChat() { - if (this.selectedChat === 'default') { - this.chatService.refreshChat(); - return; - } - - if (this.selectedChat === 'restream') { - this.restreamService.refreshChat(); - return; - } - - this.platformAppsService.refreshApp(this.selectedChat); - } - - get hideStyleBlockers() { - return this.windowsService.state.main.hideStyleBlockers; - } - - get hasChatTabs() { - return this.chatTabs.length > 1; - } - - get showDefaultPlatformChat() { - return this.selectedChat === 'default'; - } - - get restreamChatUrl() { - return this.restreamService.chatUrl; - } - - get chatApps(): ILoadedApp[] { - return this.platformAppsService.enabledApps.filter(app => { - return !!app.manifest.pages.find(page => { - return page.slot === EAppPageSlot.Chat; - }); - }); - } - - get chatTabs(): ITab[] { - const tabs: ITab[] = [ - { - name: getPlatformService(this.userService.state.auth.primaryPlatform).displayName, - value: 'default', - }, - ].concat( - this.chatApps - .filter(app => !app.poppedOutSlots.includes(this.slot)) - .map(app => { - return { - name: app.manifest.name, - value: app.id, - }; - }), - ); - - if (this.restreamService.shouldGoLiveWithRestream) { - tabs.push({ - name: $t('Multistream'), - value: 'restream', - }); - } - - if (this.userService.state.auth.primaryPlatform === 'twitter') { - // Twitter is the only primary platform without a chat - return tabs.slice(1); - } - - return tabs; - } - - get isRestreaming() { - return this.restreamService.shouldGoLiveWithRestream; - } - - get isPopOutAllowed() { - if (this.showDefaultPlatformChat) return false; - if (this.selectedChat === 'restream') return false; - - const chatPage = this.platformAppsService.views - .getApp(this.selectedChat) - .manifest.pages.find(page => page.slot === EAppPageSlot.Chat); - if (!chatPage) return false; - - // Default result is true - return chatPage.allowPopout == null ? true : chatPage.allowPopout; - } - - popOut() { - this.platformAppsService.popOutAppPage(this.selectedChat, this.slot); - this.selectedChat = 'default'; - } - - get canEditChannelInfo(): boolean { - // Twitter doesn't support editing title after going live - if (this.isTwitter && !this.isRestreaming) return false; - - return ( - this.streamingService.views.isMidStreamMode || - this.userService.state.auth?.primaryPlatform === 'twitch' - ); - } -} From 677b05459035891727306df8903259f6696a0dac Mon Sep 17 00:00:00 2001 From: Sean Beyer Date: Mon, 18 Dec 2023 15:07:51 -0800 Subject: [PATCH 02/51] Fix import errors and tabs --- app/components-react/root/LiveDock.tsx | 43 +++++++++++++------ .../shared/PlatformAppPageView.tsx | 2 + 2 files changed, 31 insertions(+), 14 deletions(-) diff --git a/app/components-react/root/LiveDock.tsx b/app/components-react/root/LiveDock.tsx index 8a9a91585ee2..aa76f1ff165b 100644 --- a/app/components-react/root/LiveDock.tsx +++ b/app/components-react/root/LiveDock.tsx @@ -2,16 +2,17 @@ import React, { useEffect, useMemo } from 'react'; import * as remote from '@electron/remote'; import cx from 'classnames'; import Animation from 'rc-animate'; -// import Tabs, { ITab } from 'components/Tabs.vue'; +import { Menu } from 'antd'; import { initStore, useController } from 'components-react/hooks/zustand'; import { EStreamingState } from 'services/streaming'; import { EAppPageSlot, ILoadedApp } from 'services/platform-apps'; -import { EPlatform, TPlatform, getPlatformService } from 'services/platforms'; +import { TPlatform, getPlatformService } from 'services/platforms'; import { $t } from 'services/i18n'; import { Services } from '../service-provider'; import Chat from './Chat'; import styles from './LiveDock.m.less'; import Tooltip from 'components-react/shared/Tooltip'; +import PlatformAppPageView from 'components-react/shared/PlatformAppPageView'; const LiveDockCtx = React.createContext(null); @@ -28,27 +29,34 @@ class LiveDockController { private windowsService = Services.WindowsService; private restreamService = Services.RestreamService; - store = initStore({ elapsedStreamTime: '', canAnimate: false, slot: EAppPageSlot.Chat }); + store = initStore({ + elapsedStreamTime: '', + canAnimate: false, + slot: EAppPageSlot.Chat, + underlyingSelectedChat: 'default', + }); // Safe getter/setter prevents getting stuck on the chat // for an app that was unloaded. - private underlyingSelectedChat = 'default'; + setChat(key: string) { + this.store.setState(s => (s.underlyingSelectedChat = key)); + } get selectedChat() { if ( - this.underlyingSelectedChat === 'default' && + this.store.underlyingSelectedChat === 'default' && this.isPlatform('twitter') && this.isRestreaming ) { return 'restream'; } - if (this.underlyingSelectedChat === 'default') return 'default'; - if (this.underlyingSelectedChat === 'restream') { + if (this.store.underlyingSelectedChat === 'default') return 'default'; + if (this.store.underlyingSelectedChat === 'restream') { if (this.restreamService.shouldGoLiveWithRestream) return 'restream'; return 'default'; } - return this.chatApps.find(app => app.id === this.underlyingSelectedChat) - ? this.underlyingSelectedChat + return this.chatApps.find(app => app.id === this.store.underlyingSelectedChat) + ? this.store.underlyingSelectedChat : 'default'; } @@ -124,9 +132,9 @@ class LiveDockController { }); } - get chatTabs(): ITab[] { + get chatTabs(): { name: string; value: string }[] { if (!this.userService.state.auth) return []; - const tabs: ITab[] = [ + const tabs: { name: string; value: string }[] = [ { name: getPlatformService(this.userService.state.auth.primaryPlatform).displayName, value: 'default', @@ -217,7 +225,7 @@ class LiveDockController { popOut() { this.platformAppsService.popOutAppPage(this.selectedChat, this.store.slot); - this.underlyingSelectedChat = 'default'; + this.setChat('default'); } setCollapsed(livedockCollapsed: boolean) { @@ -362,7 +370,15 @@ function LiveDock(p: { onLeft: boolean } = { onLeft: false }) {
{ctrl.hasChatTabs && (
- {/* */} + ctrl.setChat(ev.key)} + mode="horizontal" + > + {ctrl.chatTabs.map(tab => ( + {tab.name} + ))} + {ctrl.isPopOutAllowed && ( )} - {/* */} {!ctrl.applicationLoading && !collapsed && ( )} diff --git a/app/components-react/shared/PlatformAppPageView.tsx b/app/components-react/shared/PlatformAppPageView.tsx index 35d2f647378b..6ca4ccf40b49 100644 --- a/app/components-react/shared/PlatformAppPageView.tsx +++ b/app/components-react/shared/PlatformAppPageView.tsx @@ -10,6 +10,7 @@ import styles from './PlatformAppPageView.m.less'; export default function PlatformAppPageView(p: { appId: string; pageSlot: EAppPageSlot; + className?: string; style?: React.CSSProperties; }) { const { PlatformAppsService, WindowsService } = Services; @@ -108,6 +109,7 @@ export default function PlatformAppPageView(p: { width: '100%', ...p.style, }} + className={p.className} ref={appContainer} /> From d7ac74b1a50d5a9f6740cbb98d86151c24f8cb4f Mon Sep 17 00:00:00 2001 From: Sean Beyer Date: Mon, 18 Dec 2023 15:14:34 -0800 Subject: [PATCH 03/51] Add to ReactComponentList --- app/components-react/index.ts | 2 ++ app/components/shared/ReactComponentList.tsx | 8 ++++++++ app/components/windows/Main.vue.ts | 2 +- 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/app/components-react/index.ts b/app/components-react/index.ts index f802e7535817..095e9b5203d0 100644 --- a/app/components-react/index.ts +++ b/app/components-react/index.ts @@ -62,6 +62,7 @@ import EditTransform from './windows/EditTransform'; import InstalledApps from './windows/settings/InstalledApps'; import Hotkeys from './windows/settings/Hotkeys'; import Studio from './pages/Studio'; +import LiveDock from './root/LiveDock'; // list of React components to be used inside Vue components export const components = { @@ -127,5 +128,6 @@ export const components = { InstalledApps, Blank, Hotkeys, + LiveDock: createRoot(LiveDock), Studio: createRoot(Studio), }; diff --git a/app/components/shared/ReactComponentList.tsx b/app/components/shared/ReactComponentList.tsx index 4984daa043d6..a2f078d54425 100644 --- a/app/components/shared/ReactComponentList.tsx +++ b/app/components/shared/ReactComponentList.tsx @@ -172,6 +172,14 @@ export class InstalledApps extends ReactComponent {} }) export class LegacyEvents extends ReactComponent {} +@Component({ + props: { + name: { default: LiveDock }, + componentProps: { default: () => ({ onLeft: false }) }, + }, +}) +export class LiveDock extends ReactComponent {} + @Component({ props: { name: { default: 'ManageSceneCollections' }, diff --git a/app/components/windows/Main.vue.ts b/app/components/windows/Main.vue.ts index bcf80036a8ba..eb4a25753c9b 100644 --- a/app/components/windows/Main.vue.ts +++ b/app/components/windows/Main.vue.ts @@ -19,6 +19,7 @@ import { PlatformAppMainPage, RecordingHistory, Studio, + LiveDock, } from 'components/shared/ReactComponentList'; import { ScenesService } from 'services/scenes'; import { PlatformAppsService } from 'services/platform-apps'; @@ -36,7 +37,6 @@ import { NavigationService } from 'services/navigation'; import { AppService } from 'services/app'; import { UserService } from 'services/user'; import { IModalOptions, WindowsService } from 'services/windows'; -import LiveDock from '../LiveDock.vue'; import ResizeBar from 'components/shared/ResizeBar.vue'; import { getPlatformService } from 'services/platforms'; import ModalWrapper from '../shared/modals/ModalWrapper'; From e21b426d5aa9dcd53381b00127415ce390d1c8dc Mon Sep 17 00:00:00 2001 From: Sean Beyer Date: Mon, 18 Dec 2023 15:33:09 -0800 Subject: [PATCH 04/51] Fix strict nulls --- app/components-react/root/LiveDock.tsx | 2 +- app/components/windows/Main.vue | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/components-react/root/LiveDock.tsx b/app/components-react/root/LiveDock.tsx index aa76f1ff165b..91bcab1cae0b 100644 --- a/app/components-react/root/LiveDock.tsx +++ b/app/components-react/root/LiveDock.tsx @@ -90,7 +90,7 @@ class LiveDockController { get offlineImageSrc() { const mode = this.customizationService.isDarkTheme ? 'night' : 'day'; - return require(`../../media/images/sleeping-kevin-${mode}.png`); + return require(`../../../media/images/sleeping-kevin-${mode}.png`); } get hideViewerCount() { diff --git a/app/components/windows/Main.vue b/app/components/windows/Main.vue index 8a84ae568907..50ec714f7a16 100644 --- a/app/components/windows/Main.vue +++ b/app/components/windows/Main.vue @@ -20,7 +20,7 @@ class="sidenav" />
- + Date: Mon, 18 Dec 2023 16:11:30 -0800 Subject: [PATCH 05/51] Fix import error --- app/components/shared/ReactComponentList.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/components/shared/ReactComponentList.tsx b/app/components/shared/ReactComponentList.tsx index a2f078d54425..20a8937cae1d 100644 --- a/app/components/shared/ReactComponentList.tsx +++ b/app/components/shared/ReactComponentList.tsx @@ -174,7 +174,7 @@ export class LegacyEvents extends ReactComponent {} @Component({ props: { - name: { default: LiveDock }, + name: { default: 'LiveDock' }, componentProps: { default: () => ({ onLeft: false }) }, }, }) From 4be201aea732cb0d5e85f5275c455d16905a8e1f Mon Sep 17 00:00:00 2001 From: Sean Beyer Date: Mon, 18 Dec 2023 16:58:42 -0800 Subject: [PATCH 06/51] Fix reactivity issues --- app/components-react/root/LiveDock.m.less | 3 +- app/components-react/root/LiveDock.tsx | 53 ++++++++++++++------ app/components/shared/ReactComponentList.tsx | 1 + 3 files changed, 40 insertions(+), 17 deletions(-) diff --git a/app/components-react/root/LiveDock.m.less b/app/components-react/root/LiveDock.m.less index 7868dc9bca2b..c263247a7c81 100644 --- a/app/components-react/root/LiveDock.m.less +++ b/app/components-react/root/LiveDock.m.less @@ -6,6 +6,7 @@ z-index: 1000; width: 28%; box-sizing: border-box; + height: 100%; border-left: 1px solid var(--border); &.can-animate { @@ -61,7 +62,7 @@ font-size: 12px; - &.icon-right { + &:global(.icon-right) { transform: translate(-50%, -50%) rotate(-90deg); } } diff --git a/app/components-react/root/LiveDock.tsx b/app/components-react/root/LiveDock.tsx index 91bcab1cae0b..674b23bc86bd 100644 --- a/app/components-react/root/LiveDock.tsx +++ b/app/components-react/root/LiveDock.tsx @@ -13,6 +13,7 @@ import Chat from './Chat'; import styles from './LiveDock.m.less'; import Tooltip from 'components-react/shared/Tooltip'; import PlatformAppPageView from 'components-react/shared/PlatformAppPageView'; +import { useVuex } from 'components-react/hooks'; const LiveDockCtx = React.createContext(null); @@ -294,26 +295,46 @@ function LiveDock(p: { onLeft: boolean } = { onLeft: false }) { ctrl.collapsed ? ctrl.setCollapsed(false) : ctrl.setCollapsed(true); } - const { collapsed, isPlatform, isStreaming } = ctrl; + const { + collapsed, + isPlatform, + isStreaming, + hasChatTabs, + chatTabs, + selectedChat, + liveDockSize, + applicationLoading, + hideStyleBlockers, + } = useVuex(() => ({ + collapsed: ctrl.collapsed, + isPlatform: ctrl.isPlatform, + isStreaming: ctrl.isStreaming, + hasChatTabs: ctrl.hasChatTabs, + chatTabs: ctrl.chatTabs, + selectedChat: ctrl.selectedChat, + liveDockSize: ctrl.liveDockSize, + applicationLoading: ctrl.applicationLoading, + hideStyleBlockers: ctrl.hideStyleBlockers, + })); return (
- + {!collapsed && (
@@ -364,18 +385,18 @@ function LiveDock(p: { onLeft: boolean } = { onLeft: false }) { )}
- {!ctrl.hideStyleBlockers && + {!hideStyleBlockers && (isPlatform(['twitch', 'trovo']) || (isStreaming && isPlatform(['youtube', 'facebook', 'twitter']))) && (
- {ctrl.hasChatTabs && ( + {hasChatTabs && (
ctrl.setChat(ev.key)} mode="horizontal" > - {ctrl.chatTabs.map(tab => ( + {chatTabs.map(tab => ( {tab.name} ))} @@ -389,15 +410,15 @@ function LiveDock(p: { onLeft: boolean } = { onLeft: false }) { )}
)} - {!ctrl.applicationLoading && !collapsed && ( - + {!applicationLoading && !collapsed && ( + )} - {!['default', 'restream'].includes(ctrl.selectedChat) && ( + {!['default', 'restream'].includes(selectedChat) && ( )}
@@ -409,7 +430,7 @@ function LiveDock(p: { onLeft: boolean } = { onLeft: false }) { v-else > - {!ctrl.hideStyleBlockers && {$t('Your chat is currently offline')}} + {!hideStyleBlockers && {$t('Your chat is currently offline')}}
)}
diff --git a/app/components/shared/ReactComponentList.tsx b/app/components/shared/ReactComponentList.tsx index 20a8937cae1d..1d09ac035688 100644 --- a/app/components/shared/ReactComponentList.tsx +++ b/app/components/shared/ReactComponentList.tsx @@ -175,6 +175,7 @@ export class LegacyEvents extends ReactComponent {} @Component({ props: { name: { default: 'LiveDock' }, + wrapperStyles: { default: () => ({ height: '100%' }) }, componentProps: { default: () => ({ onLeft: false }) }, }, }) From 24067d14fd68231c274af8e201e2ffd358cc73ba Mon Sep 17 00:00:00 2001 From: Sean Beyer Date: Tue, 19 Dec 2023 10:41:53 -0800 Subject: [PATCH 07/51] Fix positioning and styling --- app/components-react/root/LiveDock.tsx | 5 +++-- app/components-react/root/ResizeBar.m.less | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/components-react/root/LiveDock.tsx b/app/components-react/root/LiveDock.tsx index 674b23bc86bd..182c999add6d 100644 --- a/app/components-react/root/LiveDock.tsx +++ b/app/components-react/root/LiveDock.tsx @@ -256,14 +256,15 @@ class LiveDockController { export default function LiveDockWithContext(p: { onLeft?: boolean }) { const controller = useMemo(() => new LiveDockController(), []); + const onLeft = p.onLeft || false; return ( - + ); } -function LiveDock(p: { onLeft: boolean } = { onLeft: false }) { +function LiveDock(p: { onLeft: boolean }) { const ctrl = useController(LiveDockCtx); useEffect(() => { diff --git a/app/components-react/root/ResizeBar.m.less b/app/components-react/root/ResizeBar.m.less index d21e8f87616d..f5fbccdce929 100644 --- a/app/components-react/root/ResizeBar.m.less +++ b/app/components-react/root/ResizeBar.m.less @@ -14,6 +14,7 @@ height: 10px; border-top: 1px solid var(--border); margin-top: -8px; + margin-left: -8px; cursor: row-resize; .resize-line { height: 4px; From ce276db54d00afafb38e5e7d51e0bde5397029bc Mon Sep 17 00:00:00 2001 From: Sean Beyer Date: Tue, 19 Dec 2023 14:41:39 -0800 Subject: [PATCH 08/51] Begin 1 to 1 port --- app/components-react/windows/Main.m.less | 127 ++++++++ app/components-react/windows/Main.tsx | 381 +++++++++++++++++++++++ app/services/platforms/index.ts | 3 +- 3 files changed, 510 insertions(+), 1 deletion(-) create mode 100644 app/components-react/windows/Main.m.less create mode 100644 app/components-react/windows/Main.tsx diff --git a/app/components-react/windows/Main.m.less b/app/components-react/windows/Main.m.less new file mode 100644 index 000000000000..644218db042c --- /dev/null +++ b/app/components-react/windows/Main.m.less @@ -0,0 +1,127 @@ +@import '../../styles/index'; + +.sidenav { + min-height: 100%; + height: 100%; + display: flex; + flex-grow: 1; +} + +.main { + display: flex; + flex-direction: column; + position: relative; + height: 100%; +} + +.main-contents { + display: grid; + grid-template-columns: auto 1fr; + flex-grow: 1; + height: 100%; +} + +.main-contents--right { + grid-template-columns: auto auto 1fr; +} + +.main-contents--left { + grid-template-columns: auto 1fr auto; +} + +.main-contents--onboarding { + grid-template-columns: 1fr; + + .main-middle { + grid-template-rows: 1fr; + } +} + +.main-middle { + flex-grow: 1; + display: grid; + grid-template-rows: minmax(0, 1fr) 48px; + position: relative; + height: 100%; +} + +.titlebar--error { + background: var(--warning) !important; + + /deep/ div, + /deep/ .titlebar-action { + color: var(--white) !important; + } +} + +.main-page-container { + /* Page always takes up remaining space */ + flex-grow: 1; + display: flex; + position: relative; +} + +.main-loading { + position: absolute; + top: 30px; + bottom: 0; + left: 0; + right: 0; + z-index: 999999; + background-color: var(--background); + -webkit-app-region: drag; + + /* Loader component is a fixed element that obscures the top bar */ + /deep/ :global(.s-loader__bg) { + top: 30px; + } +} + +.initial-loading { + top: 0 !important; +} + +.loader-enter-active, +.loader-leave-active { + transition: opacity 0.5s ease-out; +} + +.loader-enter, +.loader-leave-to { + opacity: 0; +} + +.live-dock { + height: 100%; +} + +.live-dock-wrapper { + position: relative; +} + +.live-dock-resize-bar { + position: absolute; + height: calc(100% - 20px); + bottom: 0; +} + +.live-dock-resize-bar--left { + right: 0; +} + +/deep/ .creator-sites-container .s-loader { + .s-loader__bg { + position: unset; + z-index: unset; + } +} + +.main-middle--compact { + :global(.performance-metric-icon) { + height: 12px; + } + + :global(.performance-metric) { + font-size: 12px; + } +} diff --git a/app/components-react/windows/Main.tsx b/app/components-react/windows/Main.tsx new file mode 100644 index 000000000000..a21732e9f190 --- /dev/null +++ b/app/components-react/windows/Main.tsx @@ -0,0 +1,381 @@ +import React, { useEffect, useMemo, useRef } from 'react'; +import fs from 'fs'; +import * as remote from '@electron/remote'; +import cx from 'classnames'; +import Animation from 'rc-animate'; +import { $t } from 'services/i18n'; +import { getPlatformService } from 'services/platforms'; +import ResizeBar from 'components-react/root/ResizeBar'; +import TitleBar from 'components-react/shared/TitleBar'; +import ModalWrapper from 'components-react/shared/modals/ModalWrapper'; +import { Services } from 'components-react/service-provider'; +import { WindowsService } from 'app-services'; +import { initStore, useController } from 'components-react/hooks/zustand'; +import { useVuex } from 'components-react/hooks'; +import antdThemes from 'styles/antd/index'; +import styles from './Main.m.less'; +import SideNav from 'components-react/sidebar/SideNav'; +import LiveDock from 'components-react/root/LiveDock'; +import StudioFooter from 'components-react/root/StudioFooter'; +import Loader from 'components-react/pages/Loader'; + +const MainCtx = React.createContext(null); + +class MainController { + private customizationService = Services.CustomizationService; + private navigationService = Services.NavigationService; + private appService = Services.AppService; + private userService = Services.UserService; + private windowsService = Services.WindowsService; + private scenesService = Services.ScenesService; + private platformAppsService = Services.PlatformAppsService; + private editorCommandsService = Services.EditorCommandsService; + + // $refs: { + // mainMiddle: HTMLDivElement; + // }; + + private modalOptions: IModalOptions = { + renderFn: null, + }; + + setModalOptions(opts: IModalOptions) { + this.modalOptions = opts; + } + + store = initStore({ + compactView: false, + windowWidth: 0, + hasLiveDock: true, + minDockWidth: 290, + maxDockWidth: 290, + minEditorWidth: 500, + }); + + get uiReady() { + // return this.$store.state.bulkLoadFinished && this.$store.state.i18nReady; + return false; + } + + get dockWidth() { + return this.customizationService.state.livedockSize; + } + + get title() { + return this.windowsService.state.main.title; + } + + get page() { + return this.navigationService.state.currentPage; + } + + get params() { + return this.navigationService.state.params; + } + + get theme() { + // if (this.$store.state.bulkLoadFinished) { + // return this.customizationService.currentTheme; + // } + + // return loadedTheme() || 'night-theme'; + return ''; + } + + get applicationLoading() { + return this.appService.state.loading; + } + + get showLoadingSpinner() { + return ( + this.appService.state.loading && this.page !== 'Onboarding' && this.page !== 'BrowseOverlays' + ); + } + + get isLoggedIn() { + return this.userService.isLoggedIn; + } + + get renderDock() { + return ( + this.isLoggedIn && + !this.isOnboarding && + this.store.hasLiveDock && + getPlatformService(this.userService.platform?.type)?.liveDockEnabled && + !this.showLoadingSpinner + ); + } + + get liveDockSize() { + return this.customizationService.state.livedockSize; + } + + get isDockCollapsed() { + return this.customizationService.state.livedockCollapsed; + } + + get leftDock() { + return this.customizationService.state.leftDock; + } + + get isOnboarding() { + return this.navigationService.state.currentPage === 'Onboarding'; + } + + get platformApps() { + return this.platformAppsService.enabledApps; + } + + get errorAlert() { + return this.appService.state.errorAlert; + } + + get mainResponsiveClasses() { + const classes = []; + + if (this.store.compactView) { + classes.push('main-middle--compact'); + } + + return classes.join(' '); + } + + async isDirectory(path: string) { + return new Promise((resolve, reject) => { + fs.lstat(path, (err, stats) => { + if (err) { + reject(err); + } + resolve(stats.isDirectory()); + }); + }); + } + + async onDropHandler(event: DragEvent) { + if (this.page !== 'Studio') return; + + const fileList = event.dataTransfer?.files; + + if (!fileList || fileList.length < 1) return; + + const files: string[] = []; + let fi = fileList.length; + while (fi--) files.push(fileList.item(fi).path); + + const isDirectory = await this.isDirectory(files[0]).catch(err => { + console.error('Error checking if drop is directory', err); + return false; + }); + + if (files.length > 1 || isDirectory) { + remote.dialog + .showMessageBox(remote.getCurrentWindow(), { + title: 'Streamlabs Desktop', + message: $t('Are you sure you want to import multiple files?'), + type: 'warning', + buttons: [$t('Cancel'), $t('OK')], + }) + .then(({ response }) => { + if (!response) return; + this.executeFileDrop(files); + }); + } else { + this.executeFileDrop(files); + } + } + + executeFileDrop(files: string[]) { + this.editorCommandsService.actions.executeCommand( + 'AddFilesCommand', + this.scenesService.views.activeSceneId, + files, + ); + } + + // updateLiveDockContraints() { + // const appRect = this.$root.$el.getBoundingClientRect(); + // this.maxDockWidth = Math.min(appRect.width - this.minEditorWidth, appRect.width / 2); + // this.minDockWidth = Math.min(290, this.maxDockWidth); + // } + + // windowSizeHandler() { + // if (!this.windowsService.state.main.hideStyleBlockers) { + // this.onResizeStartHandler(); + // } + // this.windowWidth = window.innerWidth; + + // clearTimeout(this.windowResizeTimeout); + + // this.hasLiveDock = this.windowWidth >= 1070; + // if (this.page === 'Studio') { + // this.hasLiveDock = this.windowWidth >= this.minEditorWidth + 100; + // } + // this.windowResizeTimeout = window.setTimeout(() => { + // this.windowsService.actions.updateStyleBlockers('main', false); + // this.updateLiveDockContraints(); + // this.updateWidth(); + // }, 200); + // } + + handleResize() { + // this.compactView = this.$refs.mainMiddle.clientWidth < 1200; + } + + handleEditorWidth(width: number) { + this.store.setState(s => (s.minEditorWidth = width)); + } + + onResizeStartHandler() { + this.windowsService.actions.updateStyleBlockers('main', true); + } + + onResizeStopHandler(offset: number) { + this.setLiveDockWidth(this.customizationService.state.livedockSize + offset); + this.windowsService.actions.updateStyleBlockers('main', false); + } + + setLiveDockWidth(width: number) { + this.customizationService.actions.setSettings({ + livedockSize: this.validateWidth(width), + }); + } + + validateWidth(width: number): number { + let constrainedWidth = Math.max(this.store.minDockWidth, width); + constrainedWidth = Math.min(this.store.maxDockWidth, width); + return constrainedWidth; + } + + updateLiveDockWidth() { + if (this.liveDockSize !== this.validateWidth(this.liveDockSize)) { + this.setLiveDockWidth(this.liveDockSize); + } + } + + resetWidth() { + // const appRect = this.$root.$el.getBoundingClientRect(); + // const defaultWidth = appRect.width * 0.28; + // this.setWidth(defaultWidth); + } +} + +export default function MainWithContext() { + const controller = useMemo(() => new MainController(), []); + return ( + +
+ + ); +} + +function Main() { + const ctrl = useController(MainCtx); + const { theme, uiReady, dockWidth, showLoadingSpinner } = useVuex(() => ({ + theme: ctrl.theme, + uiReady: ctrl.uiReady, + dockWidth: ctrl.dockWidth, + showLoadingSpinner: ctrl.showLoadingSpinner, + })); + + useEffect(() => { + window.addEventListener('resize', () => ctrl.windowSizeHandler); + const modalChangedSub = WindowsService.modalChanged.subscribe(modalOptions => { + ctrl.setModalOptions(modalOptions); + }); + + return () => { + window.removeEventListener('resize', () => ctrl.windowSizeHandler); + modalChangedSub.unsubscribe(); + }; + }, []); + + const oldTheme = useRef(null); + useEffect(() => { + if (!theme) return; + if (oldTheme.current && oldTheme.current !== theme) antdThemes[oldTheme.current].unuse(); + antdThemes[theme].use(); + oldTheme.current = theme; + }, [theme]); + + useEffect(() => { + if (dockWidth < 1) { + // migrate from old percentage value to the pixel value + ctrl.resetWidth(); + } + ctrl.handleResize(); + }, [uiReady]); + + if (!uiReady) return
; + + return ( +
+ +
+ {page !== 'Onboarding' && !showLoadingSpinner && ( + + )} + {renderDock && leftDock && ( +
+ + {!isDockCollapsed && ( + + )} +
+ )} + +
+ {/* */} + {/* */} + {!applicationLoading && page !== 'Onboarding' && } +
+ + {renderDock && !leftDock && ( +
+ {!isDockCollapsed && ( + + )} + +
+ )} +
+ + + {(!uiReady || showLoadingSpinner) && ( +
+ +
+ )} +
+
+ ); +} diff --git a/app/services/platforms/index.ts b/app/services/platforms/index.ts index c9b9d4768371..97d3fc6b242d 100644 --- a/app/services/platforms/index.ts +++ b/app/services/platforms/index.ts @@ -265,7 +265,8 @@ export const platformLabels = (platform: TPlatform | string) => [EPlatform.Instagram]: $t('Instagram'), }[platform]); -export function getPlatformService(platform: TPlatform): IPlatformService { +export function getPlatformService(platform?: TPlatform): IPlatformService { + if (!platform) return; return { twitch: TwitchService.instance, youtube: YoutubeService.instance, From 4e7aaa1227a14003f385482782751beb59ea343f Mon Sep 17 00:00:00 2001 From: Sean Beyer Date: Mon, 8 Jan 2024 13:52:04 -0800 Subject: [PATCH 09/51] Define variables and functions --- app/components-react/pages/index.ts | 15 ++++ app/components-react/windows/Main.tsx | 100 ++++++++++++++++++-------- 2 files changed, 84 insertions(+), 31 deletions(-) create mode 100644 app/components-react/pages/index.ts diff --git a/app/components-react/pages/index.ts b/app/components-react/pages/index.ts new file mode 100644 index 000000000000..c4b1d0b847af --- /dev/null +++ b/app/components-react/pages/index.ts @@ -0,0 +1,15 @@ +export { default as AlertboxLibrary } from './AlertboxLibrary'; +export { default as BrowseOverlays } from './BrowseOverlays'; +export { default as Highlighter } from './Highlighter'; +export { default as Loader } from './Loader'; +export { default as PatchNotes } from './PatchNotes'; +export { default as PlatformAppMainPage } from './PlatformAppMainPage'; +export { default as PlatformAppStore } from './PlatformAppStore'; +export { default as PlatformMerge } from './PlatformMerge'; +export { default as RecordingHistory } from './RecordingHistory'; +export { default as Studio } from './Studio'; +export { default as ThemeAudit } from './ThemeAudit'; +export { default as Grow } from './grow/Grow'; +export { default as LayoutEditor } from './layout-editor/LayoutEditor'; +export { default as Onboarding } from './onboarding/Onboarding'; +export { default as StreamScheduler } from './stream-scheduler/StreamScheduler'; diff --git a/app/components-react/windows/Main.tsx b/app/components-react/windows/Main.tsx index a21732e9f190..38361c321d74 100644 --- a/app/components-react/windows/Main.tsx +++ b/app/components-react/windows/Main.tsx @@ -6,6 +6,7 @@ import Animation from 'rc-animate'; import { $t } from 'services/i18n'; import { getPlatformService } from 'services/platforms'; import ResizeBar from 'components-react/root/ResizeBar'; +import * as appPages from 'components-react/pages'; import TitleBar from 'components-react/shared/TitleBar'; import ModalWrapper from 'components-react/shared/modals/ModalWrapper'; import { Services } from 'components-react/service-provider'; @@ -21,6 +22,13 @@ import Loader from 'components-react/pages/Loader'; const MainCtx = React.createContext(null); +const loadedTheme = () => { + const customizationState = localStorage.getItem('PersistentStatefulService-CustomizationService'); + if (customizationState) { + return JSON.parse(customizationState)?.theme; + } +}; + class MainController { private customizationService = Services.CustomizationService; private navigationService = Services.NavigationService; @@ -35,7 +43,7 @@ class MainController { // mainMiddle: HTMLDivElement; // }; - private modalOptions: IModalOptions = { + modalOptions: IModalOptions = { renderFn: null, }; @@ -52,11 +60,6 @@ class MainController { minEditorWidth: 500, }); - get uiReady() { - // return this.$store.state.bulkLoadFinished && this.$store.state.i18nReady; - return false; - } - get dockWidth() { return this.customizationService.state.livedockSize; } @@ -73,13 +76,12 @@ class MainController { return this.navigationService.state.params; } - get theme() { - // if (this.$store.state.bulkLoadFinished) { - // return this.customizationService.currentTheme; - // } + theme(bulkLoadFinished: boolean) { + if (bulkLoadFinished) { + return this.customizationService.currentTheme; + } - // return loadedTheme() || 'night-theme'; - return ''; + return loadedTheme() || 'night-theme'; } get applicationLoading() { @@ -151,7 +153,7 @@ class MainController { }); } - async onDropHandler(event: DragEvent) { + async onDropHandler(event: React.DragEvent) { if (this.page !== 'Studio') return; const fileList = event.dataTransfer?.files; @@ -160,7 +162,7 @@ class MainController { const files: string[] = []; let fi = fileList.length; - while (fi--) files.push(fileList.item(fi).path); + while (fi--) files.push(fileList.item(fi)!.path); const isDirectory = await this.isDirectory(files[0]).catch(err => { console.error('Error checking if drop is directory', err); @@ -259,24 +261,54 @@ class MainController { } } -export default function MainWithContext() { +export default function MainWithContext(p: { bulkLoadFinished: boolean; i18nReady: boolean }) { const controller = useMemo(() => new MainController(), []); return ( -
+
); } -function Main() { +function Main(p: { bulkLoadFinished: boolean; i18nReady: boolean }) { const ctrl = useController(MainCtx); - const { theme, uiReady, dockWidth, showLoadingSpinner } = useVuex(() => ({ - theme: ctrl.theme, - uiReady: ctrl.uiReady, + const { + theme, + dockWidth, + showLoadingSpinner, + errorAlert, + hasLiveDock, + renderDock, + leftDock, + applicationLoading, + page, + isDockCollapsed, + liveDockSize, + maxDockWidth, + minDockWidth, + mainResponsiveClasses, + } = useVuex(() => ({ + theme: ctrl.theme(p.bulkLoadFinished), dockWidth: ctrl.dockWidth, showLoadingSpinner: ctrl.showLoadingSpinner, + errorAlert: ctrl.errorAlert, + renderDock: ctrl.renderDock, + leftDock: ctrl.leftDock, + hasLiveDock: ctrl.store.hasLiveDock, + applicationLoading: ctrl.applicationLoading, + page: ctrl.page, + isDockCollapsed: ctrl.isDockCollapsed, + liveDockSize: ctrl.liveDockSize, + maxDockWidth: ctrl.store.maxDockWidth, + minDockWidth: ctrl.store.minDockWidth, + mainResponsiveClasses: ctrl.mainResponsiveClasses, })); + const uiReady = p.bulkLoadFinished && p.i18nReady; + + const mainWindowEl = useRef(null); + const mainMiddleEl = useRef(null); + useEffect(() => { window.addEventListener('resize', () => ctrl.windowSizeHandler); const modalChangedSub = WindowsService.modalChanged.subscribe(modalOptions => { @@ -307,8 +339,15 @@ function Main() { if (!uiReady) return
; + const Component: React.ReactNode = appPages[page]; + return ( -
+
)} -
+
{/* */} - {/* */} + {!showLoadingSpinner && ( + ctrl.handleEditorWidth(width)} + style={{ gridRow: '1 / span 1' }} + /> + )} {!applicationLoading && page !== 'Onboarding' && }
From 5886f133bf0776a1f0ca7787634108df4019f4b4 Mon Sep 17 00:00:00 2001 From: Sean Beyer Date: Mon, 8 Jan 2024 15:01:48 -0800 Subject: [PATCH 10/51] Add missing classNames --- .../pages/AlertboxLibrary.tsx | 3 +- app/components-react/pages/BrowseOverlays.tsx | 2 + app/components-react/pages/Highlighter.tsx | 7 +- app/components-react/pages/Loader.tsx | 5 +- app/components-react/pages/PatchNotes.tsx | 9 +- .../pages/PlatformAppMainPage.tsx | 7 +- .../pages/PlatformAppStore.tsx | 6 +- app/components-react/pages/PlatformMerge.tsx | 6 +- .../pages/RecordingHistory.tsx | 18 +-- app/components-react/pages/Studio.tsx | 5 +- app/components-react/pages/ThemeAudit.tsx | 10 +- app/components-react/root/ResizeBar.tsx | 2 + app/components-react/windows/Main.m.less | 1 + app/components-react/windows/Main.tsx | 114 ++++++++++-------- 14 files changed, 114 insertions(+), 81 deletions(-) diff --git a/app/components-react/pages/AlertboxLibrary.tsx b/app/components-react/pages/AlertboxLibrary.tsx index 45ac8eb119e6..b3e3476c70fc 100644 --- a/app/components-react/pages/AlertboxLibrary.tsx +++ b/app/components-react/pages/AlertboxLibrary.tsx @@ -9,7 +9,7 @@ import { GuestApiHandler } from 'util/guest-api-handler'; import { IDownloadProgress } from 'util/requests'; import { Services } from 'components-react/service-provider'; -export default function AlertboxLibrary(p: { params: { id?: string } }) { +export default function AlertboxLibrary(p: { params: { id?: string }; className?: string }) { const { NotificationsService, JsonrpcService, @@ -85,6 +85,7 @@ export default function AlertboxLibrary(p: { params: { id?: string } }) { if (!libraryUrl) return <>; return ( ; return ( ({ clips: HighlighterService.views.clips as IClip[], @@ -228,7 +229,7 @@ export default function Highlighter() { return (
diff --git a/app/components-react/pages/Loader.tsx b/app/components-react/pages/Loader.tsx index 4ac082fe0083..33148878c22d 100644 --- a/app/components-react/pages/Loader.tsx +++ b/app/components-react/pages/Loader.tsx @@ -1,4 +1,5 @@ import React, { useEffect, useState } from 'react'; +import cx from 'classnames'; import SvgContainer from 'components-react/shared/SvgContainer'; import { $t } from 'services/i18n'; @@ -35,7 +36,7 @@ const loadingStrings = () => [ 'You can customize the design of your tip page from the Theme Library.', ]; -export default function Loader() { +export default function Loader(p: { className?: string }) { const [loaderText, setLoaderText] = useState(''); useEffect(lifecycle, []); @@ -57,7 +58,7 @@ export default function Loader() { } return ( -
+
diff --git a/app/components-react/pages/PatchNotes.tsx b/app/components-react/pages/PatchNotes.tsx index 12cfaac5729e..847117676b56 100644 --- a/app/components-react/pages/PatchNotes.tsx +++ b/app/components-react/pages/PatchNotes.tsx @@ -1,10 +1,11 @@ -import { $t } from '../../services/i18n'; import React from 'react'; -import { Services } from '../service-provider'; import cx from 'classnames'; +import { $t } from '../../services/i18n'; +import { Services } from '../service-provider'; + import styles from './PatchNotes.m.less'; -export default function PatchNotes() { +export default function PatchNotes(p: { className?: string }) { const { PatchNotesService, NavigationService } = Services; const notes = PatchNotesService.notes; @@ -14,7 +15,7 @@ export default function PatchNotes() { } return ( -
+
diff --git a/app/components-react/pages/PlatformAppMainPage.tsx b/app/components-react/pages/PlatformAppMainPage.tsx index de2b3be1f107..965388f32864 100644 --- a/app/components-react/pages/PlatformAppMainPage.tsx +++ b/app/components-react/pages/PlatformAppMainPage.tsx @@ -5,7 +5,7 @@ import { Services } from 'components-react/service-provider'; import PlatformAppPageView from 'components-react/shared/PlatformAppPageView'; import { useVuex } from 'components-react/hooks'; -export default function PlatformAppMainPage(p: { params: { appId: string } }) { +export default function PlatformAppMainPage(p: { params: { appId: string }; className?: string }) { const { PlatformAppsService } = Services; const pageSlot = EAppPageSlot.TopNav; @@ -16,7 +16,10 @@ export default function PlatformAppMainPage(p: { params: { appId: string } }) { })); return ( -
+
{poppedOut ? ( $t('This app is currently popped out in another window.') ) : ( diff --git a/app/components-react/pages/PlatformAppStore.tsx b/app/components-react/pages/PlatformAppStore.tsx index c361f6967a99..6de097c38e2a 100644 --- a/app/components-react/pages/PlatformAppStore.tsx +++ b/app/components-react/pages/PlatformAppStore.tsx @@ -4,7 +4,10 @@ import BrowserView from 'components-react/shared/BrowserView'; import { GuestApiHandler } from 'util/guest-api-handler'; import * as remote from '@electron/remote'; import { Services } from 'components-react/service-provider'; -export default function PlatformAppStore(p: { params: { appId?: string; type?: string } }) { +export default function PlatformAppStore(p: { + params: { appId?: string; type?: string }; + className?: string; +}) { const { UserService, PlatformAppsService, @@ -52,6 +55,7 @@ export default function PlatformAppStore(p: { params: { appId?: string; type?: s return ( +

{$t('Connect %{platformName}', { platformName })}

{showOverlay ? : } diff --git a/app/components-react/pages/RecordingHistory.tsx b/app/components-react/pages/RecordingHistory.tsx index 03cecb60fdb9..66cc22073e41 100644 --- a/app/components-react/pages/RecordingHistory.tsx +++ b/app/components-react/pages/RecordingHistory.tsx @@ -1,17 +1,9 @@ import React, { useEffect, useMemo } from 'react'; import cx from 'classnames'; import * as remote from '@electron/remote'; -import { Tooltip, Menu, Button, message, Dropdown } from 'antd'; +import { Tooltip } from 'antd'; import { $t } from 'services/i18n'; import { ModalLayout } from 'components-react/shared/ModalLayout'; -import { - RecordingModeService, - UserService, - SharedStorageService, - OnboardingService, - WindowsService, - NotificationsService, -} from 'app-services'; import styles from './RecordingHistory.m.less'; import AutoProgressBar from 'components-react/shared/AutoProgressBar'; import { GetSLID } from 'components-react/highlighter/StorageUpload'; @@ -130,16 +122,16 @@ class RecordingHistoryController { } } -export default function RecordingHistoryPage() { +export default function RecordingHistoryPage(p: { className?: string }) { const controller = useMemo(() => new RecordingHistoryController(), []); return ( - + ); } -export function RecordingHistory() { +export function RecordingHistory(p: { className?: string }) { const controller = useController(RecordingHistoryCtx); const { formattedTimestamp, showFile, handleSelect, postError } = controller; const { uploadInfo, uploadOptions, recordings } = useVuex(() => ({ @@ -179,7 +171,7 @@ export function RecordingHistory() { } return ( -
+

{$t('Recordings')}

{$t( diff --git a/app/components-react/pages/Studio.tsx b/app/components-react/pages/Studio.tsx index 418795af2687..047d2e19d61a 100644 --- a/app/components-react/pages/Studio.tsx +++ b/app/components-react/pages/Studio.tsx @@ -1,11 +1,12 @@ import React, { ReactNode, useMemo } from 'react'; +import cx from 'classnames'; import { ELayoutElement, IVec2Array } from 'services/layout'; import * as elements from 'components-react/editor/elements'; import * as layouts from 'components-react/editor/layouts'; import { Services } from 'components-react/service-provider'; import { useVuex } from 'components-react/hooks'; -export default function Studio(p: { onTotalWidth: (width: Number) => void }) { +export default function Studio(p: { onTotalWidth: (width: Number) => void; className?: string }) { const { LayoutService } = Services; function totalWidthHandler(slots: IVec2Array, isColumns: boolean) { @@ -40,7 +41,7 @@ export default function Studio(p: { onTotalWidth: (width: Number) => void }) { return ( totalWidthHandler(slots, isColumns)} > diff --git a/app/components-react/pages/ThemeAudit.tsx b/app/components-react/pages/ThemeAudit.tsx index b5b892969cb2..2c3ae15facc7 100644 --- a/app/components-react/pages/ThemeAudit.tsx +++ b/app/components-react/pages/ThemeAudit.tsx @@ -1,6 +1,7 @@ -import { Services } from 'components-react/service-provider'; import React, { useEffect, useState } from 'react'; import execa from 'execa'; +import cx from 'classnames'; +import { Services } from 'components-react/service-provider'; import { FFPROBE_EXE } from 'services/highlighter/constants'; import { pmap } from 'util/pmap'; import { ExclamationCircleOutlined } from '@ant-design/icons'; @@ -76,7 +77,7 @@ async function readMediaInfo(): Promise { type TWarningLevel = 'OK' | 'WARN' | 'CRITICAL'; -export default function ThemeAudit() { +export default function ThemeAudit(p: { className?: string }) { const { SceneCollectionsService, ScenesService, @@ -151,7 +152,10 @@ export default function ThemeAudit() { } return ( -
+

Theme Audit: {SceneCollectionsService.activeCollection?.name}

diff --git a/app/components-react/root/ResizeBar.tsx b/app/components-react/root/ResizeBar.tsx index 66ad012c7b46..738d60dc6e78 100644 --- a/app/components-react/root/ResizeBar.tsx +++ b/app/components-react/root/ResizeBar.tsx @@ -13,6 +13,7 @@ interface ResizeBarProps { onResizestart?: (offset?: number) => void; onResizestop?: (offset?: number) => void; onInput: (val: number) => void; + className?: string; } interface ResizableData { @@ -78,6 +79,7 @@ export default function ResizeBar(p: React.PropsWithChildren) { onResize={handleResize(p.onInput)} transformScale={2} {...resizableProps} + className={p.className} handle={
diff --git a/app/components-react/windows/Main.m.less b/app/components-react/windows/Main.m.less index 644218db042c..6578c9c680fa 100644 --- a/app/components-react/windows/Main.m.less +++ b/app/components-react/windows/Main.m.less @@ -59,6 +59,7 @@ flex-grow: 1; display: flex; position: relative; + grid-row: 1 / span 1; } .main-loading { diff --git a/app/components-react/windows/Main.tsx b/app/components-react/windows/Main.tsx index 38361c321d74..4e120d3172d3 100644 --- a/app/components-react/windows/Main.tsx +++ b/app/components-react/windows/Main.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useMemo, useRef } from 'react'; +import React, { useEffect, useLayoutEffect, useMemo, useRef } from 'react'; import fs from 'fs'; import * as remote from '@electron/remote'; import cx from 'classnames'; @@ -39,10 +39,6 @@ class MainController { private platformAppsService = Services.PlatformAppsService; private editorCommandsService = Services.EditorCommandsService; - // $refs: { - // mainMiddle: HTMLDivElement; - // }; - modalOptions: IModalOptions = { renderFn: null, }; @@ -51,6 +47,8 @@ class MainController { this.modalOptions = opts; } + windowResizeTimeout: number | null = null; + store = initStore({ compactView: false, windowWidth: 0, @@ -76,6 +74,10 @@ class MainController { return this.navigationService.state.params; } + get hideStyleBlockers() { + return this.windowsService.state.mian.hideStyleBlockers; + } + theme(bulkLoadFinished: boolean) { if (bulkLoadFinished) { return this.customizationService.currentTheme; @@ -194,35 +196,6 @@ class MainController { ); } - // updateLiveDockContraints() { - // const appRect = this.$root.$el.getBoundingClientRect(); - // this.maxDockWidth = Math.min(appRect.width - this.minEditorWidth, appRect.width / 2); - // this.minDockWidth = Math.min(290, this.maxDockWidth); - // } - - // windowSizeHandler() { - // if (!this.windowsService.state.main.hideStyleBlockers) { - // this.onResizeStartHandler(); - // } - // this.windowWidth = window.innerWidth; - - // clearTimeout(this.windowResizeTimeout); - - // this.hasLiveDock = this.windowWidth >= 1070; - // if (this.page === 'Studio') { - // this.hasLiveDock = this.windowWidth >= this.minEditorWidth + 100; - // } - // this.windowResizeTimeout = window.setTimeout(() => { - // this.windowsService.actions.updateStyleBlockers('main', false); - // this.updateLiveDockContraints(); - // this.updateWidth(); - // }, 200); - // } - - handleResize() { - // this.compactView = this.$refs.mainMiddle.clientWidth < 1200; - } - handleEditorWidth(width: number) { this.store.setState(s => (s.minEditorWidth = width)); } @@ -248,16 +221,25 @@ class MainController { return constrainedWidth; } + updateWidth() { + const width = this.customizationService.state.livedockSize; + if (width !== this.validateWidth(width)) this.setWidth(width); + } + updateLiveDockWidth() { if (this.liveDockSize !== this.validateWidth(this.liveDockSize)) { this.setLiveDockWidth(this.liveDockSize); } } - resetWidth() { - // const appRect = this.$root.$el.getBoundingClientRect(); - // const defaultWidth = appRect.width * 0.28; - // this.setWidth(defaultWidth); + updateStyleBlockers(val: boolean) { + this.windowsService.actions.updateStyleBlockers('main', val); + } + + setWidth(width: number) { + this.customizationService.actions.setSettings({ + livedockSize: this.validateWidth(width), + }); } } @@ -287,6 +269,7 @@ function Main(p: { bulkLoadFinished: boolean; i18nReady: boolean }) { maxDockWidth, minDockWidth, mainResponsiveClasses, + hideStyleBlockers, } = useVuex(() => ({ theme: ctrl.theme(p.bulkLoadFinished), dockWidth: ctrl.dockWidth, @@ -302,6 +285,7 @@ function Main(p: { bulkLoadFinished: boolean; i18nReady: boolean }) { maxDockWidth: ctrl.store.maxDockWidth, minDockWidth: ctrl.store.minDockWidth, mainResponsiveClasses: ctrl.mainResponsiveClasses, + hideStyleBlockers: ctrl.hideStyleBlockers, })); const uiReady = p.bulkLoadFinished && p.i18nReady; @@ -309,14 +293,38 @@ function Main(p: { bulkLoadFinished: boolean; i18nReady: boolean }) { const mainWindowEl = useRef(null); const mainMiddleEl = useRef(null); - useEffect(() => { - window.addEventListener('resize', () => ctrl.windowSizeHandler); + function windowSizeHandler() { + if (!hideStyleBlockers) { + ctrl.onResizeStartHandler(); + } + ctrl.store.setState(s => (s.windowWidth = window.innerWidth)); + + if (ctrl.windowResizeTimeout) clearTimeout(ctrl.windowResizeTimeout); + + ctrl.store.setState(s => (s.hasLiveDock = s.windowWidth >= 1070)); + if (ctrl.page === 'Studio') { + ctrl.store.setState(s => (s.hasLiveDock = s.windowWidth >= s.minEditorWidth + 100)); + } + ctrl.windowResizeTimeout = window.setTimeout(() => { + ctrl.updateStyleBlockers(false); + const appRect = mainWindowEl.current?.getBoundingClientRect(); + if (!appRect) return; + ctrl.store.setState(s => { + s.maxDockWidth = Math.min(appRect.width - s.minEditorWidth, appRect.width / 2); + s.minDockWidth = Math.min(290, s.maxDockWidth); + }); + ctrl.updateWidth(); + }, 200); + } + + useLayoutEffect(() => { + window.addEventListener('resize', windowSizeHandler); const modalChangedSub = WindowsService.modalChanged.subscribe(modalOptions => { ctrl.setModalOptions(modalOptions); }); return () => { - window.removeEventListener('resize', () => ctrl.windowSizeHandler); + window.removeEventListener('resize', windowSizeHandler); modalChangedSub.unsubscribe(); }; }, []); @@ -330,16 +338,27 @@ function Main(p: { bulkLoadFinished: boolean; i18nReady: boolean }) { }, [theme]); useEffect(() => { - if (dockWidth < 1) { + if (dockWidth < 1 && mainWindowEl.current) { // migrate from old percentage value to the pixel value - ctrl.resetWidth(); + const appRect = mainWindowEl.current.getBoundingClientRect(); + const defaultWidth = appRect.width * 0.28; + ctrl.setWidth(defaultWidth); } - ctrl.handleResize(); }, [uiReady]); + useLayoutEffect(() => { + ctrl.store.setState( + s => (s.compactView = !!mainMiddleEl.current && mainMiddleEl.current.clientWidth < 1200), + ); + }, [uiReady, hideStyleBlockers]); + if (!uiReady) return
; - const Component: React.ReactNode = appPages[page]; + const Component: React.FunctionComponent<{ + className: string; + params: any; + onTotalWidth: (width: number) => void; + }> = appPages[page]; return (
- {page !== 'Onboarding' && !showLoadingSpinner && ( - - )} + {page !== 'Onboarding' && !showLoadingSpinner && } {renderDock && leftDock && (
@@ -383,7 +400,6 @@ function Main(p: { bulkLoadFinished: boolean; i18nReady: boolean }) { className={styles.mainPageContainer} params={ctrl.params} onTotalWidth={(width: number) => ctrl.handleEditorWidth(width)} - style={{ gridRow: '1 / span 1' }} /> )} {!applicationLoading && page !== 'Onboarding' && } From 19a03fb9077505ede3ea26a7ae0ec1558b5631f4 Mon Sep 17 00:00:00 2001 From: Sean Beyer Date: Mon, 8 Jan 2024 15:07:19 -0800 Subject: [PATCH 11/51] Fix non-modal redlines --- app/components-react/shared/TitleBar.tsx | 4 ++-- app/components-react/windows/Main.tsx | 17 +++++------------ 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/app/components-react/shared/TitleBar.tsx b/app/components-react/shared/TitleBar.tsx index ca748617c6bc..51614b7218ef 100644 --- a/app/components-react/shared/TitleBar.tsx +++ b/app/components-react/shared/TitleBar.tsx @@ -11,7 +11,7 @@ import styles from './TitleBar.m.less'; import * as remote from '@electron/remote'; import Banner from 'components-react/root/Banner'; -export default function TitleBar(props: { windowId: string }) { +export default function TitleBar(props: { windowId: string; className?: string }) { const { CustomizationService, StreamingService, WindowsService } = Services; const isMaximizable = remote.getCurrentWindow().isMaximizable() !== false; @@ -62,7 +62,7 @@ export default function TitleBar(props: { windowId: string }) { return ( <>
(s.minEditorWidth = width)); } - onResizeStartHandler() { - this.windowsService.actions.updateStyleBlockers('main', true); - } - - onResizeStopHandler(offset: number) { + onResize(offset: number) { this.setLiveDockWidth(this.customizationService.state.livedockSize + offset); - this.windowsService.actions.updateStyleBlockers('main', false); } setLiveDockWidth(width: number) { @@ -295,7 +290,7 @@ function Main(p: { bulkLoadFinished: boolean; i18nReady: boolean }) { function windowSizeHandler() { if (!hideStyleBlockers) { - ctrl.onResizeStartHandler(); + ctrl.updateStyleBlockers(true); } ctrl.store.setState(s => (s.windowWidth = window.innerWidth)); @@ -365,7 +360,7 @@ function Main(p: { bulkLoadFinished: boolean; i18nReady: boolean }) { className={cx(styles.main, theme)} id="mainWrapper" ref={mainWindowEl} - onDrop={ctrl.onDropHandler} + onDrop={(ev: React.DragEvent) => ctrl.onDropHandler(ev)} >
ctrl.onResize(val)} max={maxDockWidth} min={minDockWidth} value={liveDockSize} @@ -411,8 +405,7 @@ function Main(p: { bulkLoadFinished: boolean; i18nReady: boolean }) { ctrl.onResize(val)} max={maxDockWidth} min={minDockWidth} value={liveDockSize} From 019812af49f6a6515a7fd5b3e853f49cf4f3c612 Mon Sep 17 00:00:00 2001 From: Sean Beyer Date: Tue, 9 Jan 2024 10:30:32 -0800 Subject: [PATCH 12/51] Add modals to react --- .../shared/modals/MessageBoxModal.m.less | 30 +++++++++++++++++++ .../shared/modals/MessageBoxModal.tsx | 20 +++++++++++++ .../shared/modals/Modal.m.less | 23 ++++++++++++++ app/components-react/shared/modals/Modal.tsx | 19 ++++++++++++ .../shared/modals/ModalWrapper.tsx | 14 +++++++++ app/components-react/windows/Main.tsx | 16 +++++----- 6 files changed, 114 insertions(+), 8 deletions(-) create mode 100644 app/components-react/shared/modals/MessageBoxModal.m.less create mode 100644 app/components-react/shared/modals/MessageBoxModal.tsx create mode 100644 app/components-react/shared/modals/Modal.m.less create mode 100644 app/components-react/shared/modals/Modal.tsx create mode 100644 app/components-react/shared/modals/ModalWrapper.tsx diff --git a/app/components-react/shared/modals/MessageBoxModal.m.less b/app/components-react/shared/modals/MessageBoxModal.m.less new file mode 100644 index 000000000000..84a23570a50d --- /dev/null +++ b/app/components-react/shared/modals/MessageBoxModal.m.less @@ -0,0 +1,30 @@ +@import "../../../styles/index"; + +.wrapper { + background: var(--background); + color: var(--paragraph); + min-width: 300px; +} + +.header { + display: flex; + justify-content: flex-end; + padding: 8px; + + i { + cursor: pointer; + &:hover { + color: var(--link-active) + } + } +} + +.content-wrapper { + min-height: 120px; + display: flex; + justify-content: center; + align-items: center; + padding: 20px 20px 40px 20px; +} + +.content {} diff --git a/app/components-react/shared/modals/MessageBoxModal.tsx b/app/components-react/shared/modals/MessageBoxModal.tsx new file mode 100644 index 000000000000..dc04f5e8c4bd --- /dev/null +++ b/app/components-react/shared/modals/MessageBoxModal.tsx @@ -0,0 +1,20 @@ +import React from 'react'; +import { WindowsService } from 'services/windows'; +import styles from './MessageBoxModal.m.less'; + +/** + * A MessageBox layout + * Should be used as an alternative for `window.alert()` + */ +export default function MessageBoxModal(p: React.PropsWithChildren<{}>) { + return ( +
+
+ WindowsService.hideModal()} /> +
+
+
{p.children}
+
+
+ ); +} diff --git a/app/components-react/shared/modals/Modal.m.less b/app/components-react/shared/modals/Modal.m.less new file mode 100644 index 000000000000..a6563891529f --- /dev/null +++ b/app/components-react/shared/modals/Modal.m.less @@ -0,0 +1,23 @@ +@import "../../../styles/index"; + +.wrapper { + display: flex; + position: fixed; + z-index: 1002; // above resize bars + width: 100%; + height: 100%; + align-items: center; + justify-content: center; +} + +.fader { + .absolute(0,0,0,0); + background: fade(black, 70%); +} + +.content { + z-index: 1; + a { + color: var(--teal); + } +} diff --git a/app/components-react/shared/modals/Modal.tsx b/app/components-react/shared/modals/Modal.tsx new file mode 100644 index 000000000000..c96f837524aa --- /dev/null +++ b/app/components-react/shared/modals/Modal.tsx @@ -0,0 +1,19 @@ +import React from 'react'; +import styles from './Modal.m.less'; + +/** + * Shows content above black fade in the middle of the window + * + * @Example + * + * Loading... + * + */ +export default function Modal(p: React.PropsWithChildren<{}>) { + return ( +
+
+
{p.children}
+
+ ); +} diff --git a/app/components-react/shared/modals/ModalWrapper.tsx b/app/components-react/shared/modals/ModalWrapper.tsx new file mode 100644 index 000000000000..f6fb68c77102 --- /dev/null +++ b/app/components-react/shared/modals/ModalWrapper.tsx @@ -0,0 +1,14 @@ +import React from 'react'; +import Animation from 'rc-animate'; +import { IModalOptions } from 'services/windows'; + +/** + * Shows an animated modal + */ +export default function ModalWrapper(p: IModalOptions) { + return ( +
+ {p?.renderFn && p.renderFn()} +
+ ); +} diff --git a/app/components-react/windows/Main.tsx b/app/components-react/windows/Main.tsx index 67e0f78b90b7..6d7b573aa270 100644 --- a/app/components-react/windows/Main.tsx +++ b/app/components-react/windows/Main.tsx @@ -4,21 +4,22 @@ import * as remote from '@electron/remote'; import cx from 'classnames'; import Animation from 'rc-animate'; import { $t } from 'services/i18n'; -import { getPlatformService } from 'services/platforms'; +import { initStore, useController } from 'components-react/hooks/zustand'; +import { useVuex } from 'components-react/hooks'; import ResizeBar from 'components-react/root/ResizeBar'; import * as appPages from 'components-react/pages'; import TitleBar from 'components-react/shared/TitleBar'; import ModalWrapper from 'components-react/shared/modals/ModalWrapper'; import { Services } from 'components-react/service-provider'; import { WindowsService } from 'app-services'; -import { initStore, useController } from 'components-react/hooks/zustand'; -import { useVuex } from 'components-react/hooks'; -import antdThemes from 'styles/antd/index'; -import styles from './Main.m.less'; import SideNav from 'components-react/sidebar/SideNav'; import LiveDock from 'components-react/root/LiveDock'; import StudioFooter from 'components-react/root/StudioFooter'; import Loader from 'components-react/pages/Loader'; +import antdThemes from 'styles/antd/index'; +import { getPlatformService } from 'services/platforms'; +import { IModalOptions } from 'services/windows'; +import styles from './Main.m.less'; const MainCtx = React.createContext(null); @@ -43,8 +44,8 @@ class MainController { renderFn: null, }; - setModalOptions(opts: IModalOptions) { - this.modalOptions = opts; + setModalOptions(opts: Partial) { + this.modalOptions = { ...this.modalOptions, ...opts }; } windowResizeTimeout: number | null = null; @@ -388,7 +389,6 @@ function Main(p: { bulkLoadFinished: boolean; i18nReady: boolean }) { )}
- {/* */} {!showLoadingSpinner && ( Date: Tue, 9 Jan 2024 10:54:50 -0800 Subject: [PATCH 13/51] Import components --- app/app.ts | 2 +- app/components-react/index.ts | 50 +-- app/components/shared/ReactComponentList.tsx | 195 +---------- app/components/windows/Main.tsx | 18 + app/components/windows/Main.vue | 210 ------------ app/components/windows/Main.vue.ts | 340 ------------------- app/services/windows.ts | 4 +- 7 files changed, 31 insertions(+), 788 deletions(-) create mode 100644 app/components/windows/Main.tsx delete mode 100644 app/components/windows/Main.vue delete mode 100644 app/components/windows/Main.vue.ts diff --git a/app/app.ts b/app/app.ts index 67aff5529f70..9fee32930376 100644 --- a/app/app.ts +++ b/app/app.ts @@ -31,7 +31,7 @@ import * as obs from '../obs-api'; import path from 'path'; import util from 'util'; import uuid from 'uuid/v4'; -import Main from 'components/windows/Main.vue'; +import Main from 'components/windows/Main'; import { Loader, Blank } from 'components/shared/ReactComponentList'; import process from 'process'; import { MetricsService } from 'services/metrics'; diff --git a/app/components-react/index.ts b/app/components-react/index.ts index 095e9b5203d0..79d3824ebd8e 100644 --- a/app/components-react/index.ts +++ b/app/components-react/index.ts @@ -7,29 +7,16 @@ import ScreenCaptureProperties from './windows/ScreenCaptureProperties'; import GuestCamProperties from './windows/GuestCamProperties'; import News from './windows/notifications/News'; import PerformanceMetrics from './shared/PerformanceMetrics'; -import PatchNotes from './pages/PatchNotes'; import Display from './shared/Display'; import TitleBar from './shared/TitleBar'; -import Chat from './root/Chat'; -import Highlighter from './pages/Highlighter'; -import Grow from './pages/grow/Grow'; -import Loader from './pages/Loader'; import PlatformLogo from './shared/PlatformLogo'; -import Onboarding from './pages/onboarding/Onboarding'; import AdvancedStatistics from './windows/AdvancedStatistics'; -import StudioFooter from './root/StudioFooter'; -import StreamScheduler from './pages/stream-scheduler/StreamScheduler'; import { createRoot } from './root/ReactRoot'; -import StartStreamingButton from './root/StartStreamingButton'; import SourceProperties from './windows/SourceProperties'; -import TestWidgets from './root/TestWidgets'; import RenameSource from './windows/RenameSource'; -import NotificationsArea from './root/NotificationsArea'; -import StudioEditor from './root/StudioEditor'; import SharedComponentsLibrary from './windows/sharedComponentsLibrary/SharedComponentsLibrary'; import { ObsSettings } from './windows/settings/ObsSettings'; import ManageSceneCollections from './windows/ManageSceneCollections'; -import ThemeAudit from './pages/ThemeAudit'; import { WidgetWindow } from './widgets/common/WidgetWindow'; import SafeMode from './windows/SafeMode'; import AdvancedAudio from './windows/advanced-audio'; @@ -39,7 +26,6 @@ import SourceFilters from './windows/SourceFilters'; import RecentEvents from './editor/elements/RecentEvents'; import BrowserView from './shared/BrowserView'; import MediaGallery from './windows/MediaGallery'; -import LayoutEditor from './pages/layout-editor/LayoutEditor'; import Projector from './windows/Projector'; import AddSource from './windows/AddSource'; import SideNav from './sidebar/SideNav'; @@ -47,27 +33,20 @@ import WelcomeToPrime from './windows/WelcomeToPrime'; import Notifications from './windows/notifications/Notifications'; import NotificationsAndNews from './windows/notifications'; import Blank from './windows/Blank'; -import PlatformMerge from './pages/PlatformMerge'; -import AlertboxLibrary from './pages/AlertboxLibrary'; -import PlatformAppStore from './pages/PlatformAppStore'; -import BrowseOverlays from './pages/BrowseOverlays'; -import PlatformAppMainPage from './pages/PlatformAppMainPage'; import PlatformAppPageView from './shared/PlatformAppPageView'; import PlatformAppPopOut from './windows/PlatformAppPopOut'; import RecentEventsWindow from './windows/RecentEvents'; import NewBadge from './shared/NewBadge'; -import RecordingHistory from './pages/RecordingHistory'; import UltraIcon from './shared/UltraIcon'; import EditTransform from './windows/EditTransform'; import InstalledApps from './windows/settings/InstalledApps'; import Hotkeys from './windows/settings/Hotkeys'; -import Studio from './pages/Studio'; -import LiveDock from './root/LiveDock'; +import Main from './windows/Main'; +import Loader from './pages/Loader'; +import StartStreamingButton from './root/StartStreamingButton'; // list of React components to be used inside Vue components export const components = { - AlertboxLibrary, - BrowseOverlays, NameFolder, NameScene, BrowserView, @@ -79,28 +58,15 @@ export const components = { News, PerformanceMetrics, ManageSceneCollections, - PatchNotes, Display, TitleBar, - Chat, - Highlighter: createRoot(Highlighter), - Grow, - Loader, PlatformLogo, - Onboarding: createRoot(Onboarding), Projector, - StreamScheduler: createRoot(StreamScheduler), AdvancedStatistics, SourceProperties: createRoot(SourceProperties), SharedComponentsLibrary: createRoot(SharedComponentsLibrary), - TestWidgets, RenameSource, - StudioFooter: createRoot(StudioFooter), - StartStreamingButton, - NotificationsArea, ObsSettings: createRoot(ObsSettings), - ThemeAudit, - StudioEditor, WidgetWindow: createRoot(WidgetWindow), CustomCodeWindow: createRoot(CustomCodeWindow), SafeMode, @@ -109,17 +75,12 @@ export const components = { SourceFilters, RecentEvents, MediaGallery, - LayoutEditor: createRoot(LayoutEditor), AddSource, - RecordingHistory: createRoot(RecordingHistory), RecentEventsWindow, SideNav, WelcomeToPrime, Notifications, NotificationsAndNews, - PlatformMerge, - PlatformAppStore, - PlatformAppMainPage, PlatformAppPageView, PlatformAppPopOut, NewBadge, @@ -128,6 +89,7 @@ export const components = { InstalledApps, Blank, Hotkeys, - LiveDock: createRoot(LiveDock), - Studio: createRoot(Studio), + Main: createRoot(Main), + Loader, + StartStreamingButton, }; diff --git a/app/components/shared/ReactComponentList.tsx b/app/components/shared/ReactComponentList.tsx index 1d09ac035688..4bac1b549811 100644 --- a/app/components/shared/ReactComponentList.tsx +++ b/app/components/shared/ReactComponentList.tsx @@ -25,25 +25,9 @@ export class AdvancedAudio extends ReactComponent {} }) export class AdvancedStatistics extends ReactComponent {} -@Component({ - props: { - name: { default: 'AlertboxLibrary' }, - wrapperStyles: { default: () => ({ height: '100%' }) }, - }, -}) -export class AlertboxLibrary extends ReactComponent {} - @Component({ props: { name: { default: 'Blank' } } }) export class Blank extends ReactComponent {} -@Component({ - props: { - name: { default: 'BrowseOverlays' }, - wrapperStyles: { default: () => ({ height: '100%' }) }, - }, -}) -export class BrowseOverlays extends ReactComponent {} - @Component({ props: { name: { default: 'Browser' }, wrapperStyles: { default: () => ({ height: '100%' }) } }, }) @@ -58,17 +42,6 @@ export class Browser extends ReactComponent {} }) export class BrowserView extends ReactComponent {} -@Component({ - props: { - name: { default: 'Chat' }, - componentProps: { default: () => ({ restream: false }) }, - wrapperStyles: { - default: () => ({ height: '100%', display: 'flex', flexDirection: 'column' }), - }, - }, -}) -export class Chat extends ReactComponent {} - @Component({ props: { name: { default: 'Display' }, @@ -83,15 +56,6 @@ export class Chat extends ReactComponent {} }) export class Display extends ReactComponent {} -@Component({ - props: { - name: { default: 'DisplayElement' }, - wrapperStyles: { default: () => ({ height: '100%' }) }, - mins: { default: () => ({ x: 0, y: 0 }) }, - }, -}) -export class DisplayElement extends ReactComponent {} - @Component({ props: { name: { default: 'EditStreamWindow' }, @@ -116,14 +80,6 @@ export class EditTransform extends ReactComponent {} }) export class GoLiveWindow extends ReactComponent {} -@Component({ - props: { - name: { default: 'Grow' }, - wrapperStyles: { default: () => ({ gridRow: '1 / span 1' }) }, - }, -}) -export class Grow extends ReactComponent {} - @Component({ props: { name: { default: 'GuestCamProperties' }, @@ -132,20 +88,6 @@ export class Grow extends ReactComponent {} }) export class GuestCamProperties extends ReactComponent {} -@Component({ - props: { - name: { default: 'Highlighter' }, - componentProps: { default: () => ({}) }, - }, -}) -export class Highlighter extends ReactComponent {} - -@Component({ props: { name: { default: 'LayoutEditor' } } }) -export class LayoutEditor extends ReactComponent {} - -@Component({ props: { name: { default: 'Loader' } } }) -export class Loader extends ReactComponent {} - @Component({ props: { name: { default: 'IconLibraryProperties' }, @@ -162,24 +104,17 @@ export class IconLibraryProperties extends ReactComponent {} }) export class InstalledApps extends ReactComponent {} -@Component({ - props: { - name: { default: 'LegacyEvents' }, - wrapperStyles: { default: () => ({ height: '100%' }) }, - componentProps: { default: () => ({ onPopout: () => {} }) }, - mins: { default: () => ({ x: 360, y: 150 }) }, - }, -}) -export class LegacyEvents extends ReactComponent {} +@Component({ props: { name: { default: 'Loader' } } }) +export class Loader extends ReactComponent {} @Component({ props: { - name: { default: 'LiveDock' }, + name: { default: 'Main' }, wrapperStyles: { default: () => ({ height: '100%' }) }, - componentProps: { default: () => ({ onLeft: false }) }, + componentProps: { default: () => ({ bulkLoadFinished: false, i18nReady: false }) }, }, }) -export class LiveDock extends ReactComponent {} +export class Main extends ReactComponent {} @Component({ props: { @@ -197,24 +132,6 @@ export class ManageSceneCollections extends ReactComponent {} }) export class MediaGallery extends ReactComponent {} -@Component({ - props: { - name: { default: 'MiniFeed' }, - wrapperStyles: { default: () => ({ height: '100%' }) }, - mins: { default: () => ({ x: 330, y: 90 }) }, - }, -}) -export class MiniFeed extends ReactComponent {} - -@Component({ - props: { - name: { default: 'Mixer' }, - wrapperStyles: { default: () => ({ height: '100%' }) }, - mins: { default: () => ({ x: 150, y: 120 }) }, - }, -}) -export class Mixer extends ReactComponent {} - @Component({ props: { name: { default: 'NameFolder' }, @@ -230,10 +147,6 @@ export class NameFolder extends ReactComponent {} }, }) export class NameScene extends ReactComponent {} - -@Component({ props: { name: { default: 'NotificationsArea' } } }) -export class NotificationsArea extends ReactComponent {} - @Component({ props: { name: { default: 'NotificationsAndNews' }, @@ -250,16 +163,6 @@ export class NotificationsAndNews extends ReactComponent {} }) export class ObsSettings extends ReactComponent {} -@Component({ - props: { - name: { default: 'Onboarding' }, - }, -}) -export class Onboarding extends ReactComponent {} - -@Component({ props: { name: { default: 'PatchNotes' } } }) -export class PatchNotes extends ReactComponent {} - @Component({ props: { name: { default: 'PerformanceMetrics' }, @@ -323,23 +226,6 @@ export class RecentEvents extends ReactComponent<{ isOverlay?: boolean }> {} }) export class RecentEventsWindow extends ReactComponent {} -@Component({ - props: { - name: { default: 'RecordingHistory' }, - wrapperStyles: { default: () => ({ height: '100%' }) }, - }, -}) -export class RecordingHistory extends ReactComponent {} - -@Component({ - props: { - name: { default: 'RecordingPreview' }, - wrapperStyles: { default: () => ({ height: '100%' }) }, - mins: { default: () => ({ x: 0, y: 0 }) }, - }, -}) -export class RecordingPreview extends ReactComponent {} - @Component({ props: { name: { default: 'RenameSource' }, @@ -356,41 +242,6 @@ export class RenameSource extends ReactComponent {} }) export class SafeMode extends ReactComponent {} -@Component({ - props: { - name: { default: 'StreamPreview' }, - wrapperStyles: { default: () => ({ height: '100%' }) }, - mins: { default: () => ({ x: 0, y: 0 }) }, - }, -}) -export class StreamPreview extends ReactComponent {} - -@Component({ - props: { - name: { default: 'SceneSelector' }, - wrapperStyles: { default: () => ({ height: '100%' }) }, - mins: { default: () => ({ x: 200, y: 120 }) }, - }, -}) -export class SceneSelector extends ReactComponent {} - -@Component({ - props: { - name: { default: 'SourceSelector' }, - wrapperStyles: { default: () => ({ height: '100%' }) }, - mins: { default: () => ({ x: 200, y: 120 }) }, - }, -}) -export class SourceSelector extends ReactComponent {} - -@Component({ - props: { - name: { default: 'SideNav' }, - wrapperStyles: { default: () => ({ height: '100%' }) }, - }, -}) -export class SideNav extends ReactComponent {} - @Component({ props: { name: { default: 'SourceProperties' }, @@ -432,29 +283,6 @@ export class SourceShowcase extends ReactComponent {} @Component({ props: { name: { default: 'StartStreamingButton' } } }) export class StartStreamingButton extends ReactComponent {} -@Component({ props: { name: { default: 'StreamScheduler' } } }) -export class StreamScheduler extends ReactComponent {} - -@Component({ props: { name: { default: 'Studio' } } }) -export class Studio extends ReactComponent {} - -@Component({ props: { name: { default: 'StudioEditor' } } }) -export class StudioEditor extends ReactComponent {} - -@Component({ - props: { - name: { default: 'StudioFooter' }, - wrapperStyles: { - default: () => ({ - 'grid-row': '2 / span 1', - display: 'flex', - 'min-width': 0, - }), - }, - }, -}) -export class StudioFooter extends ReactComponent {} - @Component({ props: { name: { default: 'TestWidgets' }, @@ -463,9 +291,6 @@ export class StudioFooter extends ReactComponent {} }) export class TestWidgets extends ReactComponent<{ testers: string[] }> {} -@Component({ props: { name: { default: 'ThemeAudit' } } }) -export class ThemeAudit extends ReactComponent {} - @Component({ props: { name: { default: 'TitleBar' }, @@ -523,13 +348,3 @@ export class Hotkeys extends ReactComponent<{ highlightSearch: (searchStr: string) => void; scanning: boolean; }> {} - -@Component({ - props: { - name: { default: 'GLVolmeters' }, - wrapperStyles: { - default: () => ({ position: 'absolute', left: '17px', right: '17px', height: '100%' }), - }, - }, -}) -export class GLVolmeters extends ReactComponent {} diff --git a/app/components/windows/Main.tsx b/app/components/windows/Main.tsx new file mode 100644 index 000000000000..1db29ee06059 --- /dev/null +++ b/app/components/windows/Main.tsx @@ -0,0 +1,18 @@ +import { Component } from 'vue-property-decorator'; +import TsxComponent from 'components/tsx-component'; +import { Main as MainWindow } from 'components/shared/ReactComponentList'; + +@Component({}) +export default class Main extends TsxComponent<{}> { + // TODO: Not sure how to access Vue $store directly in React so using a wrapper component for now + render() { + return ( + + ); + } +} diff --git a/app/components/windows/Main.vue b/app/components/windows/Main.vue deleted file mode 100644 index 50ec714f7a16..000000000000 --- a/app/components/windows/Main.vue +++ /dev/null @@ -1,210 +0,0 @@ - - - - - - - diff --git a/app/components/windows/Main.vue.ts b/app/components/windows/Main.vue.ts deleted file mode 100644 index eb4a25753c9b..000000000000 --- a/app/components/windows/Main.vue.ts +++ /dev/null @@ -1,340 +0,0 @@ -import Vue from 'vue'; -import { Component, Watch } from 'vue-property-decorator'; -import { - TitleBar, - Grow, - PatchNotes, - Loader, - StreamScheduler, - StudioFooter, - Highlighter, - ThemeAudit, - LayoutEditor, - Onboarding, - SideNav, - PlatformMerge, - AlertboxLibrary, - PlatformAppStore, - BrowseOverlays, - PlatformAppMainPage, - RecordingHistory, - Studio, - LiveDock, -} from 'components/shared/ReactComponentList'; -import { ScenesService } from 'services/scenes'; -import { PlatformAppsService } from 'services/platform-apps'; -import { EditorCommandsService } from '../../app-services'; -import VueResize from 'vue-resize'; -import { $t } from 'services/i18n'; -import fs from 'fs'; -import * as remote from '@electron/remote'; -Vue.use(VueResize); - -// Pages -import { Inject } from '../../services/core/injector'; -import { CustomizationService } from 'services/customization'; -import { NavigationService } from 'services/navigation'; -import { AppService } from 'services/app'; -import { UserService } from 'services/user'; -import { IModalOptions, WindowsService } from 'services/windows'; -import ResizeBar from 'components/shared/ResizeBar.vue'; -import { getPlatformService } from 'services/platforms'; -import ModalWrapper from '../shared/modals/ModalWrapper'; -import antdThemes from 'styles/antd/index'; - -const loadedTheme = () => { - const customizationState = localStorage.getItem('PersistentStatefulService-CustomizationService'); - if (customizationState) { - return JSON.parse(customizationState)?.theme; - } -}; - -@Component({ - components: { - TitleBar, - SideNav, - Studio, - BrowseOverlays, - Onboarding, - LiveDock, - StudioFooter, - CustomLoader: Loader, - PatchNotes, - PlatformAppMainPage, - PlatformAppStore, - ResizeBar, - PlatformMerge, - LayoutEditor, - AlertboxLibrary, - ModalWrapper, - RecordingHistory, - StreamScheduler, - Highlighter, - Grow, - ThemeAudit, - }, -}) -export default class Main extends Vue { - @Inject() customizationService: CustomizationService; - @Inject() navigationService: NavigationService; - @Inject() appService: AppService; - @Inject() userService: UserService; - @Inject() windowsService: WindowsService; - @Inject() scenesService: ScenesService; - @Inject() platformAppsService: PlatformAppsService; - @Inject() editorCommandsService: EditorCommandsService; - - private modalOptions: IModalOptions = { - renderFn: null, - }; - - created() { - window.addEventListener('resize', this.windowSizeHandler); - } - - mounted() { - antdThemes[this.theme].use(); - WindowsService.modalChanged.subscribe(modalOptions => { - this.modalOptions = { ...this.modalOptions, ...modalOptions }; - }); - this.updateLiveDockContraints(); - } - - get uiReady() { - return this.$store.state.bulkLoadFinished && this.$store.state.i18nReady; - } - - @Watch('theme') - updateAntd(newTheme: string, oldTheme: string) { - antdThemes[oldTheme].unuse(); - antdThemes[newTheme].use(); - } - - @Watch('uiReady') - initializeResize() { - this.$nextTick(() => { - const dockWidth = this.customizationService.state.livedockSize; - if (dockWidth < 1) { - // migrate from old percentage value to the pixel value - this.resetWidth(); - } - this.handleResize(); - }); - } - - destroyed() { - window.removeEventListener('resize', this.windowSizeHandler); - } - - minEditorWidth = 500; - - get title() { - return this.windowsService.state.main.title; - } - - get page() { - return this.navigationService.state.currentPage; - } - - get params() { - return this.navigationService.state.params; - } - - get theme() { - if (this.$store.state.bulkLoadFinished) { - return this.customizationService.currentTheme; - } - - return loadedTheme() || 'night-theme'; - } - - get applicationLoading() { - return this.appService.state.loading; - } - - get showLoadingSpinner() { - return ( - this.appService.state.loading && this.page !== 'Onboarding' && this.page !== 'BrowseOverlays' - ); - } - - get isLoggedIn() { - return this.userService.isLoggedIn; - } - - get renderDock() { - return ( - this.isLoggedIn && - !this.isOnboarding && - this.hasLiveDock && - getPlatformService(this.userService.platform.type).liveDockEnabled && - !this.showLoadingSpinner - ); - } - - get liveDockSize() { - return this.customizationService.state.livedockSize; - } - - get isDockCollapsed() { - return this.customizationService.state.livedockCollapsed; - } - - get leftDock() { - return this.customizationService.state.leftDock; - } - - get isOnboarding() { - return this.navigationService.state.currentPage === 'Onboarding'; - } - - get platformApps() { - return this.platformAppsService.enabledApps; - } - - get errorAlert() { - return this.appService.state.errorAlert; - } - - async isDirectory(path: string) { - return new Promise((resolve, reject) => { - fs.lstat(path, (err, stats) => { - if (err) { - reject(err); - } - resolve(stats.isDirectory()); - }); - }); - } - - async onDropHandler(event: DragEvent) { - if (this.page !== 'Studio') return; - - const fileList = event.dataTransfer.files; - - if (fileList.length < 1) return; - - const files: string[] = []; - let fi = fileList.length; - while (fi--) files.push(fileList.item(fi).path); - - const isDirectory = await this.isDirectory(files[0]).catch(err => { - console.error('Error checking if drop is directory', err); - return false; - }); - - if (files.length > 1 || isDirectory) { - remote.dialog - .showMessageBox(remote.getCurrentWindow(), { - title: 'Streamlabs Desktop', - message: $t('Are you sure you want to import multiple files?'), - type: 'warning', - buttons: [$t('Cancel'), $t('OK')], - }) - .then(({ response }) => { - if (!response) return; - this.executeFileDrop(files); - }); - } else { - this.executeFileDrop(files); - } - } - - executeFileDrop(files: string[]) { - this.editorCommandsService.actions.executeCommand( - 'AddFilesCommand', - this.scenesService.views.activeSceneId, - files, - ); - } - - $refs: { - mainMiddle: HTMLDivElement; - }; - - compactView = false; - - get mainResponsiveClasses() { - const classes = []; - - if (this.compactView) { - classes.push('main-middle--compact'); - } - - return classes.join(' '); - } - - windowWidth: number; - - hasLiveDock = true; - - windowResizeTimeout: number; - - minDockWidth = 290; - maxDockWidth = this.minDockWidth; - - updateLiveDockContraints() { - const appRect = this.$root.$el.getBoundingClientRect(); - this.maxDockWidth = Math.min(appRect.width - this.minEditorWidth, appRect.width / 2); - this.minDockWidth = Math.min(290, this.maxDockWidth); - } - - windowSizeHandler() { - if (!this.windowsService.state.main.hideStyleBlockers) { - this.onResizeStartHandler(); - } - this.windowWidth = window.innerWidth; - - clearTimeout(this.windowResizeTimeout); - - this.hasLiveDock = this.windowWidth >= 1070; - if (this.page === 'Studio') { - this.hasLiveDock = this.windowWidth >= this.minEditorWidth + 100; - } - this.windowResizeTimeout = window.setTimeout(() => { - this.windowsService.actions.updateStyleBlockers('main', false); - this.updateLiveDockContraints(); - this.updateWidth(); - }, 200); - } - - handleResize() { - this.compactView = this.$refs.mainMiddle.clientWidth < 1200; - } - - handleEditorWidth(width: number) { - this.minEditorWidth = width; - } - - onResizeStartHandler() { - this.windowsService.actions.updateStyleBlockers('main', true); - } - - onResizeStopHandler(offset: number) { - this.setWidth(this.customizationService.state.livedockSize + offset); - this.windowsService.actions.updateStyleBlockers('main', false); - } - - setWidth(width: number) { - this.customizationService.actions.setSettings({ - livedockSize: this.validateWidth(width), - }); - } - - validateWidth(width: number): number { - let constrainedWidth = Math.max(this.minDockWidth, width); - constrainedWidth = Math.min(this.maxDockWidth, width); - return constrainedWidth; - } - - updateWidth() { - const width = this.customizationService.state.livedockSize; - if (width !== this.validateWidth(width)) this.setWidth(width); - } - - resetWidth() { - const appRect = this.$root.$el.getBoundingClientRect(); - const defaultWidth = appRect.width * 0.28; - this.setWidth(defaultWidth); - } -} diff --git a/app/services/windows.ts b/app/services/windows.ts index 8c7f4a6ed0c3..95ee93854460 100644 --- a/app/services/windows.ts +++ b/app/services/windows.ts @@ -10,7 +10,7 @@ import { Subject } from 'rxjs'; import { throttle } from 'lodash-decorators'; import * as remote from '@electron/remote'; -import Main from 'components/windows/Main.vue'; +import Main from 'components/windows/Main'; import Settings from 'components/windows/settings/Settings.vue'; import FFZSettings from 'components/windows/FFZSettings.vue'; import SceneTransitions from 'components/windows/SceneTransitions.vue'; @@ -40,7 +40,6 @@ import { NotificationsAndNews, PlatformAppPopOut, RecentEventsWindow, - RecordingHistory, EditTransform, Blank, } from 'components/shared/ReactComponentList'; @@ -142,7 +141,6 @@ export function getComponents() { WidgetWindow, CustomCodeWindow, SourceShowcase, - RecordingHistory, }; } From 8880d0a5808c3f2003a4ee8058b2ebe868ca8894 Mon Sep 17 00:00:00 2001 From: Sean Beyer Date: Tue, 9 Jan 2024 14:08:32 -0800 Subject: [PATCH 14/51] Fix rendering bugs --- app/components-react/windows/Main.tsx | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/app/components-react/windows/Main.tsx b/app/components-react/windows/Main.tsx index 6d7b573aa270..9a9fa7a3d8d2 100644 --- a/app/components-react/windows/Main.tsx +++ b/app/components-react/windows/Main.tsx @@ -76,7 +76,7 @@ class MainController { } get hideStyleBlockers() { - return this.windowsService.state.mian.hideStyleBlockers; + return this.windowsService.state.main.hideStyleBlockers; } theme(bulkLoadFinished: boolean) { @@ -198,7 +198,9 @@ class MainController { } handleEditorWidth(width: number) { - this.store.setState(s => (s.minEditorWidth = width)); + this.store.setState(s => { + s.minEditorWidth = width; + }); } onResize(offset: number) { @@ -293,14 +295,18 @@ function Main(p: { bulkLoadFinished: boolean; i18nReady: boolean }) { if (!hideStyleBlockers) { ctrl.updateStyleBlockers(true); } - ctrl.store.setState(s => (s.windowWidth = window.innerWidth)); + ctrl.store.setState(s => { + s.windowWidth = window.innerWidth; + }); if (ctrl.windowResizeTimeout) clearTimeout(ctrl.windowResizeTimeout); - ctrl.store.setState(s => (s.hasLiveDock = s.windowWidth >= 1070)); - if (ctrl.page === 'Studio') { - ctrl.store.setState(s => (s.hasLiveDock = s.windowWidth >= s.minEditorWidth + 100)); - } + ctrl.store.setState(s => { + s.hasLiveDock = s.windowWidth >= 1070; + if (ctrl.page === 'Studio') { + s.hasLiveDock = s.windowWidth >= s.minEditorWidth + 100; + } + }); ctrl.windowResizeTimeout = window.setTimeout(() => { ctrl.updateStyleBlockers(false); const appRect = mainWindowEl.current?.getBoundingClientRect(); @@ -343,9 +349,9 @@ function Main(p: { bulkLoadFinished: boolean; i18nReady: boolean }) { }, [uiReady]); useLayoutEffect(() => { - ctrl.store.setState( - s => (s.compactView = !!mainMiddleEl.current && mainMiddleEl.current.clientWidth < 1200), - ); + ctrl.store.setState(s => { + s.compactView = !!mainMiddleEl.current && mainMiddleEl.current.clientWidth < 1200; + }); }, [uiReady, hideStyleBlockers]); if (!uiReady) return
; From 36dc9db1505fa382e56cf0039c80aac70dd6ba5c Mon Sep 17 00:00:00 2001 From: Sean Beyer Date: Tue, 9 Jan 2024 16:18:12 -0800 Subject: [PATCH 15/51] Add misssing classes --- app/components-react/editor/layouts/Classic.tsx | 2 +- app/components-react/editor/layouts/Default.tsx | 2 +- app/components-react/editor/layouts/FourByFour.tsx | 2 +- app/components-react/editor/layouts/OnePane.tsx | 2 +- app/components-react/editor/layouts/OnePaneR.tsx | 2 +- app/components-react/editor/layouts/Pyramid.tsx | 2 +- app/components-react/editor/layouts/Triplets.tsx | 2 +- app/components-react/editor/layouts/TwoPane.tsx | 2 +- app/components-react/editor/layouts/hooks.tsx | 1 + app/components-react/root/ResizeBar.tsx | 3 +-- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/app/components-react/editor/layouts/Classic.tsx b/app/components-react/editor/layouts/Classic.tsx index 188384dc315f..7054ca4ddb9d 100644 --- a/app/components-react/editor/layouts/Classic.tsx +++ b/app/components-react/editor/layouts/Classic.tsx @@ -13,7 +13,7 @@ export function Classic(p: React.PropsWithChildren) { ); return ( -
+
{p.children?.['1'] || <>}
diff --git a/app/components-react/editor/layouts/Default.tsx b/app/components-react/editor/layouts/Default.tsx index 3b2b39d87981..9da7fdc6cdb1 100644 --- a/app/components-react/editor/layouts/Default.tsx +++ b/app/components-react/editor/layouts/Default.tsx @@ -13,7 +13,7 @@ export function Default(p: React.PropsWithChildren) { ); return ( -
+
) { ); return ( -
+
) { ); return ( -
+
) { ); return ( -
+
) { ); return ( -
+
{p.children?.['1'] || <>}
diff --git a/app/components-react/editor/layouts/Triplets.tsx b/app/components-react/editor/layouts/Triplets.tsx index bce5e2cad9d2..2dc4580441b3 100644 --- a/app/components-react/editor/layouts/Triplets.tsx +++ b/app/components-react/editor/layouts/Triplets.tsx @@ -17,7 +17,7 @@ export function Triplets(p: React.PropsWithChildren) { ); return ( -
+
) { ); return ( -
+
void = () => {}; childrenMins: Dictionary; + className?: string; } export interface IResizeMins { diff --git a/app/components-react/root/ResizeBar.tsx b/app/components-react/root/ResizeBar.tsx index 738d60dc6e78..edf8e0519108 100644 --- a/app/components-react/root/ResizeBar.tsx +++ b/app/components-react/root/ResizeBar.tsx @@ -79,9 +79,8 @@ export default function ResizeBar(p: React.PropsWithChildren) { onResize={handleResize(p.onInput)} transformScale={2} {...resizableProps} - className={p.className} handle={ -
+
} From 79a5563821acea4a146a2ea4ca5518c51e0f6383 Mon Sep 17 00:00:00 2001 From: Sean Beyer Date: Wed, 10 Jan 2024 14:01:42 -0800 Subject: [PATCH 16/51] Change scope of windowWidth --- app/components-react/windows/Main.tsx | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/app/components-react/windows/Main.tsx b/app/components-react/windows/Main.tsx index 9a9fa7a3d8d2..da8e6ef8fc00 100644 --- a/app/components-react/windows/Main.tsx +++ b/app/components-react/windows/Main.tsx @@ -52,7 +52,6 @@ class MainController { store = initStore({ compactView: false, - windowWidth: 0, hasLiveDock: true, minDockWidth: 290, maxDockWidth: 290, @@ -295,17 +294,13 @@ function Main(p: { bulkLoadFinished: boolean; i18nReady: boolean }) { if (!hideStyleBlockers) { ctrl.updateStyleBlockers(true); } - ctrl.store.setState(s => { - s.windowWidth = window.innerWidth; - }); + const windowWidth = window.innerWidth; if (ctrl.windowResizeTimeout) clearTimeout(ctrl.windowResizeTimeout); ctrl.store.setState(s => { - s.hasLiveDock = s.windowWidth >= 1070; - if (ctrl.page === 'Studio') { - s.hasLiveDock = s.windowWidth >= s.minEditorWidth + 100; - } + s.hasLiveDock = + ctrl.page === 'Studio' ? windowWidth >= s.minEditorWidth + 100 : windowWidth >= 1070; }); ctrl.windowResizeTimeout = window.setTimeout(() => { ctrl.updateStyleBlockers(false); From c32edc26295d0edcba63303d7f0ef05d2d96b199 Mon Sep 17 00:00:00 2001 From: Sean Beyer Date: Fri, 19 Jan 2024 11:03:13 -0800 Subject: [PATCH 17/51] Remove props from main window --- app/app.ts | 4 +- app/components-react/root/ReactRoot.tsx | 2 +- app/components-react/windows/Main.tsx | 40 ++++++++++++++------ app/components/shared/ReactComponentList.tsx | 1 - app/components/windows/Main.tsx | 18 --------- app/services/windows.ts | 2 +- 6 files changed, 31 insertions(+), 36 deletions(-) delete mode 100644 app/components/windows/Main.tsx diff --git a/app/app.ts b/app/app.ts index 9fee32930376..4f5df7c824d6 100644 --- a/app/app.ts +++ b/app/app.ts @@ -30,9 +30,7 @@ import { getResource } from 'services'; import * as obs from '../obs-api'; import path from 'path'; import util from 'util'; -import uuid from 'uuid/v4'; -import Main from 'components/windows/Main'; -import { Loader, Blank } from 'components/shared/ReactComponentList'; +import { Loader, Blank, Main } from 'components/shared/ReactComponentList'; import process from 'process'; import { MetricsService } from 'services/metrics'; import { UsageStatisticsService } from 'services/usage-statistics'; diff --git a/app/components-react/root/ReactRoot.tsx b/app/components-react/root/ReactRoot.tsx index bacf753fe59e..00db025828fd 100644 --- a/app/components-react/root/ReactRoot.tsx +++ b/app/components-react/root/ReactRoot.tsx @@ -29,7 +29,7 @@ class VuexModule { // watch for mutations from the global Vuex store // and increment the revision number for affected StatefulService StatefulService.store.subscribe(mutation => { - if (!mutation.payload.__vuexSyncIgnore) return; + if (mutation.payload && !mutation.payload.__vuexSyncIgnore) return; const serviceName = mutation.type.split('.')[0]; const module = this.resolveState(serviceName); module.incrementRevision(); diff --git a/app/components-react/windows/Main.tsx b/app/components-react/windows/Main.tsx index da8e6ef8fc00..456e6de74a19 100644 --- a/app/components-react/windows/Main.tsx +++ b/app/components-react/windows/Main.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useLayoutEffect, useMemo, useRef } from 'react'; +import React, { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'; import fs from 'fs'; import * as remote from '@electron/remote'; import cx from 'classnames'; @@ -20,6 +20,7 @@ import antdThemes from 'styles/antd/index'; import { getPlatformService } from 'services/platforms'; import { IModalOptions } from 'services/windows'; import styles from './Main.m.less'; +import { StatefulService } from 'services'; const MainCtx = React.createContext(null); @@ -240,17 +241,26 @@ class MainController { } } -export default function MainWithContext(p: { bulkLoadFinished: boolean; i18nReady: boolean }) { +export default function MainWithContext() { const controller = useMemo(() => new MainController(), []); return ( -
+
); } -function Main(p: { bulkLoadFinished: boolean; i18nReady: boolean }) { +function Main() { const ctrl = useController(MainCtx); + + const mainWindowEl = useRef(null); + const mainMiddleEl = useRef(null); + + const [bulkLoadFinished, setBulkLoadFinished] = useState(false); + const [i18nReady, seti18nReady] = useState(false); + + const uiReady = bulkLoadFinished && i18nReady; + const { theme, dockWidth, @@ -268,7 +278,7 @@ function Main(p: { bulkLoadFinished: boolean; i18nReady: boolean }) { mainResponsiveClasses, hideStyleBlockers, } = useVuex(() => ({ - theme: ctrl.theme(p.bulkLoadFinished), + theme: ctrl.theme(bulkLoadFinished), dockWidth: ctrl.dockWidth, showLoadingSpinner: ctrl.showLoadingSpinner, errorAlert: ctrl.errorAlert, @@ -285,10 +295,14 @@ function Main(p: { bulkLoadFinished: boolean; i18nReady: boolean }) { hideStyleBlockers: ctrl.hideStyleBlockers, })); - const uiReady = p.bulkLoadFinished && p.i18nReady; + useEffect(() => { + const unsubscribe = StatefulService.store.subscribe((_, state) => { + if (state.bulkLoadFinished) setBulkLoadFinished(true); + if (state.i18nReady) seti18nReady(true); + }); - const mainWindowEl = useRef(null); - const mainMiddleEl = useRef(null); + return unsubscribe; + }, []); function windowSizeHandler() { if (!hideStyleBlockers) { @@ -375,7 +389,6 @@ function Main(p: { bulkLoadFinished: boolean; i18nReady: boolean }) { {page !== 'Onboarding' && !showLoadingSpinner && } {renderDock && leftDock && (
- {!isDockCollapsed && ( + > + + )}
)} @@ -410,9 +425,10 @@ function Main(p: { bulkLoadFinished: boolean; i18nReady: boolean }) { max={maxDockWidth} min={minDockWidth} value={liveDockSize} - /> + > + + )} -
)}
diff --git a/app/components/shared/ReactComponentList.tsx b/app/components/shared/ReactComponentList.tsx index 4bac1b549811..554816b0e6eb 100644 --- a/app/components/shared/ReactComponentList.tsx +++ b/app/components/shared/ReactComponentList.tsx @@ -111,7 +111,6 @@ export class Loader extends ReactComponent {} props: { name: { default: 'Main' }, wrapperStyles: { default: () => ({ height: '100%' }) }, - componentProps: { default: () => ({ bulkLoadFinished: false, i18nReady: false }) }, }, }) export class Main extends ReactComponent {} diff --git a/app/components/windows/Main.tsx b/app/components/windows/Main.tsx deleted file mode 100644 index 1db29ee06059..000000000000 --- a/app/components/windows/Main.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { Component } from 'vue-property-decorator'; -import TsxComponent from 'components/tsx-component'; -import { Main as MainWindow } from 'components/shared/ReactComponentList'; - -@Component({}) -export default class Main extends TsxComponent<{}> { - // TODO: Not sure how to access Vue $store directly in React so using a wrapper component for now - render() { - return ( - - ); - } -} diff --git a/app/services/windows.ts b/app/services/windows.ts index 95ee93854460..add1ba7140c6 100644 --- a/app/services/windows.ts +++ b/app/services/windows.ts @@ -10,7 +10,6 @@ import { Subject } from 'rxjs'; import { throttle } from 'lodash-decorators'; import * as remote from '@electron/remote'; -import Main from 'components/windows/Main'; import Settings from 'components/windows/settings/Settings.vue'; import FFZSettings from 'components/windows/FFZSettings.vue'; import SceneTransitions from 'components/windows/SceneTransitions.vue'; @@ -42,6 +41,7 @@ import { RecentEventsWindow, EditTransform, Blank, + Main, } from 'components/shared/ReactComponentList'; import SourcePropertiesDeprecated from 'components/windows/SourceProperties.vue'; From a850c3c0180512399518ce3972b6dc884cf0c217 Mon Sep 17 00:00:00 2001 From: Sean Beyer Date: Tue, 23 Jan 2024 14:34:35 -0800 Subject: [PATCH 18/51] Move to native react rendering --- app/app.ts | 90 +++++++++++++++------------ app/components-react/windows/Main.tsx | 8 ++- 2 files changed, 55 insertions(+), 43 deletions(-) diff --git a/app/app.ts b/app/app.ts index 4f5df7c824d6..efb81b7b6181 100644 --- a/app/app.ts +++ b/app/app.ts @@ -30,12 +30,17 @@ import { getResource } from 'services'; import * as obs from '../obs-api'; import path from 'path'; import util from 'util'; -import { Loader, Blank, Main } from 'components/shared/ReactComponentList'; +import { Loader, Blank } from 'components/shared/ReactComponentList'; import process from 'process'; import { MetricsService } from 'services/metrics'; import { UsageStatisticsService } from 'services/usage-statistics'; import * as remote from '@electron/remote'; +// For React Windows +import React from 'react'; +import ReactDOM from 'react-dom'; +import Main from 'components-react/windows/Main'; + const { ipcRenderer } = electron; const slobsVersion = Utils.env.SLOBS_VERSION; const isProduction = Utils.env.NODE_ENV === 'production'; @@ -330,42 +335,54 @@ document.addEventListener('DOMContentLoaded', async () => { }); } - // create a root Vue component const windowId = Utils.getCurrentUrlParams().windowId; - const vm = new Vue({ - i18n, - store, - el: '#app', - data: { isRefreshing: false }, - methods: { - // refresh current window - startWindowRefresh() { - // set isRefreshing to true to unmount all components and destroy Displays - this.isRefreshing = true; - - // unregister current window from the crash handler - ipcRenderer.send('unregister-in-crash-handler', { pid: process.pid }); - - // give the window some time to finish unmounting before reload - Utils.sleep(100).then(() => { - window.location.reload(); - }); + + if (windowId !== 'main') { + // create a root Vue component + const vm = new Vue({ + i18n, + store, + el: '#app', + data: { isRefreshing: false }, + methods: { + // refresh current window + startWindowRefresh() { + // set isRefreshing to true to unmount all components and destroy Displays + this.isRefreshing = true; + + // unregister current window from the crash handler + ipcRenderer.send('unregister-in-crash-handler', { pid: process.pid }); + + // give the window some time to finish unmounting before reload + Utils.sleep(100).then(() => { + window.location.reload(); + }); + }, }, - }, - render(h) { - if (this.isRefreshing) return h(Blank); - if (windowId === 'worker') return h(Blank); - if (windowId === 'child') { - if (store.state.bulkLoadFinished && store.state.i18nReady) { - return h(ChildWindow); + render(h) { + if (this.isRefreshing) return h(Blank); + if (windowId === 'worker') return h(Blank); + if (windowId === 'child') { + if (store.state.bulkLoadFinished && store.state.i18nReady) { + return h(ChildWindow); + } + + return h(Loader); } + return h(OneOffWindow); + }, + }); - return h(Loader); - } - if (windowId === 'main') return h(Main); - return h(OneOffWindow); - }, - }); + // allow to refresh the window by pressing `F5` in the DevMode + if (Utils.isDevMode()) { + window.addEventListener('keyup', ev => { + if (ev.key === 'F5') vm.startWindowRefresh(); + }); + } + } else { + // create a roote React component + ReactDOM.render(React.createElement(Main), document.getElementById('app')); + } let mainWindowShowTime = 0; if (Utils.isMainWindow()) { @@ -393,13 +410,6 @@ document.addEventListener('DOMContentLoaded', async () => { if (ctx) setSentryContext(ctx); userService.sentryContext.subscribe(setSentryContext); } - - // allow to refresh the window by pressing `F5` in the DevMode - if (Utils.isDevMode()) { - window.addEventListener('keyup', ev => { - if (ev.key === 'F5') vm.startWindowRefresh(); - }); - } }); }); diff --git a/app/components-react/windows/Main.tsx b/app/components-react/windows/Main.tsx index 456e6de74a19..81137abd18aa 100644 --- a/app/components-react/windows/Main.tsx +++ b/app/components-react/windows/Main.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'; +import React, { ReactElement, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'; import fs from 'fs'; import * as remote from '@electron/remote'; import cx from 'classnames'; @@ -241,7 +241,7 @@ class MainController { } } -export default function MainWithContext() { +export default function MainWithContext(): ReactElement<{}> { const controller = useMemo(() => new MainController(), []); return ( @@ -371,6 +371,8 @@ function Main() { onTotalWidth: (width: number) => void; }> = appPages[page]; + console.log(uiReady); + return (
- + {(!uiReady || showLoadingSpinner) && (
From a5e7821899bdbf9a4eac7a53eb9d56fb88f4c049 Mon Sep 17 00:00:00 2001 From: Sean Beyer Date: Tue, 23 Jan 2024 16:37:55 -0800 Subject: [PATCH 19/51] Revert to Vue wrapper component --- app/app.ts | 97 +++++++++++++++++---------------- app/components/windows/Main.tsx | 41 ++++++++++++++ 2 files changed, 92 insertions(+), 46 deletions(-) create mode 100644 app/components/windows/Main.tsx diff --git a/app/app.ts b/app/app.ts index efb81b7b6181..78a1e7b48b31 100644 --- a/app/app.ts +++ b/app/app.ts @@ -31,15 +31,17 @@ import * as obs from '../obs-api'; import path from 'path'; import util from 'util'; import { Loader, Blank } from 'components/shared/ReactComponentList'; +import Main from 'components/windows/Main'; import process from 'process'; import { MetricsService } from 'services/metrics'; import { UsageStatisticsService } from 'services/usage-statistics'; import * as remote from '@electron/remote'; -// For React Windows -import React from 'react'; -import ReactDOM from 'react-dom'; -import Main from 'components-react/windows/Main'; +// // TODO: commented until we remove slap library +// // For React Windows +// import React from 'react'; +// import ReactDOM from 'react-dom'; +// import Main from 'components-react/windows/Main'; const { ipcRenderer } = electron; const slobsVersion = Utils.env.SLOBS_VERSION; @@ -337,52 +339,55 @@ document.addEventListener('DOMContentLoaded', async () => { const windowId = Utils.getCurrentUrlParams().windowId; - if (windowId !== 'main') { - // create a root Vue component - const vm = new Vue({ - i18n, - store, - el: '#app', - data: { isRefreshing: false }, - methods: { - // refresh current window - startWindowRefresh() { - // set isRefreshing to true to unmount all components and destroy Displays - this.isRefreshing = true; - - // unregister current window from the crash handler - ipcRenderer.send('unregister-in-crash-handler', { pid: process.pid }); - - // give the window some time to finish unmounting before reload - Utils.sleep(100).then(() => { - window.location.reload(); - }); - }, + // // TODO: commented until we remove slap library + // if (windowId !== 'main') { + // create a root Vue component + const vm = new Vue({ + i18n, + store, + el: '#app', + data: { isRefreshing: false }, + methods: { + // refresh current window + startWindowRefresh() { + // set isRefreshing to true to unmount all components and destroy Displays + this.isRefreshing = true; + + // unregister current window from the crash handler + ipcRenderer.send('unregister-in-crash-handler', { pid: process.pid }); + + // give the window some time to finish unmounting before reload + Utils.sleep(100).then(() => { + window.location.reload(); + }); }, - render(h) { - if (this.isRefreshing) return h(Blank); - if (windowId === 'worker') return h(Blank); - if (windowId === 'child') { - if (store.state.bulkLoadFinished && store.state.i18nReady) { - return h(ChildWindow); - } - - return h(Loader); + }, + render(h) { + if (this.isRefreshing) return h(Blank); + if (windowId === 'worker') return h(Blank); + if (windowId === 'main') return h(Main); + if (windowId === 'child') { + if (store.state.bulkLoadFinished && store.state.i18nReady) { + return h(ChildWindow); } - return h(OneOffWindow); - }, - }); - // allow to refresh the window by pressing `F5` in the DevMode - if (Utils.isDevMode()) { - window.addEventListener('keyup', ev => { - if (ev.key === 'F5') vm.startWindowRefresh(); - }); - } - } else { - // create a roote React component - ReactDOM.render(React.createElement(Main), document.getElementById('app')); + return h(Loader); + } + return h(OneOffWindow); + }, + }); + + // allow to refresh the window by pressing `F5` in the DevMode + if (Utils.isDevMode()) { + window.addEventListener('keyup', ev => { + if (ev.key === 'F5') vm.startWindowRefresh(); + }); } + // // TODO: commented until we remove slap library + // } else { + // // create a roote React component + // ReactDOM.render(React.createElement(Main), document.getElementById('app')); + // } let mainWindowShowTime = 0; if (Utils.isMainWindow()) { diff --git a/app/components/windows/Main.tsx b/app/components/windows/Main.tsx new file mode 100644 index 000000000000..47ebe148b8ae --- /dev/null +++ b/app/components/windows/Main.tsx @@ -0,0 +1,41 @@ +import Vue from 'vue'; +import { Component } from 'vue-property-decorator'; +import cx from 'classnames'; +import TsxComponent from 'components/tsx-component'; +import { Main, Loader } from 'components/shared/ReactComponentList'; +import { Inject } from 'services'; +import { CustomizationService } from 'app-services'; +import antdThemes from 'styles/antd/index'; +import styles from 'components-react/windows/Main.m.less'; + +@Component({}) +export default class MainWindow extends TsxComponent { + @Inject() customizationService: CustomizationService; + + get uiReady() { + return this.$store.state.bulkLoadFinished && this.$store.state.i18nReady; + } + + get theme() { + return this.customizationService.views.currentTheme; + } + + mounted() { + antdThemes[this.theme].use(); + } + + render() { + return ( +
+ {this.uiReady &&
} + + {!this.uiReady && ( +
+ +
+ )} +
+
+ ); + } +} From 130b661f4f90c438dbaeb819a4df902d64ea8db6 Mon Sep 17 00:00:00 2001 From: Sean Beyer Date: Mon, 5 Feb 2024 12:08:13 -0800 Subject: [PATCH 20/51] Fix stale style blockers --- app/components-react/windows/Main.tsx | 64 ++++++++++++--------------- app/components/windows/Main.tsx | 13 +++++- 2 files changed, 41 insertions(+), 36 deletions(-) diff --git a/app/components-react/windows/Main.tsx b/app/components-react/windows/Main.tsx index 81137abd18aa..a18569ac6c59 100644 --- a/app/components-react/windows/Main.tsx +++ b/app/components-react/windows/Main.tsx @@ -1,4 +1,4 @@ -import React, { ReactElement, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'; +import React, { ReactElement, useEffect, useMemo, useRef, useState } from 'react'; import fs from 'fs'; import * as remote from '@electron/remote'; import cx from 'classnames'; @@ -135,16 +135,6 @@ class MainController { return this.appService.state.errorAlert; } - get mainResponsiveClasses() { - const classes = []; - - if (this.store.compactView) { - classes.push('main-middle--compact'); - } - - return classes.join(' '); - } - async isDirectory(path: string) { return new Promise((resolve, reject) => { fs.lstat(path, (err, stats) => { @@ -275,25 +265,28 @@ function Main() { liveDockSize, maxDockWidth, minDockWidth, - mainResponsiveClasses, hideStyleBlockers, - } = useVuex(() => ({ - theme: ctrl.theme(bulkLoadFinished), - dockWidth: ctrl.dockWidth, - showLoadingSpinner: ctrl.showLoadingSpinner, - errorAlert: ctrl.errorAlert, - renderDock: ctrl.renderDock, - leftDock: ctrl.leftDock, - hasLiveDock: ctrl.store.hasLiveDock, - applicationLoading: ctrl.applicationLoading, - page: ctrl.page, - isDockCollapsed: ctrl.isDockCollapsed, - liveDockSize: ctrl.liveDockSize, - maxDockWidth: ctrl.store.maxDockWidth, - minDockWidth: ctrl.store.minDockWidth, - mainResponsiveClasses: ctrl.mainResponsiveClasses, - hideStyleBlockers: ctrl.hideStyleBlockers, - })); + compactView, + } = useVuex( + () => ({ + theme: ctrl.theme(bulkLoadFinished), + dockWidth: ctrl.dockWidth, + showLoadingSpinner: ctrl.showLoadingSpinner, + errorAlert: ctrl.errorAlert, + renderDock: ctrl.renderDock, + leftDock: ctrl.leftDock, + hasLiveDock: ctrl.store.hasLiveDock, + applicationLoading: ctrl.applicationLoading, + page: ctrl.page, + isDockCollapsed: ctrl.isDockCollapsed, + liveDockSize: ctrl.liveDockSize, + maxDockWidth: ctrl.store.maxDockWidth, + minDockWidth: ctrl.store.minDockWidth, + hideStyleBlockers: ctrl.hideStyleBlockers, + compactView: ctrl.store.compactView, + }), + true, + ); useEffect(() => { const unsubscribe = StatefulService.store.subscribe((_, state) => { @@ -328,7 +321,7 @@ function Main() { }, 200); } - useLayoutEffect(() => { + useEffect(() => { window.addEventListener('resize', windowSizeHandler); const modalChangedSub = WindowsService.modalChanged.subscribe(modalOptions => { ctrl.setModalOptions(modalOptions); @@ -338,7 +331,7 @@ function Main() { window.removeEventListener('resize', windowSizeHandler); modalChangedSub.unsubscribe(); }; - }, []); + }, [hideStyleBlockers]); const oldTheme = useRef(null); useEffect(() => { @@ -357,7 +350,7 @@ function Main() { } }, [uiReady]); - useLayoutEffect(() => { + useEffect(() => { ctrl.store.setState(s => { s.compactView = !!mainMiddleEl.current && mainMiddleEl.current.clientWidth < 1200; }); @@ -371,8 +364,6 @@ function Main() { onTotalWidth: (width: number) => void; }> = appPages[page]; - console.log(uiReady); - return (
)} -
+
{!showLoadingSpinner && ( { + const customizationState = localStorage.getItem('PersistentStatefulService-CustomizationService'); + if (customizationState) { + return JSON.parse(customizationState)?.theme; + } +}; + @Component({}) export default class MainWindow extends TsxComponent { @Inject() customizationService: CustomizationService; @@ -17,7 +24,11 @@ export default class MainWindow extends TsxComponent { } get theme() { - return this.customizationService.views.currentTheme; + if (this.$store.state.bulkLoadFinished) { + return this.customizationService.currentTheme; + } + + return loadedTheme() || 'night-theme'; } mounted() { From fea5cc4221e2843266a250f6ee3cec6aa7549831 Mon Sep 17 00:00:00 2001 From: Sean Beyer Date: Fri, 9 Feb 2024 14:49:00 -0800 Subject: [PATCH 21/51] Add css aliases --- app/components-react/pages/RecordingHistory.tsx | 1 + app/components-react/windows/Main.m.less | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/app/components-react/pages/RecordingHistory.tsx b/app/components-react/pages/RecordingHistory.tsx index 4fee4c20772f..3911ec548c8b 100644 --- a/app/components-react/pages/RecordingHistory.tsx +++ b/app/components-react/pages/RecordingHistory.tsx @@ -1,5 +1,6 @@ import React, { useEffect, useMemo } from 'react'; import * as remote from '@electron/remote'; +import cx from 'classnames'; import { Tooltip } from 'antd'; import { $t } from 'services/i18n'; import { ModalLayout } from 'components-react/shared/ModalLayout'; diff --git a/app/components-react/windows/Main.m.less b/app/components-react/windows/Main.m.less index 6578c9c680fa..a2151bcebfd5 100644 --- a/app/components-react/windows/Main.m.less +++ b/app/components-react/windows/Main.m.less @@ -19,14 +19,15 @@ grid-template-columns: auto 1fr; flex-grow: 1; height: 100%; + position: relative; } .main-contents--right { - grid-template-columns: auto auto 1fr; + grid-template-columns: [sidebar] auto [page] auto [livedock] 1fr; } .main-contents--left { - grid-template-columns: auto 1fr auto; + grid-template-columns: [sidebar] auto [livedock] 1fr [page] auto; } .main-contents--onboarding { @@ -38,7 +39,6 @@ } .main-middle { - flex-grow: 1; display: grid; grid-template-rows: minmax(0, 1fr) 48px; position: relative; From 64dc3137a39604faf973a15d72cdbf13dcc90e98 Mon Sep 17 00:00:00 2001 From: Sean Beyer Date: Thu, 22 Feb 2024 10:30:36 -0800 Subject: [PATCH 22/51] Begin livedock restructure --- app/components-react/root/LiveDock.tsx | 202 +++++++++++++---------- app/components-react/windows/Main.m.less | 4 - app/components-react/windows/Main.tsx | 66 +------- 3 files changed, 118 insertions(+), 154 deletions(-) diff --git a/app/components-react/root/LiveDock.tsx b/app/components-react/root/LiveDock.tsx index 1f8536384acb..541d7e9fa54c 100644 --- a/app/components-react/root/LiveDock.tsx +++ b/app/components-react/root/LiveDock.tsx @@ -14,6 +14,7 @@ import Chat from './Chat'; import styles from './LiveDock.m.less'; import Tooltip from 'components-react/shared/Tooltip'; import PlatformAppPageView from 'components-react/shared/PlatformAppPageView'; +import ResizeBar from 'components-react/root/ResizeBar'; import { useVuex } from 'components-react/hooks'; const LiveDockCtx = React.createContext(null); @@ -237,6 +238,22 @@ class LiveDockController { showEditStreamInfo() { this.streamingService.actions.showEditStream(); } + + onResize(offset: number) { + this.setLiveDockWidth(this.customizationService.state.livedockSize + offset); + } + + setLiveDockWidth(width: number) { + this.customizationService.actions.setSettings({ + livedockSize: this.validateWidth(width), + }); + } + + validateWidth(width: number): number { + let constrainedWidth = Math.max(this.store.minDockWidth, width); + constrainedWidth = Math.min(this.store.maxDockWidth, width); + return constrainedWidth; + } } export default function LiveDockWithContext(p: { onLeft?: boolean }) { @@ -344,101 +361,110 @@ function LiveDock(p: { onLeft: boolean }) {
{!collapsed && ( -
-
-
-
- {liveText} - {elapsedStreamTime} -
-
- ctrl.toggleViewerCount()} - /> - {viewerCount} - {Number(viewerCount) >= 0 && {$t('viewers')}} + ctrl.onResize(val)} + max={maxDockWidth} + min={minDockWidth} + value={liveDockSize} + > +
+
+
+
+ {liveText} + {elapsedStreamTime} +
+
+ ctrl.toggleViewerCount()} + /> + {viewerCount} + {Number(viewerCount) >= 0 && {$t('viewers')}} +
-
-
-
- {ctrl.canEditChannelInfo && ( - - ctrl.showEditStreamInfo()} className="icon-edit" /> - - )} - {isPlatform(['youtube', 'facebook', 'trovo']) && isStreaming && ( - - ctrl.openPlatformStream()} className="icon-studio" /> - - )} - {isPlatform(['youtube', 'facebook']) && isStreaming && ( - - ctrl.openPlatformDash()} className="icon-settings" /> - - )} -
-
- {(isPlatform(['twitch', 'trovo', 'facebook']) || - (isPlatform(['youtube', 'twitter']) && isStreaming)) && ( - ctrl.refreshChat()}>{$t('Refresh Chat')} - )} -
-
- {!hideStyleBlockers && - (isPlatform(['twitch', 'trovo']) || - (isStreaming && isPlatform(['youtube', 'facebook', 'twitter']))) && ( -
- {hasChatTabs && ( -
- setChat(ev.key)} - mode="horizontal" - > - {chatTabs.map(tab => ( - {tab.name} - ))} - - {isPopOutAllowed && ( - - ctrl.popOut()} - /> - - )} -
+
+
+ {ctrl.canEditChannelInfo && ( + + ctrl.showEditStreamInfo()} className="icon-edit" /> + )} - {!applicationLoading && !collapsed && ( - + {isPlatform(['youtube', 'facebook', 'trovo']) && isStreaming && ( + + ctrl.openPlatformStream()} className="icon-studio" /> + )} - {!['default', 'restream'].includes(visibleChat) && ( - + {isPlatform(['youtube', 'facebook']) && isStreaming && ( + + ctrl.openPlatformDash()} className="icon-settings" /> + + )} +
+
+ {(isPlatform(['twitch', 'trovo', 'facebook']) || + (isPlatform(['youtube', 'twitter']) && isStreaming)) && ( + ctrl.refreshChat()}>{$t('Refresh Chat')} )}
- )} - {(!ctrl.platform || - (isPlatform(['youtube', 'facebook', 'twitter']) && !isStreaming)) && ( -
- - {!hideStyleBlockers && {$t('Your chat is currently offline')}}
- )} -
+ {!hideStyleBlockers && + (isPlatform(['twitch', 'trovo']) || + (isStreaming && isPlatform(['youtube', 'facebook', 'twitter']))) && ( +
+ {hasChatTabs && ( +
+ setChat(ev.key)} + mode="horizontal" + > + {chatTabs.map(tab => ( + {tab.name} + ))} + + {isPopOutAllowed && ( + + ctrl.popOut()} + /> + + )} +
+ )} + {!applicationLoading && !collapsed && ( + + )} + {!['default', 'restream'].includes(visibleChat) && ( + + )} +
+ )} + {(!ctrl.platform || + (isPlatform(['youtube', 'facebook', 'twitter']) && !isStreaming)) && ( +
+ + {!hideStyleBlockers && {$t('Your chat is currently offline')}} +
+ )} +
+ )}
diff --git a/app/components-react/windows/Main.m.less b/app/components-react/windows/Main.m.less index a2151bcebfd5..ceeb701d48f3 100644 --- a/app/components-react/windows/Main.m.less +++ b/app/components-react/windows/Main.m.less @@ -96,10 +96,6 @@ height: 100%; } -.live-dock-wrapper { - position: relative; -} - .live-dock-resize-bar { position: absolute; height: calc(100% - 20px); diff --git a/app/components-react/windows/Main.tsx b/app/components-react/windows/Main.tsx index a18569ac6c59..3b93b9d40417 100644 --- a/app/components-react/windows/Main.tsx +++ b/app/components-react/windows/Main.tsx @@ -6,7 +6,6 @@ import Animation from 'rc-animate'; import { $t } from 'services/i18n'; import { initStore, useController } from 'components-react/hooks/zustand'; import { useVuex } from 'components-react/hooks'; -import ResizeBar from 'components-react/root/ResizeBar'; import * as appPages from 'components-react/pages'; import TitleBar from 'components-react/shared/TitleBar'; import ModalWrapper from 'components-react/shared/modals/ModalWrapper'; @@ -193,27 +192,6 @@ class MainController { }); } - onResize(offset: number) { - this.setLiveDockWidth(this.customizationService.state.livedockSize + offset); - } - - setLiveDockWidth(width: number) { - this.customizationService.actions.setSettings({ - livedockSize: this.validateWidth(width), - }); - } - - validateWidth(width: number): number { - let constrainedWidth = Math.max(this.store.minDockWidth, width); - constrainedWidth = Math.min(this.store.maxDockWidth, width); - return constrainedWidth; - } - - updateWidth() { - const width = this.customizationService.state.livedockSize; - if (width !== this.validateWidth(width)) this.setWidth(width); - } - updateLiveDockWidth() { if (this.liveDockSize !== this.validateWidth(this.liveDockSize)) { this.setLiveDockWidth(this.liveDockSize); @@ -223,12 +201,6 @@ class MainController { updateStyleBlockers(val: boolean) { this.windowsService.actions.updateStyleBlockers('main', val); } - - setWidth(width: number) { - this.customizationService.actions.setSettings({ - livedockSize: this.validateWidth(width), - }); - } } export default function MainWithContext(): ReactElement<{}> { @@ -317,7 +289,7 @@ function Main() { s.maxDockWidth = Math.min(appRect.width - s.minEditorWidth, appRect.width / 2); s.minDockWidth = Math.min(290, s.maxDockWidth); }); - ctrl.updateWidth(); + ctrl.updateLiveDockWidth(); }, 200); } @@ -346,7 +318,7 @@ function Main() { // migrate from old percentage value to the pixel value const appRect = mainWindowEl.current.getBoundingClientRect(); const defaultWidth = appRect.width * 0.28; - ctrl.setWidth(defaultWidth); + ctrl.setLiveDockWidth(defaultWidth); } }, [uiReady]); @@ -380,22 +352,7 @@ function Main() { })} > {page !== 'Onboarding' && !showLoadingSpinner && } - {renderDock && leftDock && ( -
- {!isDockCollapsed && ( - ctrl.onResize(val)} - max={maxDockWidth} - min={minDockWidth} - value={liveDockSize} - > - - - )} -
- )} + {renderDock && leftDock && }
}
- {renderDock && !leftDock && ( -
- {!isDockCollapsed && ( - ctrl.onResize(val)} - max={maxDockWidth} - min={minDockWidth} - value={liveDockSize} - > - - - )} -
- )} + {renderDock && !leftDock && }
From 861f5721a98161ba5bffb8cef2c6f87533f4ac22 Mon Sep 17 00:00:00 2001 From: Sean Beyer Date: Thu, 22 Feb 2024 14:52:38 -0800 Subject: [PATCH 23/51] Fix rendering livedock --- app/components-react/root/LiveDock.tsx | 39 +++++++++++------------- app/components-react/windows/Main.m.less | 4 +-- app/components-react/windows/Main.tsx | 33 ++++++++++++++++---- 3 files changed, 46 insertions(+), 30 deletions(-) diff --git a/app/components-react/root/LiveDock.tsx b/app/components-react/root/LiveDock.tsx index 541d7e9fa54c..481345078320 100644 --- a/app/components-react/root/LiveDock.tsx +++ b/app/components-react/root/LiveDock.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useMemo, useState } from 'react'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; import * as remote from '@electron/remote'; import cx from 'classnames'; import Animation from 'rc-animate'; @@ -238,35 +238,26 @@ class LiveDockController { showEditStreamInfo() { this.streamingService.actions.showEditStream(); } +} - onResize(offset: number) { - this.setLiveDockWidth(this.customizationService.state.livedockSize + offset); - } - - setLiveDockWidth(width: number) { - this.customizationService.actions.setSettings({ - livedockSize: this.validateWidth(width), - }); - } - - validateWidth(width: number): number { - let constrainedWidth = Math.max(this.store.minDockWidth, width); - constrainedWidth = Math.min(this.store.maxDockWidth, width); - return constrainedWidth; - } +interface ILiveDockProps { + maxDockWidth: number; + minDockWidth: number; + setLiveDockWidth: (offset: number) => void; + onLeft?: boolean; } -export default function LiveDockWithContext(p: { onLeft?: boolean }) { +export default function LiveDockWithContext(p: ILiveDockProps) { const controller = useMemo(() => new LiveDockController(), []); const onLeft = p.onLeft || false; return ( - + ); } -function LiveDock(p: { onLeft: boolean }) { +function LiveDock(p: ILiveDockProps) { const ctrl = useController(LiveDockCtx); const [visibleChat, setVisibleChat] = useState('default'); @@ -308,6 +299,10 @@ function LiveDock(p: { onLeft: boolean }) { ]), ); + const onResize = useCallback((offset: number) => { + p.setLiveDockWidth(liveDockSize + offset); + }, []); + useEffect(() => { if (streamingStatus === EStreamingState.Starting && ctrl.collapsed) { ctrl.setCollapsed(false); @@ -364,9 +359,9 @@ function LiveDock(p: { onLeft: boolean }) { ctrl.onResize(val)} - max={maxDockWidth} - min={minDockWidth} + onInput={(val: number) => onResize(val)} + max={p.maxDockWidth} + min={p.minDockWidth} value={liveDockSize} >
diff --git a/app/components-react/windows/Main.m.less b/app/components-react/windows/Main.m.less index ceeb701d48f3..ca6dd9808353 100644 --- a/app/components-react/windows/Main.m.less +++ b/app/components-react/windows/Main.m.less @@ -23,11 +23,11 @@ } .main-contents--right { - grid-template-columns: [sidebar] auto [page] auto [livedock] 1fr; + grid-template-columns: [sidebar] auto [livedock] auto [page] 1fr; } .main-contents--left { - grid-template-columns: [sidebar] auto [livedock] 1fr [page] auto; + grid-template-columns: [sidebar] auto [page] 1fr [livedock] auto; } .main-contents--onboarding { diff --git a/app/components-react/windows/Main.tsx b/app/components-react/windows/Main.tsx index 3b93b9d40417..3f7daf9e835c 100644 --- a/app/components-react/windows/Main.tsx +++ b/app/components-react/windows/Main.tsx @@ -198,6 +198,18 @@ class MainController { } } + setLiveDockWidth(width: number) { + this.customizationService.actions.setSettings({ + livedockSize: this.validateWidth(width), + }); + } + + validateWidth(width: number): number { + let constrainedWidth = Math.max(this.store.minDockWidth, width); + constrainedWidth = Math.min(this.store.maxDockWidth, width); + return constrainedWidth; + } + updateStyleBlockers(val: boolean) { this.windowsService.actions.updateStyleBlockers('main', val); } @@ -233,8 +245,6 @@ function Main() { leftDock, applicationLoading, page, - isDockCollapsed, - liveDockSize, maxDockWidth, minDockWidth, hideStyleBlockers, @@ -250,8 +260,6 @@ function Main() { hasLiveDock: ctrl.store.hasLiveDock, applicationLoading: ctrl.applicationLoading, page: ctrl.page, - isDockCollapsed: ctrl.isDockCollapsed, - liveDockSize: ctrl.liveDockSize, maxDockWidth: ctrl.store.maxDockWidth, minDockWidth: ctrl.store.minDockWidth, hideStyleBlockers: ctrl.hideStyleBlockers, @@ -352,7 +360,14 @@ function Main() { })} > {page !== 'Onboarding' && !showLoadingSpinner && } - {renderDock && leftDock && } + {renderDock && leftDock && ( + ctrl.setLiveDockWidth(width)} + minDockWidth={minDockWidth} + maxDockWidth={maxDockWidth} + /> + )}
}
- {renderDock && !leftDock && } + {renderDock && !leftDock && ( + ctrl.setLiveDockWidth(width)} + minDockWidth={minDockWidth} + maxDockWidth={maxDockWidth} + /> + )}
From 95b343e646c303c1b85514b1145de497a4dd87c0 Mon Sep 17 00:00:00 2001 From: Sean Beyer Date: Tue, 9 Apr 2024 15:05:58 -0700 Subject: [PATCH 24/51] Use Realm --- app/components-react/windows/Main.tsx | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/app/components-react/windows/Main.tsx b/app/components-react/windows/Main.tsx index 3f7daf9e835c..8c9b42cf40b7 100644 --- a/app/components-react/windows/Main.tsx +++ b/app/components-react/windows/Main.tsx @@ -20,6 +20,7 @@ import { getPlatformService } from 'services/platforms'; import { IModalOptions } from 'services/windows'; import styles from './Main.m.less'; import { StatefulService } from 'services'; +import { useRealmObject } from 'components-react/hooks/realm'; const MainCtx = React.createContext(null); @@ -58,10 +59,6 @@ class MainController { minEditorWidth: 500, }); - get dockWidth() { - return this.customizationService.state.livedockSize; - } - get title() { return this.windowsService.state.main.title; } @@ -237,7 +234,6 @@ function Main() { const { theme, - dockWidth, showLoadingSpinner, errorAlert, hasLiveDock, @@ -252,7 +248,6 @@ function Main() { } = useVuex( () => ({ theme: ctrl.theme(bulkLoadFinished), - dockWidth: ctrl.dockWidth, showLoadingSpinner: ctrl.showLoadingSpinner, errorAlert: ctrl.errorAlert, renderDock: ctrl.renderDock, @@ -268,6 +263,8 @@ function Main() { true, ); + const dockWidth = useRealmObject(Services.CustomizationService.state).livedockSize; + useEffect(() => { const unsubscribe = StatefulService.store.subscribe((_, state) => { if (state.bulkLoadFinished) setBulkLoadFinished(true); From 82cca15e74b28b62c363c1cdf440eaddcea29ef5 Mon Sep 17 00:00:00 2001 From: gettinToasty Date: Wed, 5 Jun 2024 15:05:50 -0700 Subject: [PATCH 25/51] Fix compilation error --- app/components-react/shared/TitleBar.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/app/components-react/shared/TitleBar.tsx b/app/components-react/shared/TitleBar.tsx index e1941915ef4d..55bf171b73c6 100644 --- a/app/components-react/shared/TitleBar.tsx +++ b/app/components-react/shared/TitleBar.tsx @@ -18,6 +18,12 @@ export default function TitleBar(props: { windowId: string; className?: string } const isMaximizable = remote.getCurrentWindow().isMaximizable() !== false; const isMac = byOS({ [OS.Windows]: false, [OS.Mac]: true }); const theme = useRealmObject(CustomizationService.state).theme; + const { title } = useVuex( + () => ({ + title: WindowsService.state[props.windowId]?.title, + }), + false, + ); const isDev = useMemo(() => Utils.isDevMode(), []); @@ -67,7 +73,7 @@ export default function TitleBar(props: { windowId: string; className?: string } )} {primeTheme && !isMac && }
- {v.title} + {title}
{!isMac && (
From 20c9e84894d19b28ea7d877d8792a5c1c40078b9 Mon Sep 17 00:00:00 2001 From: gettinToasty Date: Wed, 5 Jun 2024 15:13:28 -0700 Subject: [PATCH 26/51] Fix Livedock breaking render --- app/components-react/root/LiveDock.tsx | 224 +++++++++++++------------ 1 file changed, 113 insertions(+), 111 deletions(-) diff --git a/app/components-react/root/LiveDock.tsx b/app/components-react/root/LiveDock.tsx index 5071f2bb07b2..976728746028 100644 --- a/app/components-react/root/LiveDock.tsx +++ b/app/components-react/root/LiveDock.tsx @@ -386,64 +386,112 @@ function LiveDock(p: ILiveDockProps) { min={p.minDockWidth} value={liveDockSize} > -
-
-
-
- {liveText} - {elapsedStreamTime} -
-
- ctrl.toggleViewerCount()} - /> - {viewerCount} - {Number(viewerCount) >= 0 && {$t('viewers')}} + <> +
+
+
+
+ {liveText} + {elapsedStreamTime} +
+
+ ctrl.toggleViewerCount()} + /> + {viewerCount} + {Number(viewerCount) >= 0 && {$t('viewers')}} +
-
-
-
- {ctrl.canEditChannelInfo && ( - - ctrl.showEditStreamInfo()} className="icon-edit" /> - - )} - {isPlatform(['youtube', 'facebook', 'trovo', 'tiktok']) && isStreaming && ( - - ctrl.openPlatformStream()} className="icon-studio" /> - - )} - {isPlatform(['youtube', 'facebook', 'tiktok']) && isStreaming && ( - - ctrl.openPlatformDash()} className="icon-settings" /> - - )} +
+
+ {ctrl.canEditChannelInfo && ( + + ctrl.showEditStreamInfo()} className="icon-edit" /> + + )} + {isPlatform(['youtube', 'facebook', 'trovo', 'tiktok']) && isStreaming && ( + + ctrl.openPlatformStream()} className="icon-studio" /> + + )} + {isPlatform(['youtube', 'facebook', 'tiktok']) && isStreaming && ( + + ctrl.openPlatformDash()} className="icon-settings" /> + + )} +
+
+ {(isPlatform(['twitch', 'trovo', 'facebook']) || + (isPlatform(['youtube', 'twitter']) && isStreaming) || + (isPlatform(['tiktok']) && isRestreaming)) && ( + ctrl.refreshChat()}>{$t('Refresh Chat')} + )} +
+ {!hideStyleBlockers && + (isPlatform(['twitch', 'trovo']) || + (isStreaming && isPlatform(['youtube', 'facebook', 'twitter', 'tiktok']))) && ( +
+ {hasChatTabs && ( +
+ setChat(ev.key)} + mode="horizontal" + > + {chatTabs.map(tab => ( + {tab.name} + ))} + + {isPopOutAllowed && ( + + ctrl.popOut()} + /> + + )} +
+ )} + {!applicationLoading && !collapsed && ( + + )} + {isPlatform(['youtube', 'facebook']) && isStreaming && ( + + ctrl.openPlatformDash()} className="icon-settings" /> + + )} +
+ )}
{(isPlatform(['twitch', 'trovo', 'facebook']) || - (isPlatform(['youtube', 'twitter']) && isStreaming) || - (isPlatform(['tiktok']) && isRestreaming)) && ( + (isPlatform(['youtube', 'twitter']) && isStreaming)) && ( ctrl.refreshChat()}>{$t('Refresh Chat')} )}
@@ -473,71 +521,25 @@ function LiveDock(p: ILiveDockProps) { )}
)} - {!applicationLoading && !collapsed && ( - )} - {isPlatform(['youtube', 'facebook']) && isStreaming && ( - - ctrl.openPlatformDash()} className="icon-settings" /> - - )}
)} -
- {(isPlatform(['twitch', 'trovo', 'facebook']) || - (isPlatform(['youtube', 'twitter']) && isStreaming)) && ( - ctrl.refreshChat()}>{$t('Refresh Chat')} - )} -
-
- {!hideStyleBlockers && - (isPlatform(['twitch', 'trovo']) || - (isStreaming && isPlatform(['youtube', 'facebook', 'twitter', 'tiktok']))) && ( -
- {hasChatTabs && ( -
- setChat(ev.key)} - mode="horizontal" - > - {chatTabs.map(tab => ( - {tab.name} - ))} - - {isPopOutAllowed && ( - - ctrl.popOut()} - /> - - )} -
- )} - {!applicationLoading && !collapsed && chat} - {!['default', 'restream'].includes(visibleChat) && ( - - )} + {(!ctrl.platform || + (isPlatform(['youtube', 'facebook', 'twitter', 'tiktok']) && !isStreaming)) && ( +
+ + {!hideStyleBlockers && {$t('Your chat is currently offline')}}
)} - {(!ctrl.platform || - (isPlatform(['youtube', 'facebook', 'twitter', 'tiktok']) && !isStreaming)) && ( -
- - {!hideStyleBlockers && {$t('Your chat is currently offline')}} -
- )} + )} From 1a95e107b4294aa467b0e41b50a5bc9cd67f39f6 Mon Sep 17 00:00:00 2001 From: gettinToasty Date: Wed, 5 Jun 2024 16:06:43 -0700 Subject: [PATCH 27/51] Use realm props binding --- app/components/windows/Main.tsx | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/app/components/windows/Main.tsx b/app/components/windows/Main.tsx index f599e73a8306..ceacaad164ee 100644 --- a/app/components/windows/Main.tsx +++ b/app/components/windows/Main.tsx @@ -23,29 +23,33 @@ export default class MainWindow extends TsxComponent { return this.$store.state.bulkLoadFinished && this.$store.state.i18nReady; } - get theme() { - if (this.$store.state.bulkLoadFinished) { - return this.customizationService.currentTheme; - } + theme = 'night-theme'; - return loadedTheme() || 'night-theme'; - } + unbind: () => void; mounted() { + this.unbind = this.customizationService.state.bindProps(this, { + theme: 'theme', + }); + antdThemes[this.theme].use(); } + destroyed() { + this.unbind(); + } + render() { return (
- {this.uiReady &&
} + {/* {this.uiReady &&
} - {!this.uiReady && ( -
+ {!this.uiReady && ( */} +
- )} - + {/* )} + */}
); } From 9603e10cb81b9fe143e52ccc4f97bd1975715c79 Mon Sep 17 00:00:00 2001 From: Adrian Perez Date: Mon, 22 Jul 2024 13:28:39 -0700 Subject: [PATCH 28/51] fix: pass theme to `Loader` --- app/components/windows/Main.tsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/app/components/windows/Main.tsx b/app/components/windows/Main.tsx index ceacaad164ee..81ed90fcdf35 100644 --- a/app/components/windows/Main.tsx +++ b/app/components/windows/Main.tsx @@ -45,10 +45,12 @@ export default class MainWindow extends TsxComponent { {/* {this.uiReady &&
} {!this.uiReady && ( */} -
- -
- {/* )} +
+ +
+ {/* )}
*/}
); From 8fb450a0e25842fdbcda9a602b76ff1f02607c1d Mon Sep 17 00:00:00 2001 From: Adrian Perez Date: Mon, 22 Jul 2024 13:29:56 -0700 Subject: [PATCH 29/51] chore: uncoment main rendering --- app/components/windows/Main.tsx | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/app/components/windows/Main.tsx b/app/components/windows/Main.tsx index 81ed90fcdf35..d6e562e112d7 100644 --- a/app/components/windows/Main.tsx +++ b/app/components/windows/Main.tsx @@ -31,7 +31,7 @@ export default class MainWindow extends TsxComponent { this.unbind = this.customizationService.state.bindProps(this, { theme: 'theme', }); - + antdThemes[this.theme].use(); } @@ -42,16 +42,18 @@ export default class MainWindow extends TsxComponent { render() { return (
- {/* {this.uiReady &&
} + {this.uiReady &&
} - {!this.uiReady && ( */} -
- -
- {/* )} -
*/} + {!this.uiReady && ( +
+ +
+ )} +
); } From cd4baa7af5ae40bac8f12c9f607d9e7770f4f743 Mon Sep 17 00:00:00 2001 From: Adrian Perez Date: Wed, 24 Jul 2024 15:03:27 -0700 Subject: [PATCH 30/51] fix(main): restore wrappers, should fix resizing behavior * Restores main wrappers from the original Vue component, some of these are needed to preserve layout, while also in some cases separating the styles from the rendered component, e.g. footer, sidebar, where styles would be merged weirdly. * `overflow: auto` is apparently required and that's what it seems to fix the cutoff at the bottom, combined with the above. * Cleanup needed. --- app/components-react/windows/Main.m.less | 7 +++-- app/components-react/windows/Main.tsx | 33 ++++++++++++++++++------ app/components/windows/Main.tsx | 2 ++ 3 files changed, 32 insertions(+), 10 deletions(-) diff --git a/app/components-react/windows/Main.m.less b/app/components-react/windows/Main.m.less index ca6dd9808353..be7010b4b2e0 100644 --- a/app/components-react/windows/Main.m.less +++ b/app/components-react/windows/Main.m.less @@ -20,14 +20,17 @@ flex-grow: 1; height: 100%; position: relative; + overflow: auto; } .main-contents--right { - grid-template-columns: [sidebar] auto [livedock] auto [page] 1fr; + //grid-template-columns: [sidebar] auto [livedock] auto [page] 1fr; + grid-template-columns: auto auto 1fr; } .main-contents--left { - grid-template-columns: [sidebar] auto [page] 1fr [livedock] auto; + //grid-template-columns: [sidebar] auto [page] 1fr [livedock] auto; + grid-template-columns: auto 1fr auto; } .main-contents--onboarding { diff --git a/app/components-react/windows/Main.tsx b/app/components-react/windows/Main.tsx index 8c9b42cf40b7..6c59282beb2c 100644 --- a/app/components-react/windows/Main.tsx +++ b/app/components-react/windows/Main.tsx @@ -343,7 +343,7 @@ function Main() { return (
ctrl.onDropHandler(ev)} @@ -356,7 +356,18 @@ function Main() { [styles.mainContentsOnboarding]: page === 'Onboarding', })} > - {page !== 'Onboarding' && !showLoadingSpinner && } + {page !== 'Onboarding' && !showLoadingSpinner && ( +
+ +
+ )} {renderDock && leftDock && ( {!showLoadingSpinner && ( - ctrl.handleEditorWidth(width)} - /> +
+ ctrl.handleEditorWidth(width)} + /> +
+ )} + {!applicationLoading && page !== 'Onboarding' && ( +
+ +
)} - {!applicationLoading && page !== 'Onboarding' && }
{renderDock && !leftDock && ( diff --git a/app/components/windows/Main.tsx b/app/components/windows/Main.tsx index d6e562e112d7..7a42323d5e03 100644 --- a/app/components/windows/Main.tsx +++ b/app/components/windows/Main.tsx @@ -40,6 +40,8 @@ export default class MainWindow extends TsxComponent { } render() { + return this.uiReady &&
; + return (
{this.uiReady &&
} From 050f4ae3b104b7dcef0c20247d65a4b49586e509 Mon Sep 17 00:00:00 2001 From: Sean Beyer Date: Wed, 11 Sep 2024 14:18:58 -0700 Subject: [PATCH 31/51] Re-add ChatTabs --- app/components-react/root/LiveDock.tsx | 22 +--------------------- 1 file changed, 1 insertion(+), 21 deletions(-) diff --git a/app/components-react/root/LiveDock.tsx b/app/components-react/root/LiveDock.tsx index e93463ac897d..bc66ca0e60b2 100644 --- a/app/components-react/root/LiveDock.tsx +++ b/app/components-react/root/LiveDock.tsx @@ -517,27 +517,7 @@ function LiveDock(p: ILiveDockProps) { (isPlatform(['twitch', 'trovo']) || (isStreaming && isPlatform(['youtube', 'facebook', 'twitter', 'tiktok']))) && (
- {hasChatTabs && ( -
- setChat(ev.key)} - mode="horizontal" - > - {chatTabs.map(tab => ( - {tab.name} - ))} - - {isPopOutAllowed && ( - - ctrl.popOut()} - /> - - )} -
- )} + {hasChatTabs && } {!applicationLoading && !collapsed && chat} {!['default', 'restream'].includes(visibleChat) && ( Date: Wed, 25 Sep 2024 14:22:37 -0700 Subject: [PATCH 32/51] Fix index typing for customization service --- app/components-react/windows/Main.m.less | 2 -- app/components-react/windows/Main.tsx | 9 +++++---- .../sharedComponentsLibrary/SharedComponentsLibrary.tsx | 3 ++- app/components/windows/Main.tsx | 5 ++--- app/services/customization.ts | 8 +++++--- app/services/game-overlay/index.ts | 2 +- 6 files changed, 15 insertions(+), 14 deletions(-) diff --git a/app/components-react/windows/Main.m.less b/app/components-react/windows/Main.m.less index be7010b4b2e0..411faf226a72 100644 --- a/app/components-react/windows/Main.m.less +++ b/app/components-react/windows/Main.m.less @@ -24,12 +24,10 @@ } .main-contents--right { - //grid-template-columns: [sidebar] auto [livedock] auto [page] 1fr; grid-template-columns: auto auto 1fr; } .main-contents--left { - //grid-template-columns: [sidebar] auto [page] 1fr [livedock] auto; grid-template-columns: auto 1fr auto; } diff --git a/app/components-react/windows/Main.tsx b/app/components-react/windows/Main.tsx index 6c59282beb2c..a447580c0eb3 100644 --- a/app/components-react/windows/Main.tsx +++ b/app/components-react/windows/Main.tsx @@ -18,13 +18,14 @@ import Loader from 'components-react/pages/Loader'; import antdThemes from 'styles/antd/index'; import { getPlatformService } from 'services/platforms'; import { IModalOptions } from 'services/windows'; +import { TApplicationTheme } from 'services/customization'; import styles from './Main.m.less'; import { StatefulService } from 'services'; import { useRealmObject } from 'components-react/hooks/realm'; const MainCtx = React.createContext(null); -const loadedTheme = () => { +const loadedTheme = (): TApplicationTheme | undefined => { const customizationState = localStorage.getItem('PersistentStatefulService-CustomizationService'); if (customizationState) { return JSON.parse(customizationState)?.theme; @@ -75,7 +76,7 @@ class MainController { return this.windowsService.state.main.hideStyleBlockers; } - theme(bulkLoadFinished: boolean) { + theme(bulkLoadFinished: boolean): TApplicationTheme { if (bulkLoadFinished) { return this.customizationService.currentTheme; } @@ -310,7 +311,7 @@ function Main() { }; }, [hideStyleBlockers]); - const oldTheme = useRef(null); + const oldTheme = useRef(null); useEffect(() => { if (!theme) return; if (oldTheme.current && oldTheme.current !== theme) antdThemes[oldTheme.current].unuse(); @@ -339,7 +340,7 @@ function Main() { className: string; params: any; onTotalWidth: (width: number) => void; - }> = appPages[page]; + }> = (appPages as Dictionary)[page]; return (
{ const customizationState = localStorage.getItem('PersistentStatefulService-CustomizationService'); @@ -23,7 +24,7 @@ export default class MainWindow extends TsxComponent { return this.$store.state.bulkLoadFinished && this.$store.state.i18nReady; } - theme = 'night-theme'; + theme: TApplicationTheme = 'night-theme'; unbind: () => void; @@ -40,8 +41,6 @@ export default class MainWindow extends TsxComponent { } render() { - return this.uiReady &&
; - return (
{this.uiReady &&
} diff --git a/app/services/customization.ts b/app/services/customization.ts index 436f052d5c43..41cc642e1831 100644 --- a/app/services/customization.ts +++ b/app/services/customization.ts @@ -10,6 +10,8 @@ import * as obs from '../../obs-api'; import { RealmObject } from './realm'; import { ObjectSchema } from 'realm'; +export type TApplicationTheme = 'night-theme' | 'day-theme' | 'prime-dark' | 'prime-light'; + // Maps to --background const THEME_BACKGROUNDS = { 'night-theme': { r: 23, g: 36, b: 45 }, @@ -43,7 +45,7 @@ export interface IPinnedStatistics { export interface ICustomizationServiceState { nightMode?: string; - theme: string; + theme: TApplicationTheme; updateStreamInfoOnLive: boolean; livePreviewEnabled: boolean; leftDock: boolean; @@ -91,7 +93,7 @@ class PinnedStatistics extends RealmObject { PinnedStatistics.register({ persist: true }); export class CustomizationState extends RealmObject { - theme: string; + theme: TApplicationTheme; updateStreamInfoOnLive: boolean; livePreviewEnabled: boolean; leftDock: boolean; @@ -220,7 +222,7 @@ export class CustomizationService extends Service { return this.state.theme; } - setTheme(theme: string) { + setTheme(theme: TApplicationTheme) { obs.NodeObs.OBS_content_setDayTheme(['day-theme', 'prime-light'].includes(theme)); return this.setSettings({ theme }); } diff --git a/app/services/game-overlay/index.ts b/app/services/game-overlay/index.ts index 71c1d360905b..f54abc471967 100644 --- a/app/services/game-overlay/index.ts +++ b/app/services/game-overlay/index.ts @@ -176,7 +176,7 @@ export class GameOverlayService extends PersistentStatefulService Date: Wed, 25 Sep 2024 15:14:18 -0700 Subject: [PATCH 33/51] Fix typing in Studio.tsx --- .../editor/elements/LegacyEvents.tsx | 5 ++- app/components-react/editor/elements/index.ts | 6 ++-- app/components-react/pages/Studio.tsx | 7 ++-- app/components-react/root/ResizeBar.tsx | 4 +-- app/components-react/windows/Main.m.less | 2 +- app/services/layout/index.ts | 14 +++++--- app/services/layout/layout-data.ts | 36 ++++++++++--------- test/regular/themes.ts | 2 +- 8 files changed, 43 insertions(+), 33 deletions(-) diff --git a/app/components-react/editor/elements/LegacyEvents.tsx b/app/components-react/editor/elements/LegacyEvents.tsx index 1b39d2b82f4b..3ed4b50da6df 100644 --- a/app/components-react/editor/elements/LegacyEvents.tsx +++ b/app/components-react/editor/elements/LegacyEvents.tsx @@ -9,15 +9,14 @@ import styles from './RecentEvents.m.less'; const mins = { x: 360, y: 150 }; -export function LegacyEvents(p: { onPopout: () => void }) { +export function LegacyEvents() { const { UserService, RecentEventsService, MagicLinkService } = Services; const containerRef = useRef(null); const magicLinkDisabled = useRef(false); function popoutRecentEvents() { - p.onPopout(); - return RecentEventsService.openRecentEventsWindow(); + return RecentEventsService.actions.openRecentEventsWindow(); } function handleBrowserViewReady(view: Electron.BrowserView) { diff --git a/app/components-react/editor/elements/index.ts b/app/components-react/editor/elements/index.ts index 42e761910452..8fc50973b47d 100644 --- a/app/components-react/editor/elements/index.ts +++ b/app/components-react/editor/elements/index.ts @@ -1,7 +1,7 @@ -export { MiniFeed } from './Minifeed'; +export { MiniFeed as Minifeed } from './Minifeed'; export { LegacyEvents } from './LegacyEvents'; -export { SceneSelectorElement } from './SceneSelector'; -export { SourceSelectorElement } from './SourceSelector'; +export { SceneSelectorElement as Scenes } from './SceneSelector'; +export { SourceSelectorElement as Sources } from './SourceSelector'; export { Mixer } from './Mixer'; export { RecordingPreview } from './RecordingPreview'; export { StreamPreview } from './StreamPreview'; diff --git a/app/components-react/pages/Studio.tsx b/app/components-react/pages/Studio.tsx index 2d32282793d7..41c61de534fd 100644 --- a/app/components-react/pages/Studio.tsx +++ b/app/components-react/pages/Studio.tsx @@ -1,10 +1,10 @@ import React, { ReactNode, useMemo } from 'react'; -import cx from 'classnames'; import { ELayoutElement, IVec2Array } from 'services/layout'; import * as elements from 'components-react/editor/elements'; import * as layouts from 'components-react/editor/layouts'; import { Services } from 'components-react/service-provider'; import { useVuex } from 'components-react/hooks'; +import { TLayoutElement } from 'services/layout/layout-data'; export default function Studio(p: { onTotalWidth: (width: Number) => void; className?: string }) { const { LayoutService } = Services; @@ -29,7 +29,7 @@ export default function Studio(p: { onTotalWidth: (width: Number) => void; class const children: Dictionary = {}; const childrenMins: Dictionary = {}; elementsToRender.forEach((el: ELayoutElement) => { - const componentName = LayoutService.views.elementComponent(el); + const componentName: TLayoutElement = LayoutService.views.elementComponent(el); const Component = elements[componentName]; const slot = slottedElements[el]?.slot; if (slot && Component) { @@ -42,7 +42,8 @@ export default function Studio(p: { onTotalWidth: (width: Number) => void; class return ( totalWidthHandler(slots, isColumns)} > diff --git a/app/components-react/root/ResizeBar.tsx b/app/components-react/root/ResizeBar.tsx index 508b3cabfbfb..4c31711ba067 100644 --- a/app/components-react/root/ResizeBar.tsx +++ b/app/components-react/root/ResizeBar.tsx @@ -87,8 +87,8 @@ export default function ResizeBar(p: React.PropsWithChildren) { {...resizableProps} handle={
diff --git a/app/components-react/windows/Main.m.less b/app/components-react/windows/Main.m.less index 411faf226a72..778efef10aba 100644 --- a/app/components-react/windows/Main.m.less +++ b/app/components-react/windows/Main.m.less @@ -20,7 +20,7 @@ flex-grow: 1; height: 100%; position: relative; - overflow: auto; + // overflow: auto; } .main-contents--right { diff --git a/app/services/layout/index.ts b/app/services/layout/index.ts index 22b5884005cc..0a9ff4ea8d51 100644 --- a/app/services/layout/index.ts +++ b/app/services/layout/index.ts @@ -6,7 +6,14 @@ import { mutation } from 'services/core/stateful-service'; import { CustomizationService } from 'services/customization'; import { $t } from 'services/i18n'; import uuid from 'uuid/v4'; -import { LAYOUT_DATA, ELEMENT_DATA, ELayout, ELayoutElement } from './layout-data'; +import { + LAYOUT_DATA, + ELEMENT_DATA, + ELayout, + ELayoutElement, + TLayout, + TLayoutElement, +} from './layout-data'; import { UsageStatisticsService } from 'services/usage-statistics'; import { menuTitles } from 'services/side-nav/menu-data'; @@ -35,13 +42,13 @@ class LayoutViews extends ViewHandler { return this.state.tabs[this.state.currentTab]; } - get component() { + get component(): TLayout { return LAYOUT_DATA[this.currentTab.currentLayout].component; } get elementsToRender() { return Object.keys(this.currentTab.slottedElements).filter( - key => this.currentTab.slottedElements[key].slot, + (key: TLayoutElement) => this.currentTab.slottedElements[key].slot, ); } @@ -62,7 +69,6 @@ class LayoutViews extends ViewHandler { } elementComponent(element: ELayoutElement) { - if (!element) return ''; return ELEMENT_DATA()[element].component; } diff --git a/app/services/layout/layout-data.ts b/app/services/layout/layout-data.ts index 7e26606bb69d..87bf70d5c069 100644 --- a/app/services/layout/layout-data.ts +++ b/app/services/layout/layout-data.ts @@ -11,23 +11,13 @@ export enum ELayout { Pyramid = 'Pyramid', } -export enum ELayoutElement { - Minifeed = 'Minifeed', - LegacyEvents = 'LegacyEvents', - Display = 'Display', - Mixer = 'Mixer', - Scenes = 'Scenes', - Sources = 'Sources', - StreamPreview = 'StreamPreview', - RecordingPreview = 'RecordingPreview', - Browser = 'Browser', -} +export type TLayout = `${ELayout}`; type ILayoutData = { [Layout in ELayout]: { resizeDefaults: { bar1: number; bar2: number }; className: string; - component: string; + component: TLayout; }; }; @@ -74,10 +64,24 @@ export const LAYOUT_DATA: ILayoutData = { }, }; +export enum ELayoutElement { + Minifeed = 'Minifeed', + LegacyEvents = 'LegacyEvents', + Display = 'Display', + Mixer = 'Mixer', + Scenes = 'Scenes', + Sources = 'Sources', + StreamPreview = 'StreamPreview', + RecordingPreview = 'RecordingPreview', + Browser = 'Browser', +} + +export type TLayoutElement = `${ELayoutElement}`; + type IElementData = { [Element in ELayoutElement]: { title: string; - component: string; + component: TLayoutElement; }; }; @@ -88,7 +92,7 @@ export const ELEMENT_DATA = (): IElementData => ({ }, [ELayoutElement.Minifeed]: { title: $t('Mini Feed'), - component: 'MiniFeed', + component: 'Minifeed', }, [ELayoutElement.Mixer]: { title: $t('Audio Mixer'), @@ -96,11 +100,11 @@ export const ELEMENT_DATA = (): IElementData => ({ }, [ELayoutElement.Scenes]: { title: $t('Scene Selector'), - component: 'SceneSelectorElement', + component: 'Scenes', }, [ELayoutElement.Sources]: { title: $t('Source Selector'), - component: 'SourceSelectorElement', + component: 'Sources', }, [ELayoutElement.LegacyEvents]: { title: $t('Legacy Events'), diff --git a/test/regular/themes.ts b/test/regular/themes.ts index 31cc15796585..6b7f6e98c203 100644 --- a/test/regular/themes.ts +++ b/test/regular/themes.ts @@ -43,7 +43,7 @@ test.skip('Installing a theme', async (t: TExecutionContext) => { // wait for installation complete await focusMain(); - await waitForDisplayed('.editor-page', { timeout: 60000 }); + await waitForDisplayed('[data-name="editor-page"]', { timeout: 60000 }); // Should've loaded the overlay as a new scene collection await waitForDisplayed(`span=${OVERLAY_NAME}`); From 42bee561ba3c9ebd832fdd5ab2bc73b2e8243c4c Mon Sep 17 00:00:00 2001 From: Sean Beyer Date: Thu, 16 Jan 2025 11:15:30 -0800 Subject: [PATCH 34/51] Fix resizing problem --- app/components-react/pages/RecordingHistory.tsx | 1 - app/components-react/windows/Main.tsx | 8 +++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/app/components-react/pages/RecordingHistory.tsx b/app/components-react/pages/RecordingHistory.tsx index e19d0d06df81..7c8c1d4f8f39 100644 --- a/app/components-react/pages/RecordingHistory.tsx +++ b/app/components-react/pages/RecordingHistory.tsx @@ -1,7 +1,6 @@ import React, { useEffect, useMemo } from 'react'; import cx from 'classnames'; import * as remote from '@electron/remote'; -import cx from 'classnames'; import { Tooltip } from 'antd'; import { $t } from 'services/i18n'; import { ModalLayout } from 'components-react/shared/ModalLayout'; diff --git a/app/components-react/windows/Main.tsx b/app/components-react/windows/Main.tsx index a447580c0eb3..3d35145c58f7 100644 --- a/app/components-react/windows/Main.tsx +++ b/app/components-react/windows/Main.tsx @@ -337,7 +337,7 @@ function Main() { if (!uiReady) return
; const Component: React.FunctionComponent<{ - className: string; + className?: string; params: any; onTotalWidth: (width: number) => void; }> = (appPages as Dictionary)[page]; @@ -383,9 +383,11 @@ function Main() { ref={mainMiddleEl} > {!showLoadingSpinner && ( -
+
ctrl.handleEditorWidth(width)} /> From fc697f7623a62c0813d5d8d0a0747e3dcadd415f Mon Sep 17 00:00:00 2001 From: Sean Beyer Date: Tue, 4 Feb 2025 13:59:57 -0800 Subject: [PATCH 35/51] Fix css on resize bar --- app/components-react/root/ResizeBar.m.less | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/components-react/root/ResizeBar.m.less b/app/components-react/root/ResizeBar.m.less index 301eefeaf6d3..666bcf2342d1 100644 --- a/app/components-react/root/ResizeBar.m.less +++ b/app/components-react/root/ResizeBar.m.less @@ -23,7 +23,8 @@ } &.right { width: 10px; - left: -4px; + left: 0px; + top: 0px; cursor: col-resize; .resize-line { height: 25px; @@ -32,7 +33,8 @@ } &.left { width: 10px; - right: -4px; + right: 0px; + top: 0px; cursor: col-resize; .resize-line { height: 25px; From f3c0699ba820ad0ae62dac48734b22c7cd63587b Mon Sep 17 00:00:00 2001 From: Sean Beyer Date: Fri, 14 Feb 2025 15:09:30 -0800 Subject: [PATCH 36/51] Fix footer clipping at small sizes --- app/components-react/windows/Main.m.less | 11 +++++++++++ app/components-react/windows/Main.tsx | 11 ++--------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/app/components-react/windows/Main.m.less b/app/components-react/windows/Main.m.less index 778efef10aba..5f54e2eff5e5 100644 --- a/app/components-react/windows/Main.m.less +++ b/app/components-react/windows/Main.m.less @@ -55,6 +55,17 @@ } } +.side-nav-container { + height: 100%; + display: flex; + flex-grow: 1; + + // Scrollable does some weird things to the sidebar here + :global(.os-content-glue) { + height: 100% !important; + } +} + .main-page-container { /* Page always takes up remaining space */ flex-grow: 1; diff --git a/app/components-react/windows/Main.tsx b/app/components-react/windows/Main.tsx index 3d35145c58f7..9d22e8ad4640 100644 --- a/app/components-react/windows/Main.tsx +++ b/app/components-react/windows/Main.tsx @@ -358,14 +358,7 @@ function Main() { })} > {page !== 'Onboarding' && !showLoadingSpinner && ( -
+
)} @@ -385,7 +378,7 @@ function Main() { {!showLoadingSpinner && (
Date: Fri, 14 Feb 2025 15:39:58 -0800 Subject: [PATCH 37/51] Fix livedock collapse --- app/components-react/root/LiveDock.m.less | 5 +++++ app/components-react/root/LiveDock.tsx | 4 ++-- app/components-react/windows/Main.m.less | 4 ++++ app/components-react/windows/Main.tsx | 5 +---- 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/app/components-react/root/LiveDock.m.less b/app/components-react/root/LiveDock.m.less index eeda0415cca0..5e55be3dd930 100644 --- a/app/components-react/root/LiveDock.m.less +++ b/app/components-react/root/LiveDock.m.less @@ -48,6 +48,11 @@ } } +.live-dock-resize-bar { + // So we don't block the collapse chevron + top: 20px !important; +} + .live-dock-chevron { width: 16px; height: 20px; diff --git a/app/components-react/root/LiveDock.tsx b/app/components-react/root/LiveDock.tsx index fd30af60f127..8a380ab11f47 100644 --- a/app/components-react/root/LiveDock.tsx +++ b/app/components-react/root/LiveDock.tsx @@ -394,8 +394,8 @@ function LiveDock(p: ILiveDockProps) { {!collapsed && ( onResize(val)} max={p.maxDockWidth} min={p.minDockWidth} diff --git a/app/components-react/windows/Main.m.less b/app/components-react/windows/Main.m.less index 5f54e2eff5e5..99f089f7bff5 100644 --- a/app/components-react/windows/Main.m.less +++ b/app/components-react/windows/Main.m.less @@ -44,6 +44,8 @@ grid-template-rows: minmax(0, 1fr) 48px; position: relative; height: 100%; + width: auto; + min-width: 0; } .titlebar--error { @@ -72,6 +74,8 @@ display: flex; position: relative; grid-row: 1 / span 1; + min-width: 0; + min-height: 0; } .main-loading { diff --git a/app/components-react/windows/Main.tsx b/app/components-react/windows/Main.tsx index 9d22e8ad4640..5138ce0c118a 100644 --- a/app/components-react/windows/Main.tsx +++ b/app/components-react/windows/Main.tsx @@ -376,10 +376,7 @@ function Main() { ref={mainMiddleEl} > {!showLoadingSpinner && ( -
+
ctrl.handleEditorWidth(width)} From 4a296a79de0fb0b60981054fa14c93ca0807b30e Mon Sep 17 00:00:00 2001 From: Sean Beyer Date: Mon, 3 Mar 2025 15:11:40 -0800 Subject: [PATCH 38/51] Refactor resizebar into main window --- app/components-react/root/LiveDock.m.less | 13 +- app/components-react/root/LiveDock.tsx | 315 +++++++++------------ app/components-react/root/ResizeBar.m.less | 2 + app/components-react/root/ResizeBar.tsx | 2 +- app/components-react/windows/Main.m.less | 42 ++- app/components-react/windows/Main.tsx | 66 +++-- 6 files changed, 193 insertions(+), 247 deletions(-) diff --git a/app/components-react/root/LiveDock.m.less b/app/components-react/root/LiveDock.m.less index 5e55be3dd930..7cc4f6d4faac 100644 --- a/app/components-react/root/LiveDock.m.less +++ b/app/components-react/root/LiveDock.m.less @@ -1,22 +1,17 @@ @import '../../styles/index'; .live-dock { - padding-left: 16px; position: relative; z-index: 1000; - width: 28%; + width: 100%; box-sizing: border-box; height: 100%; - border-left: 1px solid var(--border); &.can-animate { transition: width 300ms; } &.live-dock--left { - padding-left: 0; - padding-right: 16px; - border-right: 1px solid var(--border); background-color: var(--section); } @@ -30,11 +25,6 @@ right: 0; left: auto; } - - .live-dock-expanded-contents { - border-right: 1px solid var(--border); - border-left: none; - } } .live-dock.collapsed { @@ -97,7 +87,6 @@ flex-direction: column; height: 100%; padding: 16px; - border-left: 1px solid var(--border); } .live-dock-info { diff --git a/app/components-react/root/LiveDock.tsx b/app/components-react/root/LiveDock.tsx index 8a380ab11f47..18ce13ebae24 100644 --- a/app/components-react/root/LiveDock.tsx +++ b/app/components-react/root/LiveDock.tsx @@ -1,8 +1,7 @@ -import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import React, { useEffect, useMemo, useState } from 'react'; import * as remote from '@electron/remote'; import cx from 'classnames'; -import Animation from 'rc-animate'; -import { Button, Menu } from 'antd'; +import { Menu } from 'antd'; import pick from 'lodash/pick'; import { initStore, useController } from 'components-react/hooks/zustand'; import { EStreamingState } from 'services/streaming'; @@ -14,12 +13,9 @@ import Chat from './Chat'; import styles from './LiveDock.m.less'; import Tooltip from 'components-react/shared/Tooltip'; import PlatformAppPageView from 'components-react/shared/PlatformAppPageView'; -import ResizeBar from 'components-react/root/ResizeBar'; import { useVuex } from 'components-react/hooks'; import { useRealmObject } from 'components-react/hooks/realm'; import { $i } from 'services/utils'; -import { TikTokChatInfo } from './TiktokChatInfo'; -import { ShareStreamLink } from './ShareStreamLink'; const LiveDockCtx = React.createContext(null); @@ -245,24 +241,16 @@ class LiveDockController { } } -interface ILiveDockProps { - maxDockWidth: number; - minDockWidth: number; - setLiveDockWidth: (offset: number) => void; - onLeft?: boolean; -} - -export default function LiveDockWithContext(p: ILiveDockProps) { +export default function LiveDockWithContext() { const controller = useMemo(() => new LiveDockController(), []); - const onLeft = p.onLeft || false; return ( - + ); } -function LiveDock(p: ILiveDockProps) { +function LiveDock() { const ctrl = useController(LiveDockCtx); const [visibleChat, setVisibleChat] = useState('default'); @@ -305,10 +293,6 @@ function LiveDock(p: ILiveDockProps) { const hideViewerCount = useRealmObject(Services.CustomizationService.state).hideViewerCount; const viewerCount = hideViewerCount ? $t('Viewers Hidden') : currentViewers; - const onResize = useCallback((offset: number) => { - p.setLiveDockWidth(liveDockSize + offset); - }, []); - useEffect(() => { if (streamingStatus === EStreamingState.Starting && collapsed) { ctrl.setCollapsed(false); @@ -375,172 +359,139 @@ function LiveDock(p: ILiveDockProps) { }, [Services.UserService.state.auth!.primaryPlatform, visibleChat]); return ( -
-
- -
- - {!collapsed && ( - onResize(val)} - max={p.maxDockWidth} - min={p.minDockWidth} - value={liveDockSize} - > - <> -
-
-
-
- {liveText} - {elapsedStreamTime} -
-
- ctrl.toggleViewerCount()} - /> - {viewerCount} - {Number(viewerCount) >= 0 && {$t('viewers')}} -
-
- -
-
- {ctrl.canEditChannelInfo && ( - - ctrl.showEditStreamInfo()} className="icon-edit" /> - - )} - {isPlatform(['youtube', 'facebook', 'trovo', 'tiktok', 'kick']) && isStreaming && ( - - ctrl.openPlatformStream()} className="icon-studio" /> - - )} - {isPlatform(['youtube', 'facebook', 'tiktok']) && isStreaming && ( - - ctrl.openPlatformDash()} className="icon-settings" /> - - )} -
-
- {(isPlatform(['twitch', 'trovo', 'facebook', 'kick']) || - (isPlatform(['youtube', 'twitter']) && isStreaming) || - (isPlatform(['tiktok']) && isRestreaming)) && ( - ctrl.refreshChat()}>{$t('Refresh Chat')} - )} -
-
- {!hideStyleBlockers && - (isPlatform(['twitch', 'trovo']) || - (isStreaming && - isPlatform(['youtube', 'facebook', 'twitter', 'tiktok', 'kick']))) && ( -
- {hasChatTabs && ( -
- setChat(ev.key)} - mode="horizontal" - > - {chatTabs.map(tab => ( - {tab.name} - ))} - - {isPopOutAllowed && ( - - ctrl.popOut()} - /> - - )} -
- )} - {!applicationLoading && !collapsed && ( - - )} - {isPlatform(['youtube', 'facebook']) && isStreaming && ( - - ctrl.openPlatformDash()} className="icon-settings" /> - - )} -
- )} +
+
+
+
+
+ {liveText} + {elapsedStreamTime} +
+
+ ctrl.toggleViewerCount()} + /> + {viewerCount} + {Number(viewerCount) >= 0 && {$t('viewers')}} +
+
+ +
+
+ {ctrl.canEditChannelInfo && ( + + ctrl.showEditStreamInfo()} className="icon-edit" /> + + )} + {isPlatform(['youtube', 'facebook', 'trovo', 'tiktok', 'kick']) && isStreaming && ( + + ctrl.openPlatformStream()} className="icon-studio" /> + + )} + {isPlatform(['youtube', 'facebook', 'tiktok']) && isStreaming && ( + + ctrl.openPlatformDash()} className="icon-settings" /> + + )} +
+
+ {(isPlatform(['twitch', 'trovo', 'facebook', 'kick']) || + (isPlatform(['youtube', 'twitter']) && isStreaming) || + (isPlatform(['tiktok']) && isRestreaming)) && ( + ctrl.refreshChat()}>{$t('Refresh Chat')} + )} +
+
+ {!hideStyleBlockers && + (isPlatform(['twitch', 'trovo']) || + (isStreaming && isPlatform(['youtube', 'facebook', 'twitter', 'tiktok', 'kick']))) && ( +
+ {hasChatTabs && (
- {(isPlatform(['twitch', 'trovo', 'facebook']) || - (isPlatform(['youtube', 'twitter']) && isStreaming)) && ( - ctrl.refreshChat()}>{$t('Refresh Chat')} - )} -
-
- {!hideStyleBlockers && - (isPlatform(['twitch', 'trovo']) || - (isStreaming && - isPlatform(['youtube', 'facebook', 'twitter', 'tiktok', 'kick']))) && ( -
- {hasChatTabs && } - {!applicationLoading && !collapsed && chat} - {!['default', 'restream'].includes(visibleChat) && ( - setChat(ev.key)} + mode="horizontal" + > + {chatTabs.map(tab => ( + {tab.name} + ))} + + {isPopOutAllowed && ( + + ctrl.popOut()} /> - )} -
- )} - {(!ctrl.platform || - (isPlatform(['youtube', 'facebook', 'twitter', 'tiktok', 'kick']) && - !isStreaming)) && ( -
- - {!hideStyleBlockers && {$t('Your chat is currently offline')}} + + )}
)} - - + {!applicationLoading && !collapsed && ( + + )} + {isPlatform(['youtube', 'facebook']) && isStreaming && ( + + ctrl.openPlatformDash()} className="icon-settings" /> + + )} +
+ )} +
+ {(isPlatform(['twitch', 'trovo', 'facebook']) || + (isPlatform(['youtube', 'twitter']) && isStreaming)) && ( + ctrl.refreshChat()}>{$t('Refresh Chat')} + )} +
+
+ {!hideStyleBlockers && + (isPlatform(['twitch', 'trovo']) || + (isStreaming && isPlatform(['youtube', 'facebook', 'twitter', 'tiktok', 'kick']))) && ( +
+ {hasChatTabs && } + {!applicationLoading && !collapsed && chat} + {!['default', 'restream'].includes(visibleChat) && ( + + )} +
)} - + {(!ctrl.platform || + (isPlatform(['youtube', 'facebook', 'twitter', 'tiktok', 'kick']) && !isStreaming)) && ( +
+ + {!hideStyleBlockers && {$t('Your chat is currently offline')}} +
+ )}
); } diff --git a/app/components-react/root/ResizeBar.m.less b/app/components-react/root/ResizeBar.m.less index 666bcf2342d1..fcfb64831e76 100644 --- a/app/components-react/root/ResizeBar.m.less +++ b/app/components-react/root/ResizeBar.m.less @@ -24,6 +24,7 @@ &.right { width: 10px; left: 0px; + margin-right: -8px; top: 0px; cursor: col-resize; .resize-line { @@ -34,6 +35,7 @@ &.left { width: 10px; right: 0px; + margin-left: -8px; top: 0px; cursor: col-resize; .resize-line { diff --git a/app/components-react/root/ResizeBar.tsx b/app/components-react/root/ResizeBar.tsx index 4c31711ba067..24d58406652f 100644 --- a/app/components-react/root/ResizeBar.tsx +++ b/app/components-react/root/ResizeBar.tsx @@ -1,4 +1,4 @@ -import React, { useState, useRef } from 'react'; +import React from 'react'; import { Resizable, ResizableProps } from 'react-resizable'; import cx from 'classnames'; import styles from './ResizeBar.m.less'; diff --git a/app/components-react/windows/Main.m.less b/app/components-react/windows/Main.m.less index 99f089f7bff5..ff92b1fcd980 100644 --- a/app/components-react/windows/Main.m.less +++ b/app/components-react/windows/Main.m.less @@ -15,28 +15,11 @@ } .main-contents { - display: grid; - grid-template-columns: auto 1fr; + display: flex; flex-grow: 1; height: 100%; + width: 100%; position: relative; - // overflow: auto; -} - -.main-contents--right { - grid-template-columns: auto auto 1fr; -} - -.main-contents--left { - grid-template-columns: auto 1fr auto; -} - -.main-contents--onboarding { - grid-template-columns: 1fr; - - .main-middle { - grid-template-rows: 1fr; - } } .main-middle { @@ -44,7 +27,7 @@ grid-template-rows: minmax(0, 1fr) 48px; position: relative; height: 100%; - width: auto; + flex-grow: 1; min-width: 0; } @@ -112,14 +95,21 @@ height: 100%; } -.live-dock-resize-bar { - position: absolute; - height: calc(100% - 20px); - bottom: 0; +.live-dock-container { + display: flex; + flex-grow: 1; + flex-direction: column; + height: 100%; + position: relative; + padding: 16px 8px; + width: 100%; + min-width: 0; + border-left: 1px solid var(--border); } -.live-dock-resize-bar--left { - right: 0; +.live-dock-container.left { + border-left: none; + border-right: 1px solid var(--border); } /deep/ .creator-sites-container .s-loader { diff --git a/app/components-react/windows/Main.tsx b/app/components-react/windows/Main.tsx index 5138ce0c118a..694599dfa6b0 100644 --- a/app/components-react/windows/Main.tsx +++ b/app/components-react/windows/Main.tsx @@ -15,6 +15,7 @@ import SideNav from 'components-react/sidebar/SideNav'; import LiveDock from 'components-react/root/LiveDock'; import StudioFooter from 'components-react/root/StudioFooter'; import Loader from 'components-react/pages/Loader'; +import ResizeBar from 'components-react/root/ResizeBar'; import antdThemes from 'styles/antd/index'; import { getPlatformService } from 'services/platforms'; import { IModalOptions } from 'services/windows'; @@ -108,10 +109,6 @@ class MainController { ); } - get liveDockSize() { - return this.customizationService.state.livedockSize; - } - get isDockCollapsed() { return this.customizationService.state.livedockCollapsed; } @@ -191,12 +188,14 @@ class MainController { } updateLiveDockWidth() { - if (this.liveDockSize !== this.validateWidth(this.liveDockSize)) { - this.setLiveDockWidth(this.liveDockSize); + const liveDockSize = this.customizationService.state.livedockSize; + if (liveDockSize !== this.validateWidth(liveDockSize)) { + this.setLiveDockWidth(liveDockSize); } } setLiveDockWidth(width: number) { + console.log(width, this.store.maxDockWidth); this.customizationService.actions.setSettings({ livedockSize: this.validateWidth(width), }); @@ -266,15 +265,6 @@ function Main() { const dockWidth = useRealmObject(Services.CustomizationService.state).livedockSize; - useEffect(() => { - const unsubscribe = StatefulService.store.subscribe((_, state) => { - if (state.bulkLoadFinished) setBulkLoadFinished(true); - if (state.i18nReady) seti18nReady(true); - }); - - return unsubscribe; - }, []); - function windowSizeHandler() { if (!hideStyleBlockers) { ctrl.updateStyleBlockers(true); @@ -299,6 +289,17 @@ function Main() { }, 200); } + useEffect(() => { + const unsubscribe = StatefulService.store.subscribe((_, state) => { + if (state.bulkLoadFinished) setBulkLoadFinished(true); + if (state.i18nReady) seti18nReady(true); + }); + + windowSizeHandler(); + + return unsubscribe; + }, []); + useEffect(() => { window.addEventListener('resize', windowSizeHandler); const modalChangedSub = WindowsService.modalChanged.subscribe(modalOptions => { @@ -363,12 +364,18 @@ function Main() {
)} {renderDock && leftDock && ( - ctrl.setLiveDockWidth(width)} - minDockWidth={minDockWidth} - maxDockWidth={maxDockWidth} - /> + ctrl.setLiveDockWidth(val)} + max={maxDockWidth} + min={minDockWidth} + value={dockWidth} + transformScale={1} + > +
+ +
+
)}
{renderDock && !leftDock && ( - ctrl.setLiveDockWidth(width)} - minDockWidth={minDockWidth} - maxDockWidth={maxDockWidth} - /> + ctrl.setLiveDockWidth(val)} + max={maxDockWidth} + min={minDockWidth} + value={dockWidth} + transformScale={1} + > +
+ +
+
)}
From 882b5154b3c98aa1182a9d04be2c5338249904f7 Mon Sep 17 00:00:00 2001 From: Sean Beyer Date: Tue, 4 Mar 2025 09:39:12 -0800 Subject: [PATCH 39/51] Get resize working --- app/components-react/editor/elements/Mixer.tsx | 9 ++++++--- app/components-react/windows/Main.tsx | 15 ++++++++++++--- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/app/components-react/editor/elements/Mixer.tsx b/app/components-react/editor/elements/Mixer.tsx index 1aece143acc4..258bee144a15 100644 --- a/app/components-react/editor/elements/Mixer.tsx +++ b/app/components-react/editor/elements/Mixer.tsx @@ -13,7 +13,7 @@ import { useRealmObject } from 'components-react/hooks/realm'; const mins = { x: 150, y: 120 }; export function Mixer() { - const { EditorCommandsService, AudioService, CustomizationService } = Services; + const { EditorCommandsService, AudioService, CustomizationService, WindowsService } = Services; const containerRef = useRef(null); @@ -26,10 +26,11 @@ export function Mixer() { }, []); const performanceMode = useRealmObject(CustomizationService.state).performanceMode; - const { audioSourceIds } = useVuex(() => ({ + const { audioSourceIds, hideStyleBlockers } = useVuex(() => ({ audioSourceIds: AudioService.views.sourcesForCurrentScene .filter(source => !source.mixerHidden && source.isControlledViaObs) .map(source => source.sourceId), + hideStyleBlockers: WindowsService.state.main.hideStyleBlockers, })); function showAdvancedSettings() { @@ -68,7 +69,9 @@ export function Mixer() { style={{ height: 'calc(100% - 32px)' }} >
- {audioSourceIds.length !== 0 && !performanceMode && } + {audioSourceIds.length !== 0 && !performanceMode && !hideStyleBlockers && ( + + )} {audioSourceIds.map(sourceId => ( ({ theme: ctrl.theme(bulkLoadFinished), @@ -259,6 +264,7 @@ function Main() { minDockWidth: ctrl.store.minDockWidth, hideStyleBlockers: ctrl.hideStyleBlockers, compactView: ctrl.store.compactView, + sideNavCollapsed: ctrl.sideNavCollapsed, }), true, ); @@ -343,6 +349,8 @@ function Main() { onTotalWidth: (width: number) => void; }> = (appPages as Dictionary)[page]; + const sideBarSize = sideNavCollapsed ? 70 : 220; + return (
{!showLoadingSpinner && ( @@ -406,7 +415,7 @@ function Main() { value={dockWidth} transformScale={1} > -
+
From 78ff7eecf9f37e60cd63831bf3a77227a8be8461 Mon Sep 17 00:00:00 2001 From: Sean Beyer Date: Tue, 4 Mar 2025 15:00:15 -0800 Subject: [PATCH 40/51] Refactor to add collapse --- app/components-react/root/LiveDock.m.less | 43 -------- app/components-react/root/LiveDock.tsx | 23 ---- app/components-react/windows/Main.m.less | 40 +++++++ app/components-react/windows/Main.tsx | 129 +++++++++++++++------- 4 files changed, 130 insertions(+), 105 deletions(-) diff --git a/app/components-react/root/LiveDock.m.less b/app/components-react/root/LiveDock.m.less index 7cc4f6d4faac..ac06d663c4d2 100644 --- a/app/components-react/root/LiveDock.m.less +++ b/app/components-react/root/LiveDock.m.less @@ -20,49 +20,6 @@ } } -.live-dock--left { - .live-dock-chevron { - right: 0; - left: auto; - } -} - -.live-dock.collapsed { - width: 20px !important; - padding: 0; - - .live-dock-chevron { - .center(); - - border: none; - } -} - -.live-dock-resize-bar { - // So we don't block the collapse chevron - top: 20px !important; -} - -.live-dock-chevron { - width: 16px; - height: 20px; - position: absolute; - top: 0; - left: 0; - border-bottom: 1px solid var(--border); - cursor: pointer; - - i { - .center(); - - font-size: 12px; - - &:global(.icon-right) { - transform: translate(-50%, -50%) rotate(-90deg); - } - } -} - .live-dock-end-stream { margin-left: 10px; } diff --git a/app/components-react/root/LiveDock.tsx b/app/components-react/root/LiveDock.tsx index 18ce13ebae24..9539fc8f58cd 100644 --- a/app/components-react/root/LiveDock.tsx +++ b/app/components-react/root/LiveDock.tsx @@ -212,20 +212,6 @@ class LiveDockController { }); } - setCollapsed(livedockCollapsed: boolean) { - this.store.setState(s => { - s.canAnimate = true; - }); - this.windowsService.actions.updateStyleBlockers('main', true); - this.customizationService.actions.setSettings({ livedockCollapsed }); - setTimeout(() => { - this.store.setState(s => { - s.canAnimate = false; - }); - this.windowsService.actions.updateStyleBlockers('main', false); - }, 300); - } - toggleViewerCount() { this.customizationService.actions.setHiddenViewerCount( !this.customizationService.state.hideViewerCount, @@ -288,16 +274,11 @@ function LiveDock() { ]), ); - const liveDockSize = useRealmObject(Services.CustomizationService.state).livedockSize; const collapsed = useRealmObject(Services.CustomizationService.state).livedockCollapsed; const hideViewerCount = useRealmObject(Services.CustomizationService.state).hideViewerCount; const viewerCount = hideViewerCount ? $t('Viewers Hidden') : currentViewers; useEffect(() => { - if (streamingStatus === EStreamingState.Starting && collapsed) { - ctrl.setCollapsed(false); - } - const elapsedInterval = window.setInterval(() => { if (streamingStatus === EStreamingState.Live) { setElapsedStreamTime(ctrl.getElapsedStreamTime()); @@ -321,10 +302,6 @@ function LiveDock() { } }, [visibleChat, isRestreaming, streamingStatus]); - function toggleCollapsed() { - collapsed ? ctrl.setCollapsed(false) : ctrl.setCollapsed(true); - } - // Safe getter/setter prevents getting stuck on the chat // for an app that was unloaded. function setChat(key: string) { diff --git a/app/components-react/windows/Main.m.less b/app/components-react/windows/Main.m.less index ff92b1fcd980..97c5b6786603 100644 --- a/app/components-react/windows/Main.m.less +++ b/app/components-react/windows/Main.m.less @@ -112,6 +112,46 @@ border-right: 1px solid var(--border); } +.live-dock-collapsed { + width: 20px; + height: 100%; + padding: 0; + position: relative; + border-left: 1px solid var(--border); + box-sizing: border-box; + + .live-dock-collapsed.left { + border-left: none; + border-right: 1px solid var(--border); + } + + .live-dock-chevron { + .center(); + + border: none; + } +} + +.live-dock-chevron { + width: 16px; + height: 20px; + position: absolute; + top: 0; + left: 0; + border-bottom: 1px solid var(--border); + cursor: pointer; + + i { + .center(); + + font-size: 12px; + + &:global(.icon-right) { + transform: translate(-50%, -50%) rotate(-90deg); + } + } +} + /deep/ .creator-sites-container .s-loader { .s-loader__bg { position: unset; diff --git a/app/components-react/windows/Main.tsx b/app/components-react/windows/Main.tsx index f65a8c765e2e..40627d64963a 100644 --- a/app/components-react/windows/Main.tsx +++ b/app/components-react/windows/Main.tsx @@ -19,6 +19,7 @@ import ResizeBar from 'components-react/root/ResizeBar'; import antdThemes from 'styles/antd/index'; import { getPlatformService } from 'services/platforms'; import { IModalOptions } from 'services/windows'; +import { EStreamingState } from 'services/streaming'; import { TApplicationTheme } from 'services/customization'; import styles from './Main.m.less'; import { StatefulService } from 'services'; @@ -43,6 +44,7 @@ class MainController { private platformAppsService = Services.PlatformAppsService; private editorCommandsService = Services.EditorCommandsService; private sideNavService = Services.SideNavService; + private streamingService = Services.StreamingService; modalOptions: IModalOptions = { renderFn: null, @@ -60,6 +62,7 @@ class MainController { minDockWidth: 290, maxDockWidth: 290, minEditorWidth: 500, + canAnimate: false, }); get title() { @@ -82,6 +85,10 @@ class MainController { return this.sideNavService.state.compactView; } + get streamingStatus() { + return this.streamingService.state.streamingStatus; + } + theme(bulkLoadFinished: boolean): TApplicationTheme { if (bulkLoadFinished) { return this.customizationService.currentTheme; @@ -114,10 +121,6 @@ class MainController { ); } - get isDockCollapsed() { - return this.customizationService.state.livedockCollapsed; - } - get leftDock() { return this.customizationService.state.leftDock; } @@ -214,6 +217,20 @@ class MainController { updateStyleBlockers(val: boolean) { this.windowsService.actions.updateStyleBlockers('main', val); } + + setCollapsed(livedockCollapsed: boolean) { + this.store.setState(s => { + s.canAnimate = true; + }); + this.windowsService.actions.updateStyleBlockers('main', true); + this.customizationService.actions.setSettings({ livedockCollapsed }); + setTimeout(() => { + this.store.setState(s => { + s.canAnimate = false; + }); + this.windowsService.actions.updateStyleBlockers('main', false); + }, 300); + } } export default function MainWithContext(): ReactElement<{}> { @@ -245,8 +262,6 @@ function Main() { leftDock, applicationLoading, page, - maxDockWidth, - minDockWidth, hideStyleBlockers, compactView, sideNavCollapsed, @@ -260,8 +275,6 @@ function Main() { hasLiveDock: ctrl.store.hasLiveDock, applicationLoading: ctrl.applicationLoading, page: ctrl.page, - maxDockWidth: ctrl.store.maxDockWidth, - minDockWidth: ctrl.store.minDockWidth, hideStyleBlockers: ctrl.hideStyleBlockers, compactView: ctrl.store.compactView, sideNavCollapsed: ctrl.sideNavCollapsed, @@ -270,6 +283,7 @@ function Main() { ); const dockWidth = useRealmObject(Services.CustomizationService.state).livedockSize; + const isDockCollapsed = useRealmObject(Services.CustomizationService.state).livedockCollapsed; function windowSizeHandler() { if (!hideStyleBlockers) { @@ -350,6 +364,7 @@ function Main() { }> = (appPages as Dictionary)[page]; const sideBarSize = sideNavCollapsed ? 70 : 220; + const liveDockSize = isDockCollapsed ? 20 : dockWidth; return (
)} - {renderDock && leftDock && ( - ctrl.setLiveDockWidth(val)} - max={maxDockWidth} - min={minDockWidth} - value={dockWidth} - transformScale={1} - > -
- -
-
- )} - + {leftDock && }
{!showLoadingSpinner && ( @@ -405,21 +406,7 @@ function Main() {
)}
- - {renderDock && !leftDock && ( - ctrl.setLiveDockWidth(val)} - max={maxDockWidth} - min={minDockWidth} - value={dockWidth} - transformScale={1} - > -
- -
-
- )} + {!leftDock && }
@@ -432,3 +419,67 @@ function Main() {
); } + +function LiveDockContainer(p: { onLeft?: boolean }) { + const ctrl = useController(MainCtx); + + const { maxDockWidth, minDockWidth, renderDock, streamingStatus } = useVuex( + () => ({ + maxDockWidth: ctrl.store.maxDockWidth, + minDockWidth: ctrl.store.minDockWidth, + renderDock: ctrl.renderDock, + streamingStatus: ctrl.streamingStatus, + }), + true, + ); + + useEffect(() => { + if (streamingStatus === EStreamingState.Starting && isDockCollapsed) { + ctrl.setCollapsed(false); + } + }, [streamingStatus]); + + const dockWidth = useRealmObject(Services.CustomizationService.state).livedockSize; + const isDockCollapsed = useRealmObject(Services.CustomizationService.state).livedockCollapsed; + + function Chevron() { + return ( +
ctrl.setCollapsed(!isDockCollapsed)}> + +
+ ); + } + + if (!renderDock) return <>; + + if (isDockCollapsed) { + return ( +
+ +
+ ); + } + + return ( + ctrl.setLiveDockWidth(val)} + max={maxDockWidth} + min={minDockWidth} + value={dockWidth} + transformScale={1} + > +
+ + +
+
+ ); +} From 060778af27189892dd1ca0fc50588df69c3f3ad6 Mon Sep 17 00:00:00 2001 From: Sean Beyer Date: Wed, 12 Mar 2025 08:08:34 -0700 Subject: [PATCH 41/51] Fix dock resizing --- app/components-react/windows/Main.tsx | 48 ++++++++++++++------------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/app/components-react/windows/Main.tsx b/app/components-react/windows/Main.tsx index 40627d64963a..28f05f6c31f7 100644 --- a/app/components-react/windows/Main.tsx +++ b/app/components-react/windows/Main.tsx @@ -265,6 +265,9 @@ function Main() { hideStyleBlockers, compactView, sideNavCollapsed, + maxDockWidth, + minDockWidth, + streamingStatus, } = useVuex( () => ({ theme: ctrl.theme(bulkLoadFinished), @@ -278,6 +281,9 @@ function Main() { hideStyleBlockers: ctrl.hideStyleBlockers, compactView: ctrl.store.compactView, sideNavCollapsed: ctrl.sideNavCollapsed, + maxDockWidth: ctrl.store.maxDockWidth, + minDockWidth: ctrl.store.minDockWidth, + streamingStatus: ctrl.streamingStatus, }), true, ); @@ -332,6 +338,12 @@ function Main() { }; }, [hideStyleBlockers]); + useEffect(() => { + if (streamingStatus === EStreamingState.Starting && isDockCollapsed) { + ctrl.setCollapsed(false); + } + }, [streamingStatus]); + const oldTheme = useRef(null); useEffect(() => { if (!theme) return; @@ -366,6 +378,8 @@ function Main() { const sideBarSize = sideNavCollapsed ? 70 : 220; const liveDockSize = isDockCollapsed ? 20 : dockWidth; + console.log('main', maxDockWidth); + return (
)} - {leftDock && } + {renderDock && leftDock && ( + + )}
)}
- {!leftDock && } + {renderDock && !leftDock && ( + + )}
@@ -420,25 +438,9 @@ function Main() { ); } -function LiveDockContainer(p: { onLeft?: boolean }) { +function LiveDockContainer(p: { maxDockWidth: number; minDockWidth: number; onLeft?: boolean }) { const ctrl = useController(MainCtx); - const { maxDockWidth, minDockWidth, renderDock, streamingStatus } = useVuex( - () => ({ - maxDockWidth: ctrl.store.maxDockWidth, - minDockWidth: ctrl.store.minDockWidth, - renderDock: ctrl.renderDock, - streamingStatus: ctrl.streamingStatus, - }), - true, - ); - - useEffect(() => { - if (streamingStatus === EStreamingState.Starting && isDockCollapsed) { - ctrl.setCollapsed(false); - } - }, [streamingStatus]); - const dockWidth = useRealmObject(Services.CustomizationService.state).livedockSize; const isDockCollapsed = useRealmObject(Services.CustomizationService.state).livedockCollapsed; @@ -457,8 +459,6 @@ function LiveDockContainer(p: { onLeft?: boolean }) { ); } - if (!renderDock) return <>; - if (isDockCollapsed) { return (
@@ -467,12 +467,14 @@ function LiveDockContainer(p: { onLeft?: boolean }) { ); } + console.log(p.maxDockWidth); + return ( ctrl.setLiveDockWidth(val)} - max={maxDockWidth} - min={minDockWidth} + max={p.maxDockWidth} + min={p.minDockWidth} value={dockWidth} transformScale={1} > From 149fe5863b4225d7489d706fdd87a1d8b2444b2a Mon Sep 17 00:00:00 2001 From: Sean Beyer Date: Wed, 12 Mar 2025 09:28:12 -0700 Subject: [PATCH 42/51] Add perf optimizations --- .../editor/elements/Mixer.tsx | 4 +- .../editor/elements/mixer/GLVolmeters.tsx | 37 ++++++++++--------- app/components-react/windows/Main.tsx | 22 +++-------- 3 files changed, 26 insertions(+), 37 deletions(-) diff --git a/app/components-react/editor/elements/Mixer.tsx b/app/components-react/editor/elements/Mixer.tsx index 258bee144a15..da49d27a0033 100644 --- a/app/components-react/editor/elements/Mixer.tsx +++ b/app/components-react/editor/elements/Mixer.tsx @@ -69,9 +69,7 @@ export function Mixer() { style={{ height: 'calc(100% - 32px)' }} >
- {audioSourceIds.length !== 0 && !performanceMode && !hideStyleBlockers && ( - - )} + {audioSourceIds.length !== 0 && !performanceMode && } {audioSourceIds.map(sourceId => ( new GLVolmetersController(), []); + return ( + + + + ); +} + /** * Component that renders the volume for audio sources via WebGL */ -export default function GLVolmeters() { +function GLVolmeters() { const canvasRef = useRef(null); - // init controller on mount - const controller = useMemo(() => { - const controller = new GLVolmetersController(); - controller.init(); - return controller; - }, []); + const controller = useController(GLVolmetersCtx); // start rendering volmeters when the canvas is ready useEffect(() => { @@ -80,10 +86,10 @@ export default function GLVolmeters() { ); } +const GLVolmetersCtx = React.createContext(null); class GLVolmetersController { private customizationService = Services.CustomizationService; private audioService = Services.AudioService; - private sourcesService = Services.SourcesService; subscriptions: Dictionary = {}; @@ -104,9 +110,7 @@ class GLVolmetersController { private canvasWidth: number; private canvasWidthInterval: number; - private channelCount: number; private canvasHeight: number; - private renderingInitialized: boolean; // time between 2 received peaks. // Used to render extra interpolated frames @@ -116,12 +120,10 @@ class GLVolmetersController { private firstFrameTime: number; private frameNumber: number; private sourcesOrder: string[]; - private workerId: number; private requestedFrameId: number; private bgMultiplier = this.customizationService.isDarkTheme ? 0.2 : 0.5; init() { - this.workerId = electron.ipcRenderer.sendSync('getWorkerWindowId'); this.subscribeVolmeters(); this.bg = this.customizationService.sectionBackground; this.fpsLimit = 30; @@ -141,7 +143,7 @@ class GLVolmetersController { /** * add or remove subscription for volmeters depending on current scene */ - + @debounce(500) private subscribeVolmeters() { const audioSources = this.audioSources; const sourcesOrder = audioSources.map(source => source.sourceId); @@ -183,7 +185,7 @@ class GLVolmetersController { peakHoldCounters: [], }; - this.audioService.subscribeVolmeter(sourceId).then(id => { + this.audioService.actions.return.subscribeVolmeter(sourceId).then(id => { ipcRenderer.once(`port-${id}`, e => { if (!this.subscriptions[sourceId]) return; this.subscriptions[sourceId].channelId = id; @@ -227,6 +229,9 @@ class GLVolmetersController { setupNewCanvas($canvasEl: HTMLCanvasElement) { this.$refs.canvas = $canvasEl; // Make sure all state is cleared out + if (this.gl && this.program) { + this.gl.deleteProgram(this.program); + } this.gl = null!; this.program = null!; this.positionLocation = null!; @@ -237,7 +242,6 @@ class GLVolmetersController { this.peakHoldLocation = null!; this.bgMultiplierLocation = null!; this.canvasWidth = 0; - this.channelCount = 0; this.canvasHeight = 0; this.setCanvasSize(); @@ -246,7 +250,6 @@ class GLVolmetersController { this.gl = getDefined(this.$refs.canvas.getContext('webgl', { alpha: false })); this.initWebglRendering(); - this.renderingInitialized = true; } /** @@ -357,7 +360,7 @@ class GLVolmetersController { this.gl.clearColor(bg.r / 255, bg.g / 255, bg.b / 255, 1); this.gl.clear(this.gl.COLOR_BUFFER_BIT); - if (this.canvasWidth < 0 || this.canvasHeight < 0) return; + if (this.canvasWidth < 0 || this.canvasHeight < 0 || !this.sourcesOrder) return; this.gl.viewport(0, 0, this.canvasWidth, this.canvasHeight); diff --git a/app/components-react/windows/Main.tsx b/app/components-react/windows/Main.tsx index 28f05f6c31f7..e0ca34f75290 100644 --- a/app/components-react/windows/Main.tsx +++ b/app/components-react/windows/Main.tsx @@ -24,6 +24,7 @@ import { TApplicationTheme } from 'services/customization'; import styles from './Main.m.less'; import { StatefulService } from 'services'; import { useRealmObject } from 'components-react/hooks/realm'; +import { debounce } from 'lodash-decorators'; const MainCtx = React.createContext(null); @@ -43,7 +44,6 @@ class MainController { private scenesService = Services.ScenesService; private platformAppsService = Services.PlatformAppsService; private editorCommandsService = Services.EditorCommandsService; - private sideNavService = Services.SideNavService; private streamingService = Services.StreamingService; modalOptions: IModalOptions = { @@ -81,10 +81,6 @@ class MainController { return this.windowsService.state.main.hideStyleBlockers; } - get sideNavCollapsed() { - return this.sideNavService.state.compactView; - } - get streamingStatus() { return this.streamingService.state.streamingStatus; } @@ -189,6 +185,7 @@ class MainController { ); } + @debounce(500) handleEditorWidth(width: number) { this.store.setState(s => { s.minEditorWidth = width; @@ -264,7 +261,6 @@ function Main() { page, hideStyleBlockers, compactView, - sideNavCollapsed, maxDockWidth, minDockWidth, streamingStatus, @@ -280,7 +276,6 @@ function Main() { page: ctrl.page, hideStyleBlockers: ctrl.hideStyleBlockers, compactView: ctrl.store.compactView, - sideNavCollapsed: ctrl.sideNavCollapsed, maxDockWidth: ctrl.store.maxDockWidth, minDockWidth: ctrl.store.minDockWidth, streamingStatus: ctrl.streamingStatus, @@ -336,7 +331,7 @@ function Main() { window.removeEventListener('resize', windowSizeHandler); modalChangedSub.unsubscribe(); }; - }, [hideStyleBlockers]); + }, []); useEffect(() => { if (streamingStatus === EStreamingState.Starting && isDockCollapsed) { @@ -375,11 +370,6 @@ function Main() { onTotalWidth: (width: number) => void; }> = (appPages as Dictionary)[page]; - const sideBarSize = sideNavCollapsed ? 70 : 220; - const liveDockSize = isDockCollapsed ? 20 : dockWidth; - - console.log('main', maxDockWidth); - return (
{!showLoadingSpinner && ( @@ -467,8 +457,6 @@ function LiveDockContainer(p: { maxDockWidth: number; minDockWidth: number; onLe ); } - console.log(p.maxDockWidth); - return (
From b1b39376e657d27c2d2130ca176e21df6e354f80 Mon Sep 17 00:00:00 2001 From: Sean Beyer Date: Wed, 19 Mar 2025 12:22:39 -0700 Subject: [PATCH 43/51] Add animation --- app/components-react/windows/Main.tsx | 49 ++++++++++++++++----------- 1 file changed, 29 insertions(+), 20 deletions(-) diff --git a/app/components-react/windows/Main.tsx b/app/components-react/windows/Main.tsx index e0ca34f75290..5166ec383284 100644 --- a/app/components-react/windows/Main.tsx +++ b/app/components-react/windows/Main.tsx @@ -449,27 +449,36 @@ function LiveDockContainer(p: { maxDockWidth: number; minDockWidth: number; onLe ); } - if (isDockCollapsed) { - return ( -
- -
- ); - } + const transitionName = useMemo(() => { + if ((p.onLeft && isDockCollapsed) || (!p.onLeft && !isDockCollapsed)) { + return 'ant-slide-right'; + } + return 'ant-slide-left'; + }, [p.onLeft, isDockCollapsed]); return ( - ctrl.setLiveDockWidth(val)} - max={p.maxDockWidth} - min={p.minDockWidth} - value={dockWidth} - transformScale={2} - > -
- - -
-
+ + {isDockCollapsed && ( +
+ +
+ )} + {!isDockCollapsed && ( + ctrl.setLiveDockWidth(val)} + max={p.maxDockWidth} + min={p.minDockWidth} + value={dockWidth} + transformScale={1} + key="expanded" + > +
+ + +
+
+ )} +
); } From 77e35408b977b3aeace7de3eb58cee9fa13f5475 Mon Sep 17 00:00:00 2001 From: Sean Beyer Date: Fri, 21 Mar 2025 12:56:25 -0700 Subject: [PATCH 44/51] Fix non-editor size weirdness --- app/components-react/windows/Main.m.less | 4 +--- app/components-react/windows/Main.tsx | 2 -- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/app/components-react/windows/Main.m.less b/app/components-react/windows/Main.m.less index 97c5b6786603..d26e39f6e0a3 100644 --- a/app/components-react/windows/Main.m.less +++ b/app/components-react/windows/Main.m.less @@ -27,6 +27,7 @@ grid-template-rows: minmax(0, 1fr) 48px; position: relative; height: 100%; + width: 100%; flex-grow: 1; min-width: 0; } @@ -127,8 +128,6 @@ .live-dock-chevron { .center(); - - border: none; } } @@ -138,7 +137,6 @@ position: absolute; top: 0; left: 0; - border-bottom: 1px solid var(--border); cursor: pointer; i { diff --git a/app/components-react/windows/Main.tsx b/app/components-react/windows/Main.tsx index 5166ec383284..04466bae41c5 100644 --- a/app/components-react/windows/Main.tsx +++ b/app/components-react/windows/Main.tsx @@ -395,7 +395,6 @@ function Main() { )}
{!showLoadingSpinner && ( @@ -439,7 +438,6 @@ function LiveDockContainer(p: { maxDockWidth: number; minDockWidth: number; onLe
ctrl.setCollapsed(!isDockCollapsed)}> Date: Fri, 21 Mar 2025 13:07:05 -0700 Subject: [PATCH 45/51] Fix chat disappearing --- app/components-react/root/LiveDock.tsx | 62 +++----------------------- 1 file changed, 7 insertions(+), 55 deletions(-) diff --git a/app/components-react/root/LiveDock.tsx b/app/components-react/root/LiveDock.tsx index 9539fc8f58cd..fada10f3afbc 100644 --- a/app/components-react/root/LiveDock.tsx +++ b/app/components-react/root/LiveDock.tsx @@ -252,7 +252,6 @@ function LiveDock() { hideStyleBlockers, currentViewers, pageSlot, - canAnimate, liveText, isPopOutAllowed, streamingStatus, @@ -266,7 +265,6 @@ function LiveDock() { 'applicationLoading', 'hideStyleBlockers', 'pageSlot', - 'canAnimate', 'currentViewers', 'liveText', 'isPopOutAllowed', @@ -403,65 +401,19 @@ function LiveDock() { (isPlatform(['twitch', 'trovo']) || (isStreaming && isPlatform(['youtube', 'facebook', 'twitter', 'tiktok', 'kick']))) && (
- {hasChatTabs && ( -
- setChat(ev.key)} - mode="horizontal" - > - {chatTabs.map(tab => ( - {tab.name} - ))} - - {isPopOutAllowed && ( - - ctrl.popOut()} - /> - - )} -
- )} - {!applicationLoading && !collapsed && ( - } + {!applicationLoading && !collapsed && chat} + {!['default', 'restream'].includes(visibleChat) && ( + )} - {isPlatform(['youtube', 'facebook']) && isStreaming && ( - - ctrl.openPlatformDash()} className="icon-settings" /> - - )}
)} -
- {(isPlatform(['twitch', 'trovo', 'facebook']) || - (isPlatform(['youtube', 'twitter']) && isStreaming)) && ( - ctrl.refreshChat()}>{$t('Refresh Chat')} - )} -
- {!hideStyleBlockers && - (isPlatform(['twitch', 'trovo']) || - (isStreaming && isPlatform(['youtube', 'facebook', 'twitter', 'tiktok', 'kick']))) && ( -
- {hasChatTabs && } - {!applicationLoading && !collapsed && chat} - {!['default', 'restream'].includes(visibleChat) && ( - - )} -
- )} {(!ctrl.platform || (isPlatform(['youtube', 'facebook', 'twitter', 'tiktok', 'kick']) && !isStreaming)) && (
From 80b05abc171c84b193ffc68e680a109c59c09946 Mon Sep 17 00:00:00 2001 From: Sean Beyer Date: Wed, 23 Apr 2025 09:49:33 -0700 Subject: [PATCH 46/51] Fix strict nulls issue --- app/services/platforms/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/services/platforms/index.ts b/app/services/platforms/index.ts index 823d6592f924..adbebe2db1e7 100644 --- a/app/services/platforms/index.ts +++ b/app/services/platforms/index.ts @@ -286,7 +286,7 @@ export const platformLabels = (platform: TPlatform | string) => [EPlatform.Kick]: $t('Kick'), }[platform]); -export function getPlatformService(platform?: TPlatform): IPlatformService { +export function getPlatformService(platform?: TPlatform): IPlatformService | undefined { if (!platform) return; return { twitch: TwitchService.instance, From 4e7caee90e356987617ef215e716da5fcee29ad6 Mon Sep 17 00:00:00 2001 From: Sean Beyer Date: Wed, 23 Apr 2025 10:04:20 -0700 Subject: [PATCH 47/51] Fix strict nulls again --- app/services/platforms/index.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/services/platforms/index.ts b/app/services/platforms/index.ts index adbebe2db1e7..46df3c5bf5db 100644 --- a/app/services/platforms/index.ts +++ b/app/services/platforms/index.ts @@ -286,8 +286,7 @@ export const platformLabels = (platform: TPlatform | string) => [EPlatform.Kick]: $t('Kick'), }[platform]); -export function getPlatformService(platform?: TPlatform): IPlatformService | undefined { - if (!platform) return; +export function getPlatformService(platform: TPlatform): IPlatformService { return { twitch: TwitchService.instance, youtube: YoutubeService.instance, From 699e37578aa8679cb841fb9f40f52380bb8ad9a5 Mon Sep 17 00:00:00 2001 From: Sean Beyer Date: Wed, 23 Apr 2025 10:15:00 -0700 Subject: [PATCH 48/51] Fix strict nulls last time pls --- app/components-react/windows/Main.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/components-react/windows/Main.tsx b/app/components-react/windows/Main.tsx index 04466bae41c5..712925f62a76 100644 --- a/app/components-react/windows/Main.tsx +++ b/app/components-react/windows/Main.tsx @@ -112,7 +112,8 @@ class MainController { this.isLoggedIn && !this.isOnboarding && this.store.hasLiveDock && - getPlatformService(this.userService.platform?.type)?.liveDockEnabled && + this.userService.platform && + getPlatformService(this.userService.platform.type).liveDockEnabled && !this.showLoadingSpinner ); } From 985d41b2fc84e5e63d5a7d814a31f54e18facb86 Mon Sep 17 00:00:00 2001 From: Sean Beyer Date: Mon, 5 May 2025 15:17:55 -0700 Subject: [PATCH 49/51] Address CR --- .../editor/elements/mixer/GLVolmeters.tsx | 4 +++- app/components-react/pages/PlatformAppStore.tsx | 13 +++++++------ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/app/components-react/editor/elements/mixer/GLVolmeters.tsx b/app/components-react/editor/elements/mixer/GLVolmeters.tsx index c962c37533ac..60f417f317a7 100644 --- a/app/components-react/editor/elements/mixer/GLVolmeters.tsx +++ b/app/components-react/editor/elements/mixer/GLVolmeters.tsx @@ -361,7 +361,9 @@ class GLVolmetersController { this.gl.clearColor(bg.r / 255, bg.g / 255, bg.b / 255, 1); this.gl.clear(this.gl.COLOR_BUFFER_BIT); - if (this.canvasWidth < 0 || this.canvasHeight < 0 || !this.sourcesOrder) return; + if (this.canvasWidth < 0 || this.canvasHeight < 0 || !this.sourcesOrder) { + return; + } this.gl.viewport(0, 0, this.canvasWidth, this.canvasHeight); diff --git a/app/components-react/pages/PlatformAppStore.tsx b/app/components-react/pages/PlatformAppStore.tsx index 76b9eb3eb0d2..95beb880cabe 100644 --- a/app/components-react/pages/PlatformAppStore.tsx +++ b/app/components-react/pages/PlatformAppStore.tsx @@ -86,17 +86,18 @@ export default function PlatformAppStore(p: { } if (!platformAppsUrl) return <>; + + const heightDiff = + currentUrl.includes('installed-apps') && HighlighterService.views.highlighterVersion !== '' + ? '72' + : '0'; + return ( <> Date: Mon, 5 May 2025 15:52:59 -0700 Subject: [PATCH 50/51] Clean up platform logic dealing with lviedock features --- app/components-react/root/LiveDock.tsx | 48 ++++++++++++------------- app/services/platforms/base-platform.ts | 8 +++++ app/services/platforms/facebook.ts | 17 +++++++-- app/services/platforms/index.ts | 11 ++++++ app/services/platforms/instagram.ts | 2 ++ app/services/platforms/kick.ts | 6 ++++ app/services/platforms/tiktok.ts | 7 ++++ app/services/platforms/trovo.ts | 6 ++++ app/services/platforms/twitch.ts | 3 ++ app/services/platforms/twitter.ts | 12 ++++++- app/services/platforms/youtube.ts | 7 ++++ 11 files changed, 100 insertions(+), 27 deletions(-) diff --git a/app/components-react/root/LiveDock.tsx b/app/components-react/root/LiveDock.tsx index fada10f3afbc..3824991fb22f 100644 --- a/app/components-react/root/LiveDock.tsx +++ b/app/components-react/root/LiveDock.tsx @@ -6,7 +6,7 @@ import pick from 'lodash/pick'; import { initStore, useController } from 'components-react/hooks/zustand'; import { EStreamingState } from 'services/streaming'; import { EAppPageSlot, ILoadedApp } from 'services/platform-apps'; -import { getPlatformService, TPlatform } from 'services/platforms'; +import { getPlatformService, TLiveDockFeature, TPlatform } from 'services/platforms'; import { $t } from 'services/i18n'; import { Services } from '../service-provider'; import Chat from './Chat'; @@ -23,7 +23,6 @@ class LiveDockController { private streamingService = Services.StreamingService; private youtubeService = Services.YoutubeService; private facebookService = Services.FacebookService; - private trovoService = Services.TrovoService; private kickService = Services.KickService; private tiktokService = Services.TikTokService; private userService = Services.UserService; @@ -79,6 +78,11 @@ class LiveDockController { return this.userService.platform?.type; } + get platformService() { + if (!this.platform) return; + return getPlatformService(this.platform); + } + get offlineImageSrc() { const mode = this.customizationService.isDarkTheme ? 'night' : 'day'; return $i(`images/sleeping-kevin-${mode}.png`); @@ -175,21 +179,14 @@ class LiveDockController { } openPlatformStream() { - let url = ''; - if (this.platform === 'youtube') url = this.youtubeService.streamPageUrl; - if (this.platform === 'facebook') url = this.facebookService.streamPageUrl; - if (this.platform === 'trovo') url = this.trovoService.streamPageUrl; - if (this.platform === 'kick') url = this.kickService.streamPageUrl; - if (this.platform === 'tiktok') url = this.tiktokService.streamPageUrl; + const url = this.platformService?.streamPageUrl; + if (!url) return; remote.shell.openExternal(url); } openPlatformDash() { - let url = ''; - if (this.platform === 'youtube') url = this.youtubeService.dashboardUrl; - if (this.platform === 'facebook') url = this.facebookService.streamDashboardUrl; - if (this.platform === 'tiktok') url = this.tiktokService.dashboardUrl; - if (this.platform === 'kick') url = this.kickService.dashboardUrl; + const url = this.platformService?.dashboardUrl; + if (!url) return; remote.shell.openExternal(url); } @@ -225,6 +222,10 @@ class LiveDockController { showMultistreamChatInfo() { this.chatService.actions.showMultistreamChatWindow(); } + + hasLiveDockFeature(feature: TLiveDockFeature) { + return this.platformService?.hasLiveDockFeature(feature); + } } export default function LiveDockWithContext() { @@ -244,6 +245,7 @@ function LiveDock() { const { isPlatform, + hasLiveDockFeature, isStreaming, isRestreaming, hasChatTabs, @@ -253,7 +255,6 @@ function LiveDock() { currentViewers, pageSlot, liveText, - isPopOutAllowed, streamingStatus, } = useVuex(() => pick(ctrl, [ @@ -267,7 +268,7 @@ function LiveDock() { 'pageSlot', 'currentViewers', 'liveText', - 'isPopOutAllowed', + 'hasLiveDockFeature', 'streamingStatus', ]), ); @@ -370,7 +371,7 @@ function LiveDock() { ctrl.showEditStreamInfo()} className="icon-edit" /> )} - {isPlatform(['youtube', 'facebook', 'trovo', 'tiktok', 'kick']) && isStreaming && ( + {hasLiveDockFeature('view-stream') && isStreaming && ( ctrl.openPlatformStream()} className="icon-studio" /> )} - {isPlatform(['youtube', 'facebook', 'tiktok']) && isStreaming && ( + {hasLiveDockFeature('dashboard') && isStreaming && (
- {(isPlatform(['twitch', 'trovo', 'facebook', 'kick']) || - (isPlatform(['youtube', 'twitter']) && isStreaming) || - (isPlatform(['tiktok']) && isRestreaming)) && ( + {(hasLiveDockFeature('refresh-chat') || + (hasLiveDockFeature('refresh-chat-streaming') && isStreaming) || + (hasLiveDockFeature('refresh-chat-restreaming') && isRestreaming)) && ( ctrl.refreshChat()}>{$t('Refresh Chat')} )}
{!hideStyleBlockers && - (isPlatform(['twitch', 'trovo']) || - (isStreaming && isPlatform(['youtube', 'facebook', 'twitter', 'tiktok', 'kick']))) && ( + (hasLiveDockFeature('chat-offline') || + (isStreaming && hasLiveDockFeature('chat-streaming'))) && (
{hasChatTabs && } {!applicationLoading && !collapsed && chat} @@ -414,8 +415,7 @@ function LiveDock() {
)}
- {(!ctrl.platform || - (isPlatform(['youtube', 'facebook', 'twitter', 'tiktok', 'kick']) && !isStreaming)) && ( + {(!ctrl.platform || (hasLiveDockFeature('chat-streaming') && !isStreaming)) && (
{!hideStyleBlockers && {$t('Your chat is currently offline')}} diff --git a/app/services/platforms/base-platform.ts b/app/services/platforms/base-platform.ts index 0565740f679f..5afed345f166 100644 --- a/app/services/platforms/base-platform.ts +++ b/app/services/platforms/base-platform.ts @@ -6,6 +6,7 @@ import { TPlatformCapability, TStartStreamOptions, TPlatformCapabilityMap, + TLiveDockFeature, } from './index'; import { StreamingService } from 'services/streaming'; import { UserService } from 'services/user'; @@ -41,11 +42,18 @@ export abstract class BasePlatformService extends Stat abstract capabilities: Set; + abstract liveDockFeatures: Set; + @ExecuteInCurrentWindow() hasCapability(capability: T): this is TPlatformCapabilityMap[T] { return this.capabilities.has(capability); } + @ExecuteInCurrentWindow() + hasLiveDockFeature(feature: TLiveDockFeature) { + return this.liveDockFeatures.has(feature); + } + get mergeUrl() { const host = this.hostsService.streamlabs; const token = this.userService.apiToken; diff --git a/app/services/platforms/facebook.ts b/app/services/platforms/facebook.ts index f2c8eaab609b..7e29427a4d08 100644 --- a/app/services/platforms/facebook.ts +++ b/app/services/platforms/facebook.ts @@ -2,7 +2,14 @@ import moment from 'moment'; import flatten from 'lodash/flatten'; import * as remote from '@electron/remote'; import { mutation, InheritMutations, ViewHandler } from '../core/stateful-service'; -import { IPlatformService, IGame, TPlatformCapability, IPlatformRequest, IPlatformState } from '.'; +import { + IPlatformService, + IGame, + TPlatformCapability, + IPlatformRequest, + IPlatformState, + TLiveDockFeature, +} from '.'; import { HostsService } from 'services/hosts'; import { Inject } from 'services/core/injector'; import { authorizedHeaders } from 'util/requests'; @@ -169,6 +176,12 @@ export class FacebookService 'themes', 'viewerCount', ]); + readonly liveDockFeatures = new Set([ + 'chat-streaming', + 'refresh-chat', + 'dashboard', + 'view-stream', + ]); authWindowOptions: Electron.BrowserWindowConstructorOptions = { width: 800, height: 800 }; @@ -248,7 +261,7 @@ export class FacebookService return this.state.streamPageUrl; } - get streamDashboardUrl(): string { + get dashboardUrl(): string { return this.state.streamDashboardUrl; } diff --git a/app/services/platforms/index.ts b/app/services/platforms/index.ts index 46df3c5bf5db..10154ca56aba 100644 --- a/app/services/platforms/index.ts +++ b/app/services/platforms/index.ts @@ -172,6 +172,7 @@ export interface IPlatformState { export interface IPlatformService { capabilities: Set; hasCapability(capability: T): this is TPlatformCapabilityMap[T]; + hasLiveDockFeature(feature: TLiveDockFeature): boolean; authWindowOptions: Electron.BrowserWindowConstructorOptions; @@ -218,6 +219,7 @@ export interface IPlatformService { readonly mergeUrl: string; readonly streamPageUrl: string; readonly chatUrl: string; + readonly dashboardUrl?: string; /** * the list of widgets supported by the platform @@ -241,6 +243,15 @@ export interface IUserInfo { username?: string; } +export type TLiveDockFeature = + | 'chat-offline' + | 'chat-streaming' + | 'dashboard' + | 'view-stream' + | 'refresh-chat' + | 'refresh-chat-streaming' + | 'refresh-chat-restreaming'; + export enum EPlatform { Twitch = 'twitch', YouTube = 'youtube', diff --git a/app/services/platforms/instagram.ts b/app/services/platforms/instagram.ts index aa583ea21ece..eff31cb76f78 100644 --- a/app/services/platforms/instagram.ts +++ b/app/services/platforms/instagram.ts @@ -8,6 +8,7 @@ import { TPlatformCapability, TStartStreamOptions, EPlatformCallResult, + TLiveDockFeature, } from '.'; import { BasePlatformService } from './base-platform'; import { IGoLiveSettings } from 'services/streaming'; @@ -40,6 +41,7 @@ export class InstagramService readonly platform = 'instagram'; readonly displayName = 'Instagram'; readonly capabilities = new Set(['resolutionPreset']); + readonly liveDockFeatures = new Set(); static initialState: IInstagramServiceState = { ...BasePlatformService.initialState, diff --git a/app/services/platforms/kick.ts b/app/services/platforms/kick.ts index ef9b246eefbc..6cd2e5684691 100644 --- a/app/services/platforms/kick.ts +++ b/app/services/platforms/kick.ts @@ -6,6 +6,7 @@ import { IPlatformService, IPlatformState, TPlatformCapability, + TLiveDockFeature, } from './index'; import { authorizedHeaders, jfetch } from '../../util/requests'; import { StreamError, throwStreamError } from '../streaming/stream-error'; @@ -121,6 +122,11 @@ export class KickService readonly platform = 'kick'; readonly displayName = 'Kick'; readonly capabilities = new Set(['title', 'chat', 'game']); + readonly liveDockFeatures = new Set([ + 'view-stream', + 'refresh-chat', + 'chat-streaming', + ]); authWindowOptions: Electron.BrowserWindowConstructorOptions = { width: 600, diff --git a/app/services/platforms/tiktok.ts b/app/services/platforms/tiktok.ts index 8cbfbd50fa3a..0661f2bfbf1f 100644 --- a/app/services/platforms/tiktok.ts +++ b/app/services/platforms/tiktok.ts @@ -7,6 +7,7 @@ import { IPlatformService, IPlatformState, TPlatformCapability, + TLiveDockFeature, } from './index'; import { authorizedHeaders, jfetch } from '../../util/requests'; import { @@ -118,6 +119,12 @@ export class TikTokService readonly platform = 'tiktok'; readonly displayName = 'TikTok'; readonly capabilities = new Set(['title', 'viewerCount']); + readonly liveDockFeatures = new Set([ + 'view-stream', + 'dashboard', + 'refresh-chat-restreaming', + 'chat-streaming', + ]); authWindowOptions: Electron.BrowserWindowConstructorOptions = { width: 600, diff --git a/app/services/platforms/trovo.ts b/app/services/platforms/trovo.ts index bf443c30add4..ca59ae0da858 100644 --- a/app/services/platforms/trovo.ts +++ b/app/services/platforms/trovo.ts @@ -6,6 +6,7 @@ import { IPlatformService, IPlatformState, TPlatformCapability, + TLiveDockFeature, } from './index'; import { authorizedHeaders, jfetch } from '../../util/requests'; import { throwStreamError } from '../streaming/stream-error'; @@ -62,6 +63,11 @@ export class TrovoService 'streamlabels', 'viewerCount', ]); + readonly liveDockFeatures = new Set([ + 'chat-offline', + 'refresh-chat', + 'view-stream', + ]); readonly apiBase = 'https://open-api.trovo.live/openplatform'; readonly rtmpServer = 'rtmp://livepush.trovo.live/live/'; readonly platform = 'trovo'; diff --git a/app/services/platforms/twitch.ts b/app/services/platforms/twitch.ts index d269516816e0..fc69ad9b2c89 100644 --- a/app/services/platforms/twitch.ts +++ b/app/services/platforms/twitch.ts @@ -4,6 +4,7 @@ import { IPlatformRequest, IPlatformService, IPlatformState, + TLiveDockFeature, TPlatformCapability, } from '.'; import { HostsService } from 'services/hosts'; @@ -118,6 +119,8 @@ export class TwitchService 'viewerCount', ]); + readonly liveDockFeatures = new Set(['chat-offline', 'refresh-chat']); + authWindowOptions: Electron.BrowserWindowConstructorOptions = { width: 600, height: 800, diff --git a/app/services/platforms/twitter.ts b/app/services/platforms/twitter.ts index eb39128d3655..b7353b39d157 100644 --- a/app/services/platforms/twitter.ts +++ b/app/services/platforms/twitter.ts @@ -1,6 +1,12 @@ import { InheritMutations, Inject, mutation, Service } from '../core'; import { BasePlatformService } from './base-platform'; -import { IPlatformRequest, IPlatformService, IPlatformState, TPlatformCapability } from './index'; +import { + IPlatformRequest, + IPlatformService, + IPlatformState, + TPlatformCapability, + TLiveDockFeature, +} from './index'; import { authorizedHeaders, jfetch } from '../../util/requests'; import { throwStreamError } from '../streaming/stream-error'; import { platformAuthorizedRequest } from './utils'; @@ -49,6 +55,10 @@ export class TwitterPlatformService }; readonly capabilities = new Set(['title', 'viewerCount']); + readonly liveDockFeatures = new Set([ + 'refresh-chat-streaming', + 'chat-streaming', + ]); readonly apiBase = 'https://api.x.com/2'; readonly domain = 'https://x.com'; readonly platform = 'twitter'; diff --git a/app/services/platforms/youtube.ts b/app/services/platforms/youtube.ts index ead6031b9322..061134dd8fbb 100644 --- a/app/services/platforms/youtube.ts +++ b/app/services/platforms/youtube.ts @@ -5,6 +5,7 @@ import { EPlatformCallResult, IPlatformRequest, IPlatformState, + TLiveDockFeature, } from '.'; import { Inject } from 'services/core/injector'; import { authorizedHeaders, jfetch } from 'util/requests'; @@ -187,6 +188,12 @@ export class YoutubeService 'themes', 'viewerCount', ]); + readonly liveDockFeatures = new Set([ + 'view-stream', + 'dashboard', + 'refresh-chat-streaming', + 'chat-streaming', + ]); static initialState: IYoutubeServiceState = { ...BasePlatformService.initialState, From ad65657697902295f2cf46a4199be7ca51ebece2 Mon Sep 17 00:00:00 2001 From: Sean Beyer Date: Fri, 22 Aug 2025 16:00:16 -0700 Subject: [PATCH 51/51] Fix tests --- app/services/customization.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/services/customization.ts b/app/services/customization.ts index f1b4870e36d0..2097bdf9dd51 100644 --- a/app/services/customization.ts +++ b/app/services/customization.ts @@ -13,8 +13,6 @@ import { Theme } from 'styles/antd'; export type TApplicationTheme = 'night-theme' | 'day-theme' | 'prime-dark' | 'prime-light'; -export type TApplicationTheme = 'night-theme' | 'day-theme' | 'prime-dark' | 'prime-light'; - // Maps to --background const THEME_BACKGROUNDS = { 'night-theme': { r: 23, g: 36, b: 45 },