mirror of
https://github.com/duckietm/Nitro-V3.git
synced 2026-06-19 15:06:20 +00:00
feat(furni-editor): editable furnidata name/desc section + read-only classname/public_name + diff-confirm + revert
This commit is contained in:
@@ -18,7 +18,8 @@ export const FurniEditorView: FC<{}> = () =>
|
|||||||
items, total, page, loading, error, clearError,
|
items, total, page, loading, error, clearError,
|
||||||
selectedItem, setSelectedItem, furniDataEntry,
|
selectedItem, setSelectedItem, furniDataEntry,
|
||||||
interactions,
|
interactions,
|
||||||
searchItems, loadDetail, loadBySpriteId, updateItem, deleteItem, loadInteractions
|
searchItems, loadDetail, loadBySpriteId, updateItem, deleteItem, loadInteractions,
|
||||||
|
updateFurnidata, revertFurnidata
|
||||||
} = useFurniEditor();
|
} = useFurniEditor();
|
||||||
|
|
||||||
const isMod = useHasPermission('acc_catalogfurni');
|
const isMod = useHasPermission('acc_catalogfurni');
|
||||||
@@ -155,6 +156,8 @@ export const FurniEditorView: FC<{}> = () =>
|
|||||||
onUpdate={ updateItem }
|
onUpdate={ updateItem }
|
||||||
onDelete={ deleteItem }
|
onDelete={ deleteItem }
|
||||||
onBack={ handleBack }
|
onBack={ handleBack }
|
||||||
|
onUpdateFurnidata={ updateFurnidata }
|
||||||
|
onRevertFurnidata={ revertFurnidata }
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ interface FurniEditorEditViewProps
|
|||||||
onUpdate: (id: number, fields: Record<string, unknown>) => void;
|
onUpdate: (id: number, fields: Record<string, unknown>) => void;
|
||||||
onDelete: (id: number) => void;
|
onDelete: (id: number) => void;
|
||||||
onBack: () => void;
|
onBack: () => void;
|
||||||
|
onUpdateFurnidata: (id: number, name: string, description: string) => void;
|
||||||
|
onRevertFurnidata: (id: number) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const FIELD_TIPS: Record<string, string> = {
|
const FIELD_TIPS: Record<string, string> = {
|
||||||
@@ -65,7 +67,7 @@ const Tip: FC<{ field: string }> = ({ field }) =>
|
|||||||
|
|
||||||
export const FurniEditorEditView: FC<FurniEditorEditViewProps> = props =>
|
export const FurniEditorEditView: FC<FurniEditorEditViewProps> = props =>
|
||||||
{
|
{
|
||||||
const { item, furniDataEntry, interactions, loading, onUpdate, onDelete, onBack } = props;
|
const { item, furniDataEntry, interactions, loading, onUpdate, onDelete, onBack, onUpdateFurnidata, onRevertFurnidata } = props;
|
||||||
const saveRef = useRef<() => void>(null);
|
const saveRef = useRef<() => void>(null);
|
||||||
|
|
||||||
const [ form, setForm ] = useState({
|
const [ form, setForm ] = useState({
|
||||||
@@ -91,6 +93,9 @@ export const FurniEditorEditView: FC<FurniEditorEditViewProps> = props =>
|
|||||||
});
|
});
|
||||||
|
|
||||||
const [ showDeleteDialog, setShowDeleteDialog ] = useState(false);
|
const [ showDeleteDialog, setShowDeleteDialog ] = useState(false);
|
||||||
|
const [ furniName, setFurniName ] = useState('');
|
||||||
|
const [ furniDescription, setFurniDescription ] = useState('');
|
||||||
|
const [ confirmFurnidata, setConfirmFurnidata ] = useState(false);
|
||||||
|
|
||||||
useEffect(() =>
|
useEffect(() =>
|
||||||
{
|
{
|
||||||
@@ -119,7 +124,10 @@ export const FurniEditorEditView: FC<FurniEditorEditViewProps> = props =>
|
|||||||
});
|
});
|
||||||
|
|
||||||
setShowDeleteDialog(false);
|
setShowDeleteDialog(false);
|
||||||
}, [ item ]);
|
setFurniName(String(furniDataEntry?.name ?? ''));
|
||||||
|
setFurniDescription(String(furniDataEntry?.description ?? ''));
|
||||||
|
setConfirmFurnidata(false);
|
||||||
|
}, [ item, furniDataEntry ]);
|
||||||
|
|
||||||
const setField = useCallback((key: string, value: unknown) =>
|
const setField = useCallback((key: string, value: unknown) =>
|
||||||
{
|
{
|
||||||
@@ -209,6 +217,7 @@ export const FurniEditorEditView: FC<FurniEditorEditViewProps> = props =>
|
|||||||
const inputClass = (field?: string) =>
|
const inputClass = (field?: string) =>
|
||||||
`w-full px-2 py-1 text-xs leading-normal rounded-sm border border-[#ccc] min-h-[calc(1.5em+0.5rem+2px)] ${ field && validation[field] ? 'border-red-500 bg-red-50' : '' }`;
|
`w-full px-2 py-1 text-xs leading-normal rounded-sm border border-[#ccc] min-h-[calc(1.5em+0.5rem+2px)] ${ field && validation[field] ? 'border-red-500 bg-red-50' : '' }`;
|
||||||
const labelClass = 'text-[11px] font-bold text-[#333] mb-0 flex items-center gap-0.5';
|
const labelClass = 'text-[11px] font-bold text-[#333] mb-0 flex items-center gap-0.5';
|
||||||
|
const readonlyClass = 'w-full px-2 py-1 text-sm font-mono rounded-sm border border-[#ddd] bg-[#f2f2eb] text-[#555] select-all';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column gap={ 1 } className="h-full overflow-auto">
|
<Column gap={ 1 } className="h-full overflow-auto">
|
||||||
@@ -232,14 +241,12 @@ export const FurniEditorEditView: FC<FurniEditorEditViewProps> = props =>
|
|||||||
<Section title="Basic Info">
|
<Section title="Basic Info">
|
||||||
<div className="grid grid-cols-2 gap-2">
|
<div className="grid grid-cols-2 gap-2">
|
||||||
<div>
|
<div>
|
||||||
<label className={ labelClass }>Item Name</label>
|
<label className={ labelClass }>Classname</label>
|
||||||
<input className={ inputClass('itemName') } value={ form.itemName } onChange={ e => setField('itemName', e.target.value) } />
|
<div className={ readonlyClass }>{ form.itemName }</div>
|
||||||
{ validation.itemName && <span className="text-[9px] text-red-500">{ validation.itemName }</span> }
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className={ labelClass }>Public Name</label>
|
<label className={ labelClass }>Public Name (DB fallback)</label>
|
||||||
<input className={ inputClass('publicName') } value={ form.publicName } onChange={ e => setField('publicName', e.target.value) } />
|
<div className={ readonlyClass }>{ form.publicName }</div>
|
||||||
{ validation.publicName && <span className="text-[9px] text-red-500">{ validation.publicName }</span> }
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className={ labelClass }>Sprite ID</label>
|
<label className={ labelClass }>Sprite ID</label>
|
||||||
@@ -320,18 +327,24 @@ export const FurniEditorEditView: FC<FurniEditorEditViewProps> = props =>
|
|||||||
</div>
|
</div>
|
||||||
</Section>
|
</Section>
|
||||||
|
|
||||||
{ furniDataEntry &&
|
<Section title="Furnidata (display name)" defaultOpen={ true }>
|
||||||
<Section title="FurniData.json" defaultOpen={ false }>
|
<Column gap={ 1 }>
|
||||||
<div className="grid grid-cols-2 gap-x-3 gap-y-0.5 text-[10px]">
|
<div>
|
||||||
{ Object.entries(furniDataEntry).map(([ key, value ]) => (
|
<label className={ labelClass }>Display Name</label>
|
||||||
<div key={ key } className="flex justify-between bg-[#f5f5f5] px-2 py-0.5 rounded">
|
<input className={ inputClass() } value={ furniName } onChange={ e => setFurniName(e.target.value) } maxLength={ 256 } />
|
||||||
<span className="font-bold text-[#555]">{ key }</span>
|
|
||||||
<span className="text-[#333] truncate ml-1 max-w-[120px] text-right">{ String(value ?? '') }</span>
|
|
||||||
</div>
|
|
||||||
)) }
|
|
||||||
</div>
|
</div>
|
||||||
</Section>
|
<div>
|
||||||
}
|
<label className={ labelClass }>Description</label>
|
||||||
|
<textarea className={ inputClass() } rows={ 3 } value={ furniDescription } onChange={ e => setFurniDescription(e.target.value) } maxLength={ 256 } />
|
||||||
|
</div>
|
||||||
|
{ (furniName !== String(furniDataEntry?.name ?? '') || furniDescription !== String(furniDataEntry?.description ?? '')) &&
|
||||||
|
<span className="text-[10px] text-orange-500 font-bold">Unsaved furnidata changes</span> }
|
||||||
|
<Flex gap={ 1 }>
|
||||||
|
<Button variant="success" disabled={ loading } onClick={ () => setConfirmFurnidata(true) }>Save name/desc</Button>
|
||||||
|
<Button variant="secondary" disabled={ loading } onClick={ () => onRevertFurnidata(item.id) }>Revert</Button>
|
||||||
|
</Flex>
|
||||||
|
</Column>
|
||||||
|
</Section>
|
||||||
|
|
||||||
{ /* Actions */ }
|
{ /* Actions */ }
|
||||||
<Flex gap={ 1 } justifyContent="between" alignItems="center" className="mt-1">
|
<Flex gap={ 1 } justifyContent="between" alignItems="center" className="mt-1">
|
||||||
@@ -366,6 +379,21 @@ export const FurniEditorEditView: FC<FurniEditorEditViewProps> = props =>
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{ /* Furnidata Confirmation Dialog */ }
|
||||||
|
{ confirmFurnidata &&
|
||||||
|
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50" onClick={ () => setConfirmFurnidata(false) }>
|
||||||
|
<div className="bg-white rounded-lg shadow-xl p-4 w-[320px]" onClick={ e => e.stopPropagation() }>
|
||||||
|
<Text bold className="text-[14px] mb-2 block">Apply furnidata change to ALL clients?</Text>
|
||||||
|
<div className="text-xs mb-1"><b>Name:</b> { String(furniDataEntry?.name ?? '') } → { furniName }</div>
|
||||||
|
<div className="text-xs mb-3"><b>Desc:</b> { String(furniDataEntry?.description ?? '') } → { furniDescription }</div>
|
||||||
|
<Flex gap={ 1 } justifyContent="end">
|
||||||
|
<Button variant="secondary" onClick={ () => setConfirmFurnidata(false) }>Cancel</Button>
|
||||||
|
<Button variant="success" onClick={ () => { onUpdateFurnidata(item.id, furniName, furniDescription); setConfirmFurnidata(false); } }>Confirm</Button>
|
||||||
|
</Flex>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
</Column>
|
</Column>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user