Skip to content
Permalink

Comparing changes

This is a direct comparison between two commits made in this repository or its related repositories. View the default comparison for this range or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: stream-labs/desktop
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 64113c8d407c361ab56c95b69839dcb0dd82496b
Choose a base ref
..
head repository: stream-labs/desktop
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: f0c03eedd58b24e219d8b53e6a69d7ef1bdb3afd
Choose a head ref
Showing 361 changed files with 7,672 additions and 1,357 deletions.
4 changes: 4 additions & 0 deletions app/app.g.less
Original file line number Diff line number Diff line change
@@ -23,6 +23,10 @@
user-select: text;
}

::selection {
background: var(--button) !important;
}

.main {
background: var(--background);
color: var(--paragraph);
27 changes: 26 additions & 1 deletion app/components-react/editor/elements/SceneSelector.m.less
Original file line number Diff line number Diff line change
@@ -27,6 +27,10 @@
cursor: not-allowed;
}
}

:global(.icon-add-circle) {
margin-right: auto;
}
}

:global(.no-top-padding) {
@@ -37,7 +41,7 @@
}

.active-scene-container {
margin-right: auto;
margin-right: 4px;
font-weight: 500;
font-size: 16px;
color: var(--title);
@@ -80,6 +84,11 @@
display: none;
}

:global(.ant-tree-title) {
display: block;
width: 100%;
}

:global(.ant-tree-treenode), :global(.ant-tree-node-content-wrapper) {
width: 100%;
transition: all 0s !important;
@@ -154,6 +163,22 @@

> i {
margin-right: 8px;
opacity: 0;
}

:global(.fa-signal) {
opacity: 1;
color: var(--teal);
}

:global(.icon-lock), :global(.icon-hide), :global(.icon-broadcast), :global(.icon-studio) {
opacity: 0.7;
}

&:hover {
i {
opacity: 1;
}
}
}

65 changes: 35 additions & 30 deletions app/components-react/editor/elements/SceneSelector.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import React, { useState, useRef } from 'react';
import Fuse from 'fuse.js';
import cx from 'classnames';
import { Dropdown, Tooltip, Tree } from 'antd';
import { DownOutlined } from '@ant-design/icons';
@@ -8,7 +7,6 @@ import { Menu } from 'util/menus/Menu';
import { getOS } from 'util/operating-systems';
import { Services } from 'components-react/service-provider';
import { useVuex } from 'components-react/hooks';
import { TextInput } from 'components-react/shared/inputs';
import HelpTip from 'components-react/shared/HelpTip';
import Scrollable from 'components-react/shared/Scrollable';
import { useTree, IOnDropInfo } from 'components-react/hooks/useTree';
@@ -17,6 +15,7 @@ import { EDismissable } from 'services/dismissables';
import { ERenderingMode } from '../../../../obs-api';
import styles from './SceneSelector.m.less';
import useBaseElement from './hooks';
import { IScene } from 'services/scenes';

