chore(release): gate users-branch features for hotfix
Add USERS_BRANCH_FEATURES_ENABLED (off by default) to hide Players library, session tokens, and related UI while keeping the preview import fix. Restore primary Run button when the flag is off. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+50
-15
@@ -8,6 +8,7 @@ import { installStdoutEpipeGuards } from './safeConsole';
|
||||
installStdoutEpipeGuards();
|
||||
|
||||
import { openDialogFilterLabel } from '../shared/appBranding';
|
||||
import { USERS_BRANCH_FEATURES_ENABLED } from '../shared/features/usersBranchFeatures';
|
||||
import { ipcChannels, type ScenePreviewImportEvent, type SessionState } from '../shared/ipc/contracts';
|
||||
import {
|
||||
PROJECT_ZIP_OPEN_DIALOG_FILTER,
|
||||
@@ -402,8 +403,10 @@ async function main() {
|
||||
const licenseService = new LicenseService(app.getPath('userData'));
|
||||
tokensStore = new TokensStore(app.getPath('userData'));
|
||||
await tokensStore.ensureLoaded();
|
||||
playersStore = new PlayersStore(app.getPath('userData'));
|
||||
await playersStore.ensureLoaded();
|
||||
if (USERS_BRANCH_FEATURES_ENABLED) {
|
||||
playersStore = new PlayersStore(app.getPath('userData'));
|
||||
await playersStore.ensureLoaded();
|
||||
}
|
||||
setLicenseAssert(() => {
|
||||
licenseService.assertForIpc();
|
||||
});
|
||||
@@ -429,9 +432,11 @@ async function main() {
|
||||
sceneTokensSessionStore.reset();
|
||||
sceneNpcTokensSessionStore.reset();
|
||||
scenePlayerTokensSessionStore.reset();
|
||||
const playerIds = Array.isArray(req?.playerIds) ? req.playerIds.map(String).filter(Boolean) : [];
|
||||
if (playerIds.length > 0) {
|
||||
scenePlayerTokensSessionStore.dispatch({ kind: 'setSelection', playerIds });
|
||||
if (USERS_BRANCH_FEATURES_ENABLED) {
|
||||
const playerIds = Array.isArray(req?.playerIds) ? req.playerIds.map(String).filter(Boolean) : [];
|
||||
if (playerIds.length > 0) {
|
||||
scenePlayerTokensSessionStore.dispatch({ kind: 'setSelection', playerIds });
|
||||
}
|
||||
}
|
||||
effectsStore.dispatch({ kind: 'tool.set', tool: effectsDefaultTool() });
|
||||
openMultiWindow();
|
||||
@@ -1357,13 +1362,19 @@ async function main() {
|
||||
});
|
||||
|
||||
registerHandler(ipcChannels.players.list, async () => {
|
||||
await playersStore!.ensureLoaded();
|
||||
return { players: playersStore!.listPlayers(), teams: playersStore!.listTeams() };
|
||||
if (!USERS_BRANCH_FEATURES_ENABLED || !playersStore) {
|
||||
return { players: [], teams: [] };
|
||||
}
|
||||
await playersStore.ensureLoaded();
|
||||
return { players: playersStore.listPlayers(), teams: playersStore.listTeams() };
|
||||
});
|
||||
registerHandler(
|
||||
ipcChannels.players.upsert,
|
||||
async ({ id, name, filePath, teamId, ringColor, imageOffset, imageScale }) => {
|
||||
const player = await playersStore!.upsert(
|
||||
if (!USERS_BRANCH_FEATURES_ENABLED || !playersStore) {
|
||||
throw new Error('Players library is disabled in this build');
|
||||
}
|
||||
const player = await playersStore.upsert(
|
||||
{
|
||||
name,
|
||||
...(id !== undefined ? { id } : {}),
|
||||
@@ -1380,17 +1391,26 @@ async function main() {
|
||||
},
|
||||
);
|
||||
registerHandler(ipcChannels.players.delete, async ({ id }) => {
|
||||
await playersStore!.delete(id);
|
||||
if (!USERS_BRANCH_FEATURES_ENABLED || !playersStore) {
|
||||
return { ok: true };
|
||||
}
|
||||
await playersStore.delete(id);
|
||||
emitPlayersState();
|
||||
return { ok: true };
|
||||
});
|
||||
registerHandler(ipcChannels.players.setOrder, async ({ playerIds }) => {
|
||||
const players = await playersStore!.setPlayersOrder(playerIds);
|
||||
if (!USERS_BRANCH_FEATURES_ENABLED || !playersStore) {
|
||||
return { players: [] };
|
||||
}
|
||||
const players = await playersStore.setPlayersOrder(playerIds);
|
||||
emitPlayersState();
|
||||
return { players };
|
||||
});
|
||||
registerHandler(ipcChannels.players.upsertTeam, async ({ id, name, color }) => {
|
||||
const team = await playersStore!.upsertTeam({
|
||||
if (!USERS_BRANCH_FEATURES_ENABLED || !playersStore) {
|
||||
throw new Error('Players library is disabled in this build');
|
||||
}
|
||||
const team = await playersStore.upsertTeam({
|
||||
name,
|
||||
...(id !== undefined ? { id } : {}),
|
||||
...(color !== undefined ? { color } : {}),
|
||||
@@ -1399,21 +1419,33 @@ async function main() {
|
||||
return { team };
|
||||
});
|
||||
registerHandler(ipcChannels.players.deleteTeam, async ({ id }) => {
|
||||
await playersStore!.deleteTeam(id);
|
||||
if (!USERS_BRANCH_FEATURES_ENABLED || !playersStore) {
|
||||
return { ok: true };
|
||||
}
|
||||
await playersStore.deleteTeam(id);
|
||||
emitPlayersState();
|
||||
return { ok: true };
|
||||
});
|
||||
registerHandler(ipcChannels.players.setTeamsOrder, async ({ teamIds }) => {
|
||||
const teams = await playersStore!.setTeamsOrder(teamIds);
|
||||
if (!USERS_BRANCH_FEATURES_ENABLED || !playersStore) {
|
||||
return { teams: [] };
|
||||
}
|
||||
const teams = await playersStore.setTeamsOrder(teamIds);
|
||||
emitPlayersState();
|
||||
return { teams };
|
||||
});
|
||||
registerHandler(ipcChannels.players.assignTeam, async ({ playerId, teamId }) => {
|
||||
const player = await playersStore!.assignPlayerTeam(playerId, teamId);
|
||||
if (!USERS_BRANCH_FEATURES_ENABLED || !playersStore) {
|
||||
throw new Error('Players library is disabled in this build');
|
||||
}
|
||||
const player = await playersStore.assignPlayerTeam(playerId, teamId);
|
||||
emitPlayersState();
|
||||
return { player };
|
||||
});
|
||||
registerHandler(ipcChannels.players.pickImage, async () => {
|
||||
if (!USERS_BRANCH_FEATURES_ENABLED || !playersStore) {
|
||||
return { canceled: true as const };
|
||||
}
|
||||
const { canceled, filePaths } = await dialog.showOpenDialog({
|
||||
properties: ['openFile'],
|
||||
filters: [
|
||||
@@ -1432,7 +1464,10 @@ async function main() {
|
||||
return { canceled: false as const, filePath, previewDataUrl };
|
||||
});
|
||||
registerHandler(ipcChannels.players.imageUrl, ({ id }) => {
|
||||
return { url: playersStore!.getImageUrl(id) };
|
||||
if (!USERS_BRANCH_FEATURES_ENABLED || !playersStore) {
|
||||
return { url: null };
|
||||
}
|
||||
return { url: playersStore.getImageUrl(id) };
|
||||
});
|
||||
registerHandler(ipcChannels.sceneNpcTokensSession.getState, () => {
|
||||
return { state: sceneNpcTokensSessionStore.getState() };
|
||||
|
||||
@@ -81,7 +81,7 @@ async function serveFile(info: ReadInfo, request: Request): Promise<Response> {
|
||||
export function registerDndAssetProtocol(
|
||||
projectStore: ZipProjectStore,
|
||||
tokensStore: TokensStore,
|
||||
playersStore: PlayersStore,
|
||||
playersStore: PlayersStore | null,
|
||||
): void {
|
||||
session.defaultSession.protocol.handle('dnd', async (request) => {
|
||||
const url = new URL(request.url);
|
||||
@@ -95,7 +95,7 @@ export function registerDndAssetProtocol(
|
||||
} else if (url.hostname === 'token') {
|
||||
info = tokensStore.getImageReadInfo(asTokenId(id));
|
||||
} else if (url.hostname === 'player') {
|
||||
info = playersStore.getImageReadInfo(asPlayerId(id));
|
||||
info = playersStore?.getImageReadInfo(asPlayerId(id)) ?? null;
|
||||
}
|
||||
if (!info) {
|
||||
return new Response(null, { status: 404 });
|
||||
|
||||
@@ -5,6 +5,7 @@ import { pickEraseTargetId } from '../../shared/effectEraserHitTest';
|
||||
import { fitAspectRect } from '../../shared/geometry/fitAspectRect';
|
||||
import { ipcChannels } from '../../shared/ipc/contracts';
|
||||
import type { SessionState } from '../../shared/ipc/contracts';
|
||||
import { USERS_BRANCH_FEATURES_ENABLED } from '../../shared/features/usersBranchFeatures';
|
||||
import {
|
||||
isNodeInMainStoryline,
|
||||
isNodeInSideStoryline,
|
||||
@@ -1778,6 +1779,8 @@ export function ControlApp() {
|
||||
<div className={styles.previewHeader}>
|
||||
<div className={styles.previewTitle}>{t('control.screenPreview')}</div>
|
||||
<div className={styles.previewActions}>
|
||||
{USERS_BRANCH_FEATURES_ENABLED ? (
|
||||
<>
|
||||
<label className={styles.npcTokenScale}>
|
||||
<span className={styles.npcTokenScaleLabel}>{t('control.npcTokenScale')}</span>
|
||||
<input
|
||||
@@ -1816,6 +1819,8 @@ export function ControlApp() {
|
||||
? t('control.hidePlayers')
|
||||
: t('control.showPlayers')}
|
||||
</Button>
|
||||
) : null}
|
||||
</>
|
||||
) : null}
|
||||
<Button onClick={() => void api.invoke(ipcChannels.windows.closeMultiWindow, {})}>
|
||||
{t('control.stopPresentation')}
|
||||
@@ -2026,7 +2031,7 @@ export function ControlApp() {
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
{previewContentRect ? (
|
||||
{USERS_BRANCH_FEATURES_ENABLED && previewContentRect ? (
|
||||
<SceneNpcTokensOverlay
|
||||
placements={currentScene?.npcTokens ?? []}
|
||||
library={session?.project?.npcs ?? []}
|
||||
@@ -2046,7 +2051,7 @@ export function ControlApp() {
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
{previewContentRect ? (
|
||||
{USERS_BRANCH_FEATURES_ENABLED && previewContentRect ? (
|
||||
<ScenePlayerTokensOverlay
|
||||
library={appPlayers}
|
||||
session={scenePlayerTokensSession}
|
||||
@@ -2509,7 +2514,7 @@ export function ControlApp() {
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
{npcSessionCtxMenu
|
||||
{USERS_BRANCH_FEATURES_ENABLED && npcSessionCtxMenu
|
||||
? createPortal(
|
||||
<>
|
||||
<button
|
||||
|
||||
@@ -2,6 +2,7 @@ import React, { startTransition, useCallback, useEffect, useMemo, useRef, useSta
|
||||
import { createPortal } from 'react-dom';
|
||||
|
||||
import { moveSceneInListOrder, reconcileSceneListOrder } from '../../shared/graph/sceneListOrder';
|
||||
import { USERS_BRANCH_FEATURES_ENABLED } from '../../shared/features/usersBranchFeatures';
|
||||
import type {
|
||||
NpcImportResolution,
|
||||
SceneImportResolution,
|
||||
@@ -841,6 +842,7 @@ export function EditorApp() {
|
||||
{t('top.file')}
|
||||
</button>
|
||||
) : null}
|
||||
{USERS_BRANCH_FEATURES_ENABLED ? (
|
||||
<button
|
||||
type="button"
|
||||
data-testid="players-header-btn"
|
||||
@@ -858,6 +860,7 @@ export function EditorApp() {
|
||||
>
|
||||
{t('top.players')}
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
<div className={styles.flex1} />
|
||||
{appVersionText ? (
|
||||
@@ -868,6 +871,7 @@ export function EditorApp() {
|
||||
<div className={styles.headerActions}>
|
||||
{state.project ? (
|
||||
<>
|
||||
{USERS_BRANCH_FEATURES_ENABLED ? (
|
||||
<div
|
||||
ref={runSplitRef}
|
||||
className={styles.splitRun}
|
||||
@@ -917,6 +921,19 @@ export function EditorApp() {
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<Button
|
||||
variant="primary"
|
||||
disabled={runDisabled || launching}
|
||||
data-testid="run-main-btn"
|
||||
onClick={() => {
|
||||
if (!licenseActive || !graphStartGraphNodeId || launching) return;
|
||||
launchFromGraphNode(graphStartGraphNodeId);
|
||||
}}
|
||||
>
|
||||
{t('top.run')}
|
||||
</Button>
|
||||
)}
|
||||
{runDisabled ? (
|
||||
<Button
|
||||
variant="ghost"
|
||||
@@ -1620,16 +1637,20 @@ export function EditorApp() {
|
||||
}}
|
||||
/>
|
||||
<CheckUpdatesModal open={checkUpdatesOpen} onClose={() => setCheckUpdatesOpen(false)} />
|
||||
<PlayersManagerModal open={playersManagerOpen} onClose={() => setPlayersManagerOpen(false)} />
|
||||
<LaunchPlayersModal
|
||||
open={launchPlayersOpen}
|
||||
onClose={() => setLaunchPlayersOpen(false)}
|
||||
onConfirm={(playerIds) => {
|
||||
if (!graphStartGraphNodeId) return;
|
||||
launchFromGraphNode(graphStartGraphNodeId, playerIds);
|
||||
}}
|
||||
/>
|
||||
{runMenuOpen && runMenuPos
|
||||
{USERS_BRANCH_FEATURES_ENABLED ? (
|
||||
<>
|
||||
<PlayersManagerModal open={playersManagerOpen} onClose={() => setPlayersManagerOpen(false)} />
|
||||
<LaunchPlayersModal
|
||||
open={launchPlayersOpen}
|
||||
onClose={() => setLaunchPlayersOpen(false)}
|
||||
onConfirm={(playerIds) => {
|
||||
if (!graphStartGraphNodeId) return;
|
||||
launchFromGraphNode(graphStartGraphNodeId, playerIds);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
) : null}
|
||||
{USERS_BRANCH_FEATURES_ENABLED && runMenuOpen && runMenuPos
|
||||
? createPortal(
|
||||
<div
|
||||
data-runmenu-root="1"
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
/** Порядок разделов в окне «Инструкция». */
|
||||
export const HELP_SECTION_IDS = [
|
||||
import { USERS_BRANCH_FEATURES_ENABLED } from '../../../shared/features/usersBranchFeatures';
|
||||
|
||||
const HELP_SECTION_IDS_ALL = [
|
||||
'overview',
|
||||
'license',
|
||||
'projects',
|
||||
@@ -25,7 +27,11 @@ export const HELP_SECTION_IDS = [
|
||||
'settings',
|
||||
] as const;
|
||||
|
||||
export type HelpSectionId = (typeof HELP_SECTION_IDS)[number];
|
||||
export type HelpSectionId = (typeof HELP_SECTION_IDS_ALL)[number];
|
||||
|
||||
export const HELP_SECTION_IDS: readonly HelpSectionId[] = USERS_BRANCH_FEATURES_ENABLED
|
||||
? HELP_SECTION_IDS_ALL
|
||||
: HELP_SECTION_IDS_ALL.filter((id) => id !== 'players');
|
||||
|
||||
export function helpSectionTitleKey(id: HelpSectionId): string {
|
||||
return `help.section.${id}.title`;
|
||||
|
||||
@@ -14,6 +14,7 @@ import type {
|
||||
import editorStyles from '../editor/EditorApp.module.css';
|
||||
import { useEditorI18n } from '../editor/i18n/EditorI18nContext';
|
||||
import { getDndApi } from '../shared/dndApi';
|
||||
import { USERS_BRANCH_FEATURES_ENABLED } from '../../shared/features/usersBranchFeatures';
|
||||
import { PlayerTokenView } from '../shared/playerToken/PlayerTokenView';
|
||||
import { Button, Input, Select } from '../shared/ui/controls';
|
||||
import { useAssetUrl } from '../shared/useAssetImageUrl';
|
||||
@@ -737,27 +738,35 @@ export function NpcsEditorApp() {
|
||||
<div>
|
||||
<div className={styles.fieldLabel}>{t('npcs.avatar')}</div>
|
||||
<div className={styles.avatarPick}>
|
||||
<PlayerTokenView
|
||||
name={selected.name}
|
||||
imageUrl={selectedUrl}
|
||||
ringColor={npcDispositionRingColor(selected.disposition ?? 'neutral')}
|
||||
imageOffset={selected.imageOffset}
|
||||
imageScale={selected.imageScale}
|
||||
sizePx={140}
|
||||
panEnabled
|
||||
onImageOffsetChange={(imageOffset) => {
|
||||
void api.invoke(ipcChannels.project.updateNpcFields, {
|
||||
npcId: selected.id,
|
||||
imageOffset,
|
||||
});
|
||||
}}
|
||||
onImageScaleChange={(imageScale) => {
|
||||
void api.invoke(ipcChannels.project.updateNpcFields, {
|
||||
npcId: selected.id,
|
||||
imageScale,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
{USERS_BRANCH_FEATURES_ENABLED ? (
|
||||
<PlayerTokenView
|
||||
name={selected.name}
|
||||
imageUrl={selectedUrl}
|
||||
ringColor={npcDispositionRingColor(selected.disposition ?? 'neutral')}
|
||||
imageOffset={selected.imageOffset}
|
||||
imageScale={selected.imageScale}
|
||||
sizePx={140}
|
||||
panEnabled
|
||||
onImageOffsetChange={(imageOffset) => {
|
||||
void api.invoke(ipcChannels.project.updateNpcFields, {
|
||||
npcId: selected.id,
|
||||
imageOffset,
|
||||
});
|
||||
}}
|
||||
onImageScaleChange={(imageScale) => {
|
||||
void api.invoke(ipcChannels.project.updateNpcFields, {
|
||||
npcId: selected.id,
|
||||
imageScale,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<div className={styles.avatarPreview}>
|
||||
{selectedUrl ? (
|
||||
<img className={styles.avatarPreviewImg} src={selectedUrl} alt="" />
|
||||
) : null}
|
||||
</div>
|
||||
)}
|
||||
<Button
|
||||
disabled={avatarBusy}
|
||||
onClick={() => {
|
||||
@@ -782,6 +791,7 @@ export function NpcsEditorApp() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{USERS_BRANCH_FEATURES_ENABLED ? (
|
||||
<label>
|
||||
<div className={styles.fieldLabel}>{t('npcs.disposition')}</div>
|
||||
<Select
|
||||
@@ -800,6 +810,7 @@ export function NpcsEditorApp() {
|
||||
]}
|
||||
/>
|
||||
</label>
|
||||
) : null}
|
||||
|
||||
<div>
|
||||
<div className={styles.fieldLabel}>{t('npcs.name')}</div>
|
||||
|
||||
@@ -44,6 +44,7 @@ import {
|
||||
trapTypeLabelRu,
|
||||
} from '../../shared/types/sceneTraps';
|
||||
import { sceneViewPanBy, sceneViewZoomAt } from '../../shared/types/sceneView';
|
||||
import { USERS_BRANCH_FEATURES_ENABLED } from '../../shared/features/usersBranchFeatures';
|
||||
import editorStyles from '../editor/EditorApp.module.css';
|
||||
import { useEditorI18n } from '../editor/i18n/EditorI18nContext';
|
||||
import { getDndApi } from '../shared/dndApi';
|
||||
@@ -484,7 +485,7 @@ export function SceneEditorApp() {
|
||||
return;
|
||||
}
|
||||
const npcId = e.dataTransfer.getData(SCENE_NPC_DND_MIME);
|
||||
if (npcId) {
|
||||
if (USERS_BRANCH_FEATURES_ENABLED && npcId) {
|
||||
addNpcTokenAt(npcId as NpcId, p.x, p.y);
|
||||
return;
|
||||
}
|
||||
@@ -600,6 +601,7 @@ export function SceneEditorApp() {
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
{USERS_BRANCH_FEATURES_ENABLED ? (
|
||||
<div className={styles.accordion} data-testid="scene-npc-accordion">
|
||||
<button type="button" className={styles.accordionHead} onClick={() => setNpcsOpen((v) => !v)}>
|
||||
НПС {npcsOpen ? '▾' : '▸'}
|
||||
@@ -621,6 +623,7 @@ export function SceneEditorApp() {
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<div className={styles.accordion}>
|
||||
<button type="button" className={styles.accordionHead} onClick={() => setTokensOpen((v) => !v)}>
|
||||
@@ -884,7 +887,7 @@ export function SceneEditorApp() {
|
||||
);
|
||||
})
|
||||
: null}
|
||||
{contentRect
|
||||
{USERS_BRANCH_FEATURES_ENABLED && contentRect
|
||||
? localNpcTokens.map((tok) => {
|
||||
const npc = project?.npcs.find((item) => item.id === tok.npcId);
|
||||
if (!npc) return null;
|
||||
@@ -1050,7 +1053,7 @@ export function SceneEditorApp() {
|
||||
)
|
||||
: null}
|
||||
|
||||
{npcCtxMenu
|
||||
{USERS_BRANCH_FEATURES_ENABLED && npcCtxMenu
|
||||
? createPortal(
|
||||
<>
|
||||
<button
|
||||
|
||||
@@ -2,6 +2,7 @@ import React, { useEffect, useRef, useState } from 'react';
|
||||
|
||||
import { computeTimeSec } from '../../main/video/videoPlaybackStore';
|
||||
import type { SessionState } from '../../shared/ipc/contracts';
|
||||
import { USERS_BRANCH_FEATURES_ENABLED } from '../../shared/features/usersBranchFeatures';
|
||||
|
||||
import { ExplosionVideoOverlay } from './effects/ExplosionVideoOverlay';
|
||||
import { PixiEffectsOverlay } from './effects/PxiEffectsOverlay';
|
||||
@@ -54,7 +55,8 @@ export function PresentationView({
|
||||
const [materialsOverlay] = useMaterialsOverlayState();
|
||||
const [npcsOverlay] = useNpcsOverlayState();
|
||||
const appTokens = useAppTokens();
|
||||
const { players: appPlayers } = useAppPlayers();
|
||||
const appPlayersResult = useAppPlayers();
|
||||
const appPlayers = USERS_BRANCH_FEATURES_ENABLED ? appPlayersResult.players : [];
|
||||
const [sceneTokensSession] = useSceneTokensSession();
|
||||
const [sceneNpcTokensSession] = useSceneNpcTokensSession();
|
||||
const [scenePlayerTokensSession] = useScenePlayerTokensSession();
|
||||
@@ -196,7 +198,7 @@ export function PresentationView({
|
||||
viewport={contentRect}
|
||||
/>
|
||||
) : null}
|
||||
{scene?.previewAssetType === 'image' && contentRect ? (
|
||||
{USERS_BRANCH_FEATURES_ENABLED && scene?.previewAssetType === 'image' && contentRect ? (
|
||||
<SceneNpcTokensOverlay
|
||||
placements={scene.npcTokens ?? []}
|
||||
library={project?.npcs ?? []}
|
||||
@@ -205,7 +207,7 @@ export function PresentationView({
|
||||
grid={scene.grid}
|
||||
/>
|
||||
) : null}
|
||||
{scene?.previewAssetType === 'image' && contentRect ? (
|
||||
{USERS_BRANCH_FEATURES_ENABLED && scene?.previewAssetType === 'image' && contentRect ? (
|
||||
<ScenePlayerTokensOverlay
|
||||
library={appPlayers}
|
||||
session={scenePlayerTokensSession}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
/**
|
||||
* Hotfix release: set to `false` to hide everything merged from branch `users`
|
||||
* (Players library, run-with-players, circular NPC/player session tokens, disposition UI).
|
||||
* Re-enable after QA.
|
||||
*/
|
||||
export const USERS_BRANCH_FEATURES_ENABLED = false;
|
||||
Reference in New Issue
Block a user