Merge branch 'users' into main

Players library, circular NPC tokens, disposition types; keep main overlay/release fixes.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Ivan Fontosh
2026-08-04 08:03:22 +08:00
69 changed files with 5943 additions and 327 deletions
+1 -1
View File
@@ -302,7 +302,7 @@ function FilterToolbar({
return (
<Panel position="top-left">
<Select
className={styles.filterSelect}
{...(styles.filterSelect ? { className: styles.filterSelect } : {})}
ariaLabel={ui.graphFilter}
value={value}
onChange={(next) => onChange(next as GraphGroupFilter)}
@@ -189,6 +189,15 @@
margin-bottom: 8px;
}
.colorInput {
width: 100%;
height: 36px;
padding: 0;
border: 1px solid var(--stroke);
border-radius: 8px;
background: transparent;
}
.avatarPick {
display: grid;
gap: 10px;
+42 -5
View File
@@ -14,8 +14,10 @@ import type {
import editorStyles from '../editor/EditorApp.module.css';
import { useEditorI18n } from '../editor/i18n/EditorI18nContext';
import { getDndApi } from '../shared/dndApi';
import { PlayerTokenView } from '../shared/playerToken/PlayerTokenView';
import { Button, Input, Select } from '../shared/ui/controls';
import { useAssetUrl } from '../shared/useAssetImageUrl';
import { npcDispositionRingColor } from '../../shared/types/npcDisposition';
import { NpcDescriptionField } from './NpcDescriptionField';
import { NpcEditModal } from './NpcEditModal';
@@ -735,11 +737,27 @@ export function NpcsEditorApp() {
<div>
<div className={styles.fieldLabel}>{t('npcs.avatar')}</div>
<div className={styles.avatarPick}>
<div className={styles.avatarPreview}>
{selectedUrl ? (
<img className={styles.avatarPreviewImg} src={selectedUrl} alt="" />
) : null}
</div>
<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,
});
}}
/>
<Button
disabled={avatarBusy}
onClick={() => {
@@ -764,6 +782,25 @@ export function NpcsEditorApp() {
</div>
</div>
<label>
<div className={styles.fieldLabel}>{t('npcs.disposition')}</div>
<Select
value={selected.disposition ?? 'neutral'}
ariaLabel={t('npcs.disposition')}
onChange={(next) => {
void api.invoke(ipcChannels.project.updateNpcFields, {
npcId: selected.id,
disposition: next as 'hostile' | 'neutral' | 'friendly',
});
}}
options={[
{ value: 'hostile', label: t('npcs.disposition.hostile') },
{ value: 'neutral', label: t('npcs.disposition.neutral') },
{ value: 'friendly', label: t('npcs.disposition.friendly') },
]}
/>
</label>
<div>
<div className={styles.fieldLabel}>{t('npcs.name')}</div>
<Input