function SceneSelector() {
const {
@@ -30,15 +29,15 @@ function SceneSelector() {

const { treeSort } = useTree(true);

const [searchQuery, setSearchQuery] = useState('');
const [showDropdown, setShowDropdown] = useState(false);
const { scenes, activeSceneId, collections, activeCollection } = useVuex(() => ({
const { scenes, activeSceneId, activeScene, collections, activeCollection } = useVuex(() => ({
scenes: ScenesService.views.scenes.map(scene => ({
title: scene.name,
title: <TreeNode scene={scene} removeScene={removeScene} />,
key: scene.id,
selectable: true,
isLeaf: true,
})),
activeScene: ScenesService.views.activeScene,
activeSceneId: ScenesService.views.activeSceneId,
activeCollection: SceneCollectionsService.activeCollection,
collections: SceneCollectionsService.collections,
@@ -58,7 +57,7 @@ function SceneSelector() {
});
menu.append({
label: $t('Remove'),
click: removeScene,
click: () => removeScene(activeScene),
});
menu.append({
label: $t('Filters'),
@@ -85,8 +84,17 @@ function SceneSelector() {
ScenesService.actions.showNameScene();
}

function removeScene() {
const name = ScenesService.views.activeScene?.name;
function showTransitions() {
TransitionsService.actions.showSceneTransitions();
}

function manageCollections() {
SceneCollectionsService.actions.showManageWindow();
}

function removeScene(scene: IScene | null) {
if (!scene) return;
const name = scene.name;
remote.dialog
.showMessageBox(remote.getCurrentWindow(), {
title: 'Streamlabs Desktop',
@@ -104,31 +112,17 @@ function SceneSelector() {
return;
}

EditorCommandsService.actions.executeCommand('RemoveSceneCommand', activeSceneId);
EditorCommandsService.actions.executeCommand('RemoveSceneCommand', scene.id);
});
}

function showTransitions() {
TransitionsService.actions.showSceneTransitions();
}

function manageCollections() {
SceneCollectionsService.actions.showManageWindow();
}

function loadCollection(id: string) {
if (SceneCollectionsService.getCollection(id)?.operatingSystem !== getOS()) return;

SceneCollectionsService.actions.load(id);
setShowDropdown(false);
}

function filteredCollections() {
if (!searchQuery) return collections;
const fuse = new Fuse(collections, { shouldSort: true, keys: ['name'] });
return fuse.search(searchQuery);
}

const DropdownMenu = (
<div className={cx(styles.dropdownContainer, 'react')}>
<div className={styles.dropdownItem} onClick={manageCollections} style={{ marginTop: '6px' }}>
@@ -138,7 +132,7 @@ function SceneSelector() {
<hr style={{ borderColor: 'var(--border)' }} />
<span className={styles.whisper}>{$t('Your Scene Collections')}</span>
<Scrollable style={{ height: 'calc(100% - 60px)' }}>
{filteredCollections().map(collection => (
{collections.map(collection => (
<div
key={collection.id}
onClick={() => loadCollection(collection.id)}
@@ -172,16 +166,14 @@ function SceneSelector() {
placement="bottomLeft"
>
<span className={styles.activeSceneContainer} data-name="SceneSelectorDropdown">
<DownOutlined style={{ marginRight: '4px' }} />
<span className={styles.activeScene}>{activeCollection?.name}</span>
<DownOutlined style={{ marginLeft: '4px' }} />
</span>
</Dropdown>
<Tooltip title={$t('Add a new Scene.')} placement="bottomRight">
<i className="icon-add icon-button icon-button--lg" onClick={addScene} />
</Tooltip>
<Tooltip title={$t('Remove Scene.')} placement="bottomRight">
<i className="icon-subtract icon-button icon-button--lg" onClick={removeScene} />
<Tooltip title={$t('Add a new Scene.')} placement="bottomLeft">
<i className="icon-add-circle icon-button icon-button--lg" onClick={addScene} />
</Tooltip>

<Tooltip title={$t('Edit Scene Transitions.')} placement="bottomRight">
<i className="icon-transition icon-button icon-button--lg" onClick={showTransitions} />
</Tooltip>
@@ -211,6 +203,19 @@ function SceneSelector() {
);
}

function TreeNode(p: { scene: IScene; removeScene: (scene: IScene) => void }) {
const { ScenesService, EditorCommandsService } = Services;

return (
<div className={styles.sourceTitleContainer} data-name={p.scene.name} data-role="scene">
<span className={styles.sourceTitle}>{p.scene.name}</span>
<Tooltip title={$t('Remove Scene.')} placement="left">
<i onClick={() => p.removeScene(p.scene)} className="icon-trash" />
</Tooltip>
</div>
);
}

export default function SceneSelectorElement() {
const containerRef = useRef<HTMLDivElement>(null);
const { renderElement } = useBaseElement(
81 changes: 39 additions & 42 deletions app/components-react/editor/elements/SourceSelector.tsx
Original file line number Diff line number Diff line change
@@ -49,7 +49,6 @@ class SourceSelectorModule {

sourcesTooltip = $t('The building blocks of your scene. Also contains widgets.');
addSourceTooltip = $t('Add a new Source to your Scene. Includes widgets.');
removeSourcesTooltip = $t('Remove Sources from your Scene.');
openSourcePropertiesTooltip = $t('Open the Source Properties.');
addGroupTooltip = $t('Add a Group so you can move multiple Sources at the same time.');

@@ -89,6 +88,8 @@ class SourceSelectorModule {
cycleSelectiveRecording={() => this.cycleSelectiveRecording(sceneNode.id)}
ref={this.nodeRefs[sceneNode.id]}
onDoubleClick={() => this.sourceProperties(sceneNode.id)}
removeSource={() => this.removeItems(sceneNode.id)}
sourceProperties={() => this.sourceProperties(sceneNode.id)}
/>
),
isLeaf: !children,
@@ -112,7 +113,7 @@ class SourceSelectorModule {
const isGuestCamActive = itemsForNode.some(i => {
return (
this.sourcesService.state.sources[i.sourceId].type === 'mediasoupconnector' &&
this.guestCamService.state.guestInfo
!!this.guestCamService.views.getGuestBySourceId(i.sourceId)
);
});

@@ -214,7 +215,10 @@ class SourceSelectorModule {
event && event.stopPropagation();
}

removeItems() {
async removeItems(id?: string) {
if (id) {
await this.selectionService.actions.return.select([id]);
}
this.selectionService.views.globalSelection.remove();
}

@@ -242,16 +246,6 @@ class SourceSelectorModule {
this.sourcesService.actions.showSourceProperties(item.sourceId);
}

canShowProperties(): boolean {
if (this.activeItemIds.length === 0) return false;
const sceneNode = this.scene.state.nodes.find(
n => n.id === this.selectionService.state.lastSelectedId,
);
return !!(sceneNode && sceneNode.sceneNodeType === 'item'
? this.sourcesService.views.getSource(sceneNode.sourceId)?.hasProps()
: false);
}

determinePlacement(info: Parameters<Required<TreeProps>['onDrop']>[0]) {
if (!info.dropToGap && !info.node.isLeaf) return EPlaceType.Inside;
const dropPos = info.node.pos.split('-');
@@ -439,17 +433,11 @@ function StudioControls() {
sourcesTooltip,
addGroupTooltip,
addSourceTooltip,
removeSourcesTooltip,
openSourcePropertiesTooltip,
selectiveRecordingEnabled,
selectiveRecordingLocked,
activeItemIds,
addSource,
addFolder,
removeItems,
toggleSelectiveRecording,
canShowProperties,
sourceProperties,
} = useModule(SourceSelectorModule);

return (
@@ -459,6 +447,9 @@ function StudioControls() {
<span className={styles.sourcesHeader}>{$t('Sources')}</span>
</Tooltip>
</div>
<Tooltip title={addSourceTooltip} placement="bottomLeft">
<i className="icon-add-circle icon-button icon-button--lg" onClick={addSource} />
</Tooltip>

<Tooltip title={$t('Toggle Selective Recording')} placement="bottomRight">
<i
@@ -469,31 +460,9 @@ function StudioControls() {
onClick={toggleSelectiveRecording}
/>
</Tooltip>

<Tooltip title={addGroupTooltip} placement="bottomRight">
<i className="icon-add-folder icon-button icon-button--lg" onClick={addFolder} />
</Tooltip>

<Tooltip title={addSourceTooltip} placement="bottomRight">
<i className="icon-add icon-button icon-button--lg" onClick={addSource} />
</Tooltip>

<Tooltip title={removeSourcesTooltip} placement="bottomRight">
<i
className={cx({
'icon-subtract icon-button icon-button--lg': true,
disabled: activeItemIds.length === 0,
})}
onClick={removeItems}
/>
</Tooltip>

<Tooltip title={openSourcePropertiesTooltip} placement="bottomRight">
<i
className={cx({ disabled: !canShowProperties(), 'icon-settings icon-button': true })}
onClick={() => sourceProperties(activeItemIds[0])}
/>
</Tooltip>
</div>
);
}
@@ -571,6 +540,8 @@ const TreeNode = React.forwardRef(
toggleLock: (ev: unknown) => unknown;
cycleSelectiveRecording: (ev: unknown) => void;
onDoubleClick: () => void;
removeSource: () => void;
sourceProperties: () => void;
},
ref: React.RefObject<HTMLDivElement>,
) => {
@@ -583,6 +554,8 @@ const TreeNode = React.forwardRef(
: { icon: 'icon-studio', tooltip: $t('Only visible on Recording') };
}

const [hoveredIcon, setHoveredIcon] = useState('');

return (
<div
className={styles.sourceTitleContainer}
@@ -594,7 +567,7 @@ const TreeNode = React.forwardRef(
<span className={styles.sourceTitle}>{p.title}</span>
{p.canShowActions && (
<>
{p.isGuestCamActive && <i className="fa fa-signal" style={{ color: 'var(--teal)' }} />}
{p.isGuestCamActive && <i className="fa fa-signal" />}
{p.selectiveRecordingEnabled && (
<Tooltip title={selectiveRecordingMetadata().tooltip} placement="left">
<i
@@ -607,6 +580,30 @@ const TreeNode = React.forwardRef(
<i onClick={p.toggleVisibility} className={p.isVisible ? 'icon-view' : 'icon-hide'} />
</>
)}
<Tooltip
title={$t('Remove Sources from your Scene.')}
placement="left"
visible={hoveredIcon === 'icon-trash'}
>
<i
onClick={p.removeSource}
className="icon-trash"
onMouseEnter={() => setHoveredIcon('icon-trash')}
onMouseLeave={() => setHoveredIcon('')}
/>
</Tooltip>
<Tooltip
title={$t('Open the Source Properties.')}
placement="left"
visible={hoveredIcon === 'icon-settings'}
>
<i
onClick={p.sourceProperties}
className="icon-settings"
onMouseEnter={() => setHoveredIcon('icon-settings')}
onMouseLeave={() => setHoveredIcon('')}
/>
</Tooltip>
</div>
);
},
Loading