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:
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user