fix: control UX — no default tool, launch loader, NPC layout
Deselect effect tools by default and on re-click, block the editor during session launch, and fix NPC overlay/window empty-state layout. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -23,7 +23,7 @@ import {
|
||||
PixiEffectsOverlay,
|
||||
type PixiEffectsOverlayHandle,
|
||||
} from '../shared/effects/PxiEffectsOverlay';
|
||||
import type { EffectInstance, ExplosionInstance } from '../../shared/types/effects';
|
||||
import type { EffectInstance, EffectToolType, ExplosionInstance } from '../../shared/types/effects';
|
||||
import { SceneDarknessOverlay } from '../shared/effects/SceneDarknessOverlay';
|
||||
import { useEffectsState } from '../shared/effects/useEffectsState';
|
||||
import { useSceneDarknessState } from '../shared/effects/useSceneDarknessState';
|
||||
@@ -839,10 +839,16 @@ export function ControlApp() {
|
||||
const showReturnToMain = isInSideStoryline && mainStoryReturnGraphNodeId !== null;
|
||||
const branchOptionOffset = showReturnToMain ? 1 : 0;
|
||||
|
||||
const tool = fxState?.tool ?? { tool: 'fog', radiusN: 0.08, intensity: 0.6 };
|
||||
const tool = fxState?.tool ?? { tool: 'none' as const, radiusN: 0.08, intensity: 0.6 };
|
||||
const toolRef = useRef(tool);
|
||||
toolRef.current = tool;
|
||||
|
||||
/** Повторный клик по активному инструменту снимает выбор. */
|
||||
function selectEffectTool(next: Exclude<EffectToolType, 'none'>): void {
|
||||
const toolName: EffectToolType = tool.tool === next ? 'none' : next;
|
||||
void fx.dispatch({ kind: 'tool.set', tool: { ...tool, tool: toolName } });
|
||||
}
|
||||
|
||||
function layoutBrushCursor(): void {
|
||||
const el = brushCursorElRef.current;
|
||||
const p = cursorPosRef.current;
|
||||
@@ -850,7 +856,7 @@ export function ControlApp() {
|
||||
const ps = previewSizeRef.current;
|
||||
const t = toolRef.current;
|
||||
if (!el) return;
|
||||
if (!p) {
|
||||
if (!p || t.tool === 'none') {
|
||||
el.style.visibility = 'hidden';
|
||||
return;
|
||||
}
|
||||
@@ -1447,7 +1453,7 @@ export function ControlApp() {
|
||||
iconOnly
|
||||
title={t('control.eraser')}
|
||||
ariaLabel={t('control.eraser')}
|
||||
onClick={() => void fx.dispatch({ kind: 'tool.set', tool: { ...tool, tool: 'eraser' } })}
|
||||
onClick={() => selectEffectTool('eraser')}
|
||||
>
|
||||
<span className={styles.iconGlyph}>🧹</span>
|
||||
</Button>
|
||||
@@ -1483,7 +1489,7 @@ export function ControlApp() {
|
||||
iconOnly
|
||||
title={t('control.fog')}
|
||||
ariaLabel={t('control.fog')}
|
||||
onClick={() => void fx.dispatch({ kind: 'tool.set', tool: { ...tool, tool: 'fog' } })}
|
||||
onClick={() => selectEffectTool('fog')}
|
||||
>
|
||||
<span className={styles.iconGlyph}>🌫️</span>
|
||||
</Button>
|
||||
@@ -1492,7 +1498,7 @@ export function ControlApp() {
|
||||
iconOnly
|
||||
title={t('control.rain')}
|
||||
ariaLabel={t('control.rain')}
|
||||
onClick={() => void fx.dispatch({ kind: 'tool.set', tool: { ...tool, tool: 'rain' } })}
|
||||
onClick={() => selectEffectTool('rain')}
|
||||
>
|
||||
<span className={styles.iconGlyph}>🌧️</span>
|
||||
</Button>
|
||||
@@ -1501,7 +1507,7 @@ export function ControlApp() {
|
||||
iconOnly
|
||||
title={t('control.fire')}
|
||||
ariaLabel={t('control.fire')}
|
||||
onClick={() => void fx.dispatch({ kind: 'tool.set', tool: { ...tool, tool: 'fire' } })}
|
||||
onClick={() => selectEffectTool('fire')}
|
||||
>
|
||||
<span className={styles.iconGlyph}>🔥</span>
|
||||
</Button>
|
||||
@@ -1510,7 +1516,7 @@ export function ControlApp() {
|
||||
iconOnly
|
||||
title={t('control.water')}
|
||||
ariaLabel={t('control.water')}
|
||||
onClick={() => void fx.dispatch({ kind: 'tool.set', tool: { ...tool, tool: 'water' } })}
|
||||
onClick={() => selectEffectTool('water')}
|
||||
>
|
||||
<span className={styles.iconGlyph}>💧</span>
|
||||
</Button>
|
||||
@@ -1524,9 +1530,7 @@ export function ControlApp() {
|
||||
iconOnly
|
||||
title={t('control.lightning')}
|
||||
ariaLabel={t('control.lightning')}
|
||||
onClick={() =>
|
||||
void fx.dispatch({ kind: 'tool.set', tool: { ...tool, tool: 'lightning' } })
|
||||
}
|
||||
onClick={() => selectEffectTool('lightning')}
|
||||
>
|
||||
<span className={styles.iconGlyph}>⚡</span>
|
||||
</Button>
|
||||
@@ -1535,7 +1539,7 @@ export function ControlApp() {
|
||||
iconOnly
|
||||
title={t('control.sunbeam')}
|
||||
ariaLabel={t('control.sunbeam')}
|
||||
onClick={() => void fx.dispatch({ kind: 'tool.set', tool: { ...tool, tool: 'sunbeam' } })}
|
||||
onClick={() => selectEffectTool('sunbeam')}
|
||||
>
|
||||
<span className={styles.iconGlyph}>☀️</span>
|
||||
</Button>
|
||||
@@ -1544,7 +1548,7 @@ export function ControlApp() {
|
||||
iconOnly
|
||||
title={t('control.freeze')}
|
||||
ariaLabel={t('control.freeze')}
|
||||
onClick={() => void fx.dispatch({ kind: 'tool.set', tool: { ...tool, tool: 'freeze' } })}
|
||||
onClick={() => selectEffectTool('freeze')}
|
||||
>
|
||||
<span className={styles.iconGlyph}>❄️</span>
|
||||
</Button>
|
||||
@@ -1553,9 +1557,7 @@ export function ControlApp() {
|
||||
iconOnly
|
||||
title={t('control.darkness')}
|
||||
ariaLabel={t('control.darkness')}
|
||||
onClick={() =>
|
||||
void fx.dispatch({ kind: 'tool.set', tool: { ...tool, tool: 'darkness' } })
|
||||
}
|
||||
onClick={() => selectEffectTool('darkness')}
|
||||
>
|
||||
<span className={styles.iconGlyph}>🌑</span>
|
||||
</Button>
|
||||
@@ -1564,9 +1566,7 @@ export function ControlApp() {
|
||||
iconOnly
|
||||
title={t('control.poisonCloud')}
|
||||
ariaLabel={t('control.poisonCloud')}
|
||||
onClick={() =>
|
||||
void fx.dispatch({ kind: 'tool.set', tool: { ...tool, tool: 'poisonCloud' } })
|
||||
}
|
||||
onClick={() => selectEffectTool('poisonCloud')}
|
||||
>
|
||||
<span className={styles.iconGlyph}>☣️</span>
|
||||
</Button>
|
||||
@@ -1575,9 +1575,7 @@ export function ControlApp() {
|
||||
iconOnly
|
||||
title={t('control.explosion')}
|
||||
ariaLabel={t('control.explosion')}
|
||||
onClick={() =>
|
||||
void fx.dispatch({ kind: 'tool.set', tool: { ...tool, tool: 'explosion' } })
|
||||
}
|
||||
onClick={() => selectEffectTool('explosion')}
|
||||
>
|
||||
<span className={styles.iconGlyph}>💥</span>
|
||||
</Button>
|
||||
@@ -1592,9 +1590,7 @@ export function ControlApp() {
|
||||
iconOnly
|
||||
title={t('control.explorerBrush')}
|
||||
ariaLabel={t('control.explorerBrush')}
|
||||
onClick={() =>
|
||||
void fx.dispatch({ kind: 'tool.set', tool: { ...tool, tool: 'exploreBrush' } })
|
||||
}
|
||||
onClick={() => selectEffectTool('exploreBrush')}
|
||||
>
|
||||
<span className={styles.iconGlyph}>🔦</span>
|
||||
</Button>
|
||||
@@ -1772,6 +1768,7 @@ export function ControlApp() {
|
||||
return;
|
||||
}
|
||||
if (e.button !== 0) return;
|
||||
if (tool.tool === 'none') return;
|
||||
const p = toNPoint(e);
|
||||
if (!p) return;
|
||||
cursorPosRef.current = p;
|
||||
|
||||
@@ -97,6 +97,8 @@ void test('ControlApp: эффекты в пульте, иконки с тулт
|
||||
assert.ok(src.includes("title={t('control.poisonCloud')}"));
|
||||
assert.ok(src.includes("title={t('control.fog')}"));
|
||||
assert.ok(src.includes("ariaLabel={t('control.fog')}"));
|
||||
assert.ok(src.includes('selectEffectTool'), 'повторный клик снимает инструмент');
|
||||
assert.ok(src.includes("tool.tool === next ? 'none' : next"));
|
||||
assert.ok(src.includes('iconOnly'));
|
||||
assert.ok(src.includes("title={t('control.clearEffects')}"));
|
||||
assert.ok(src.includes("ariaLabel={t('control.clearEffects')}"));
|
||||
|
||||
Reference in New Issue
Block a user