mirror of
https://github.com/duckietm/Nitro-V3.git
synced 2026-06-19 15:06:20 +00:00
feat(furni-editor): 'Import from Habbo' button to fetch official texts
useFurniEditor gains importText(id) + importResult (10049 round-trip); the edit view shows an 'Import from Habbo' button that fills Display Name/ Description with the official text for review before Save (nonce-guarded, classname-matched), with an inline result note.
This commit is contained in:
@@ -19,7 +19,7 @@ export const FurniEditorView: FC<{}> = () =>
|
|||||||
selectedItem, setSelectedItem, furniDataEntry,
|
selectedItem, setSelectedItem, furniDataEntry,
|
||||||
interactions,
|
interactions,
|
||||||
searchItems, loadDetail, loadBySpriteId, updateItem, deleteItem, loadInteractions,
|
searchItems, loadDetail, loadBySpriteId, updateItem, deleteItem, loadInteractions,
|
||||||
updateFurnidata, revertFurnidata
|
updateFurnidata, revertFurnidata, importText, importResult
|
||||||
} = useFurniEditor();
|
} = useFurniEditor();
|
||||||
|
|
||||||
const isMod = useHasPermission('acc_catalogfurni');
|
const isMod = useHasPermission('acc_catalogfurni');
|
||||||
@@ -158,6 +158,8 @@ export const FurniEditorView: FC<{}> = () =>
|
|||||||
onBack={ handleBack }
|
onBack={ handleBack }
|
||||||
onUpdateFurnidata={ updateFurnidata }
|
onUpdateFurnidata={ updateFurnidata }
|
||||||
onRevertFurnidata={ revertFurnidata }
|
onRevertFurnidata={ revertFurnidata }
|
||||||
|
onImportText={ importText }
|
||||||
|
importResult={ importResult }
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ interface FurniEditorEditViewProps
|
|||||||
onBack: () => void;
|
onBack: () => void;
|
||||||
onUpdateFurnidata: (id: number, name: string, description: string) => void;
|
onUpdateFurnidata: (id: number, name: string, description: string) => void;
|
||||||
onRevertFurnidata: (id: number) => void;
|
onRevertFurnidata: (id: number) => void;
|
||||||
|
onImportText: (id: number) => void;
|
||||||
|
importResult: { found: boolean; name: string; description: string; classname: string; nonce: number } | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const FIELD_TIPS: Record<string, string> = {
|
const FIELD_TIPS: Record<string, string> = {
|
||||||
@@ -110,7 +112,7 @@ const CopyValue: FC<{ value: string | number }> = ({ value }) =>
|
|||||||
|
|
||||||
export const FurniEditorEditView: FC<FurniEditorEditViewProps> = props =>
|
export const FurniEditorEditView: FC<FurniEditorEditViewProps> = props =>
|
||||||
{
|
{
|
||||||
const { item, furniDataEntry, interactions, loading, onUpdate, onDelete, onBack, onUpdateFurnidata, onRevertFurnidata } = props;
|
const { item, furniDataEntry, interactions, loading, onUpdate, onDelete, onBack, onUpdateFurnidata, onRevertFurnidata, onImportText, importResult } = props;
|
||||||
const saveRef = useRef<() => void>(null);
|
const saveRef = useRef<() => void>(null);
|
||||||
|
|
||||||
const [ form, setForm ] = useState({
|
const [ form, setForm ] = useState({
|
||||||
@@ -139,6 +141,8 @@ export const FurniEditorEditView: FC<FurniEditorEditViewProps> = props =>
|
|||||||
const [ furniName, setFurniName ] = useState('');
|
const [ furniName, setFurniName ] = useState('');
|
||||||
const [ furniDescription, setFurniDescription ] = useState('');
|
const [ furniDescription, setFurniDescription ] = useState('');
|
||||||
const [ confirmFurnidata, setConfirmFurnidata ] = useState(false);
|
const [ confirmFurnidata, setConfirmFurnidata ] = useState(false);
|
||||||
|
const [ importNote, setImportNote ] = useState('');
|
||||||
|
const appliedImportNonce = useRef(0);
|
||||||
|
|
||||||
useEffect(() =>
|
useEffect(() =>
|
||||||
{
|
{
|
||||||
@@ -170,6 +174,7 @@ export const FurniEditorEditView: FC<FurniEditorEditViewProps> = props =>
|
|||||||
setFurniName(String(furniDataEntry?.name ?? ''));
|
setFurniName(String(furniDataEntry?.name ?? ''));
|
||||||
setFurniDescription(String(furniDataEntry?.description ?? ''));
|
setFurniDescription(String(furniDataEntry?.description ?? ''));
|
||||||
setConfirmFurnidata(false);
|
setConfirmFurnidata(false);
|
||||||
|
setImportNote('');
|
||||||
}, [ item, furniDataEntry ]);
|
}, [ item, furniDataEntry ]);
|
||||||
|
|
||||||
const setField = useCallback((key: string, value: unknown) =>
|
const setField = useCallback((key: string, value: unknown) =>
|
||||||
@@ -238,6 +243,27 @@ export const FurniEditorEditView: FC<FurniEditorEditViewProps> = props =>
|
|||||||
furniName !== String(furniDataEntry?.name ?? '') || furniDescription !== String(furniDataEntry?.description ?? ''),
|
furniName !== String(furniDataEntry?.name ?? '') || furniDescription !== String(furniDataEntry?.description ?? ''),
|
||||||
[ furniName, furniDescription, furniDataEntry ]);
|
[ furniName, furniDescription, furniDataEntry ]);
|
||||||
|
|
||||||
|
// Apply an "Import from Habbo" result into the editable fields (review then Save).
|
||||||
|
useEffect(() =>
|
||||||
|
{
|
||||||
|
if(!importResult || importResult.nonce === appliedImportNonce.current) return;
|
||||||
|
appliedImportNonce.current = importResult.nonce;
|
||||||
|
|
||||||
|
// Ignore a result that belongs to a different furni (user navigated away).
|
||||||
|
if(importResult.classname && importResult.classname.trim().toLowerCase() !== String(item?.itemName ?? '').trim().toLowerCase()) return;
|
||||||
|
|
||||||
|
if(importResult.found)
|
||||||
|
{
|
||||||
|
setFurniName(importResult.name);
|
||||||
|
setFurniDescription(importResult.description);
|
||||||
|
setImportNote('Imported from Habbo — review and Save');
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
setImportNote('Not found on Habbo for this classname');
|
||||||
|
}
|
||||||
|
}, [ importResult, item ]);
|
||||||
|
|
||||||
const handleSave = useCallback(() =>
|
const handleSave = useCallback(() =>
|
||||||
{
|
{
|
||||||
if(!isValid) return;
|
if(!isValid) return;
|
||||||
@@ -336,10 +362,22 @@ export const FurniEditorEditView: FC<FurniEditorEditViewProps> = props =>
|
|||||||
<input className={ inputClass() } value={ furniDescription } onChange={ e => setFurniDescription(e.target.value) } maxLength={ 256 } />
|
<input className={ inputClass() } value={ furniDescription } onChange={ e => setFurniDescription(e.target.value) } maxLength={ 256 } />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Flex gap={ 1 } className="mt-1.5">
|
<Flex gap={ 1 } className="mt-1.5" alignItems="center">
|
||||||
<Button variant="success" disabled={ loading || !furnidataDirty } onClick={ () => setConfirmFurnidata(true) }>Save name/desc</Button>
|
<Button variant="success" disabled={ loading || !furnidataDirty } onClick={ () => setConfirmFurnidata(true) }>Save name/desc</Button>
|
||||||
<Button variant="secondary" disabled={ loading } onClick={ () => onRevertFurnidata(item.id) }>Revert</Button>
|
<Button variant="secondary" disabled={ loading } onClick={ () => onRevertFurnidata(item.id) }>Revert</Button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
disabled={ loading }
|
||||||
|
onClick={ () => onImportText(item.id) }
|
||||||
|
title="Fetch the official name & description from Habbo"
|
||||||
|
className="ml-auto inline-flex items-center gap-1 text-[11px] font-medium px-2.5 py-1.5 rounded-lg border border-slate-300 bg-[#ffffff] text-slate-600 hover:bg-slate-50 hover:border-slate-400 disabled:opacity-50 transition"
|
||||||
|
>
|
||||||
|
<svg className="w-3.5 h-3.5" viewBox="0 0 20 20" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M10 3v9" /><path d="m6.5 8.5 3.5 3.5 3.5-3.5" /><path d="M4 16h12" /></svg>
|
||||||
|
Import from Habbo
|
||||||
|
</button>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
{ importNote &&
|
||||||
|
<Text className={ `mt-1 text-[10px] ${ importNote.startsWith('Not found') ? 'text-amber-600' : 'text-primary' }` }>{ importNote }</Text> }
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex items-start gap-2 text-[11px] text-slate-500 bg-slate-50 border border-slate-200 rounded-lg px-2.5 py-2 leading-snug">
|
<div className="flex items-start gap-2 text-[11px] text-slate-500 bg-slate-50 border border-slate-200 rounded-lg px-2.5 py-2 leading-snug">
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { FurniEditorBySpriteComposer, FurniEditorDeleteComposer, FurniEditorDetailComposer, FurniEditorDetailResultEvent, FurniEditorInteractionsComposer, FurniEditorInteractionsResultEvent, FurniEditorResultEvent, FurniEditorRevertFurnidataComposer, FurniEditorSearchComposer, FurniEditorSearchResultEvent, FurniEditorUpdateComposer, FurniEditorUpdateFurnidataComposer } from '@nitrots/nitro-renderer';
|
import { FurniEditorBySpriteComposer, FurniEditorDeleteComposer, FurniEditorDetailComposer, FurniEditorDetailResultEvent, FurniEditorInteractionsComposer, FurniEditorInteractionsResultEvent, FurniEditorResultEvent, FurniEditorRevertFurnidataComposer, FurniEditorSearchComposer, FurniEditorSearchResultEvent, FurniEditorUpdateComposer, FurniEditorUpdateFurnidataComposer, FurniEditorImportTextComposer, FurniEditorImportTextResultEvent } from '@nitrots/nitro-renderer';
|
||||||
import { useCallback, useRef, useState } from 'react';
|
import { useCallback, useRef, useState } from 'react';
|
||||||
import { NotificationAlertType, SendMessageComposer } from '../../api';
|
import { NotificationAlertType, SendMessageComposer } from '../../api';
|
||||||
import { useMessageEvent, useNotification } from '../../hooks';
|
import { useMessageEvent, useNotification } from '../../hooks';
|
||||||
@@ -61,6 +61,8 @@ export const useFurniEditor = () =>
|
|||||||
const [ interactions, setInteractions ] = useState<string[]>([]);
|
const [ interactions, setInteractions ] = useState<string[]>([]);
|
||||||
const [ furniDataEntry, setFurniDataEntry ] = useState<Record<string, unknown> | null>(null);
|
const [ furniDataEntry, setFurniDataEntry ] = useState<Record<string, unknown> | null>(null);
|
||||||
const pendingActionRef = useRef<{ action: string; itemId: number } | null>(null);
|
const pendingActionRef = useRef<{ action: string; itemId: number } | null>(null);
|
||||||
|
const [ importResult, setImportResult ] = useState<{ found: boolean; name: string; description: string; classname: string; nonce: number } | null>(null);
|
||||||
|
const importNonceRef = useRef(0);
|
||||||
const { simpleAlert = null } = useNotification();
|
const { simpleAlert = null } = useNotification();
|
||||||
|
|
||||||
const clearError = useCallback(() => setError(null), []);
|
const clearError = useCallback(() => setError(null), []);
|
||||||
@@ -264,6 +266,28 @@ export const useFurniEditor = () =>
|
|||||||
SendMessageComposer(new FurniEditorRevertFurnidataComposer(id));
|
SendMessageComposer(new FurniEditorRevertFurnidataComposer(id));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const importText = useCallback((id: number) =>
|
||||||
|
{
|
||||||
|
setLoading(true);
|
||||||
|
setError(null);
|
||||||
|
SendMessageComposer(new FurniEditorImportTextComposer(id));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useMessageEvent(FurniEditorImportTextResultEvent, (event: FurniEditorImportTextResultEvent) =>
|
||||||
|
{
|
||||||
|
const parser = event.getParser();
|
||||||
|
|
||||||
|
setLoading(false);
|
||||||
|
importNonceRef.current += 1;
|
||||||
|
setImportResult({
|
||||||
|
found: parser.found,
|
||||||
|
name: parser.name,
|
||||||
|
description: parser.description,
|
||||||
|
classname: parser.classname,
|
||||||
|
nonce: importNonceRef.current
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
const loadInteractions = useCallback(() =>
|
const loadInteractions = useCallback(() =>
|
||||||
{
|
{
|
||||||
SendMessageComposer(new FurniEditorInteractionsComposer());
|
SendMessageComposer(new FurniEditorInteractionsComposer());
|
||||||
@@ -274,6 +298,6 @@ export const useFurniEditor = () =>
|
|||||||
selectedItem, setSelectedItem, catalogItems, furniDataEntry,
|
selectedItem, setSelectedItem, catalogItems, furniDataEntry,
|
||||||
interactions,
|
interactions,
|
||||||
searchItems, loadDetail, loadBySpriteId, updateItem, deleteItem, loadInteractions,
|
searchItems, loadDetail, loadBySpriteId, updateItem, deleteItem, loadInteractions,
|
||||||
updateFurnidata, revertFurnidata
|
updateFurnidata, revertFurnidata, importText, importResult
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user