diff --git a/public/renderer-config.json b/public/renderer-config.json index b0beddf..7322252 100644 --- a/public/renderer-config.json +++ b/public/renderer-config.json @@ -1,4 +1,113 @@ { + "socket.url": "ws://localhost:2097", + "asset.url": "http://localhost:3000/nitro-assets", + "image.library.url": "http://localhost:3000/swf/c_images/", + "hof.furni.url": "http://localhost:3000/swf/dcr/hof_furni", + "images.url": "${asset.url}/images", + "gamedata.url": "${asset.url}/gamedata", + "sounds.url": "${asset.url}/sounds/%sample%.mp3", + "external.texts.url": [ "${gamedata.url}/ExternalTexts.json", "${gamedata.url}/UITexts.json" ], + "external.samples.url": "${hof.furni.url}/mp3/sound_machine_sample_%sample%.mp3", + "furnidata.url": "${gamedata.url}/FurnitureData.json", + "productdata.url": "${gamedata.url}/ProductData.json", + "avatar.actions.url": "${gamedata.url}/HabboAvatarActions.json", + "avatar.figuredata.url": "${gamedata.url}/FigureData.json", + "avatar.figuremap.url": "${gamedata.url}/FigureMap.json", + "avatar.effectmap.url": "${gamedata.url}/EffectMap.json", + "avatar.asset.url": "${asset.url}/bundled/figure/%libname%.nitro", + "avatar.asset.effect.url": "${asset.url}/bundled/effect/%libname%.nitro", + "furni.asset.url": "${asset.url}/bundled/furniture/%libname%.nitro", + "furni.asset.icon.url": "${hof.furni.url}/icons/%libname%%param%_icon.png", + "pet.asset.url": "${asset.url}/bundled/pet/%libname%.nitro", + "generic.asset.url": "${asset.url}/bundled/generic/%libname%.nitro", + "badge.asset.url": "${image.library.url}album1584/%badgename%.gif", + "furni.rotation.bounce.steps": 20, + "furni.rotation.bounce.height": 0.0625, + "enable.avatar.arrow": false, + "system.log.debug": true, + "system.log.warn": true, + "system.log.error": true, + "system.log.events": true, + "system.log.packets": true, + "system.fps.animation": 24, + "system.fps.max": 60, + "system.pong.manually": true, + "system.pong.interval.ms": 20000, + "room.color.skip.transition": true, + "room.landscapes.enabled": true, + "avatar.mandatory.libraries": [ + "bd:1", + "li:0" + ], + "avatar.mandatory.effect.libraries": [ + "dance.1", + "dance.2", + "dance.3", + "dance.4" + ], + "avatar.default.figuredata": {"palettes":[{"id":1,"colors":[{"id":99999,"index":1001,"club":0,"selectable":false,"hexCode":"DDDDDD"},{"id":99998,"index":1001,"club":0,"selectable":false,"hexCode":"FAFAFA"}]},{"id":3,"colors":[{"id":10001,"index":1001,"club":0,"selectable":false,"hexCode":"EEEEEE"},{"id":10002,"index":1002,"club":0,"selectable":false,"hexCode":"FA3831"},{"id":10003,"index":1003,"club":0,"selectable":false,"hexCode":"FD92A0"},{"id":10004,"index":1004,"club":0,"selectable":false,"hexCode":"2AC7D2"},{"id":10005,"index":1005,"club":0,"selectable":false,"hexCode":"35332C"},{"id":10006,"index":1006,"club":0,"selectable":false,"hexCode":"EFFF92"},{"id":10007,"index":1007,"club":0,"selectable":false,"hexCode":"C6FF98"},{"id":10008,"index":1008,"club":0,"selectable":false,"hexCode":"FF925A"},{"id":10009,"index":1009,"club":0,"selectable":false,"hexCode":"9D597E"},{"id":10010,"index":1010,"club":0,"selectable":false,"hexCode":"B6F3FF"},{"id":10011,"index":1011,"club":0,"selectable":false,"hexCode":"6DFF33"},{"id":10012,"index":1012,"club":0,"selectable":false,"hexCode":"3378C9"},{"id":10013,"index":1013,"club":0,"selectable":false,"hexCode":"FFB631"},{"id":10014,"index":1014,"club":0,"selectable":false,"hexCode":"DFA1E9"},{"id":10015,"index":1015,"club":0,"selectable":false,"hexCode":"F9FB32"},{"id":10016,"index":1016,"club":0,"selectable":false,"hexCode":"CAAF8F"},{"id":10017,"index":1017,"club":0,"selectable":false,"hexCode":"C5C6C5"},{"id":10018,"index":1018,"club":0,"selectable":false,"hexCode":"47623D"},{"id":10019,"index":1019,"club":0,"selectable":false,"hexCode":"8A8361"},{"id":10020,"index":1020,"club":0,"selectable":false,"hexCode":"FF8C33"},{"id":10021,"index":1021,"club":0,"selectable":false,"hexCode":"54C627"},{"id":10022,"index":1022,"club":0,"selectable":false,"hexCode":"1E6C99"},{"id":10023,"index":1023,"club":0,"selectable":false,"hexCode":"984F88"},{"id":10024,"index":1024,"club":0,"selectable":false,"hexCode":"77C8FF"},{"id":10025,"index":1025,"club":0,"selectable":false,"hexCode":"FFC08E"},{"id":10026,"index":1026,"club":0,"selectable":false,"hexCode":"3C4B87"},{"id":10027,"index":1027,"club":0,"selectable":false,"hexCode":"7C2C47"},{"id":10028,"index":1028,"club":0,"selectable":false,"hexCode":"D7FFE3"},{"id":10029,"index":1029,"club":0,"selectable":false,"hexCode":"8F3F1C"},{"id":10030,"index":1030,"club":0,"selectable":false,"hexCode":"FF6393"},{"id":10031,"index":1031,"club":0,"selectable":false,"hexCode":"1F9B79"},{"id":10032,"index":1032,"club":0,"selectable":false,"hexCode":"FDFF33"}]}],"setTypes":[{"type":"hd","paletteId":1,"mandatory_f_0":true,"mandatory_f_1":true,"mandatory_m_0":true,"mandatory_m_1":true,"sets":[{"id":99999,"gender":"U","club":0,"colorable":true,"selectable":false,"preselectable":false,"sellable":false,"parts":[{"id":1,"type":"bd","colorable":true,"index":0,"colorindex":1},{"id":1,"type":"hd","colorable":true,"index":0,"colorindex":1},{"id":1,"type":"lh","colorable":true,"index":0,"colorindex":1},{"id":1,"type":"rh","colorable":true,"index":0,"colorindex":1}]}]},{"type":"bds","paletteId":1,"mandatory_f_0":false,"mandatory_f_1":false,"mandatory_m_0":false,"mandatory_m_1":false,"sets":[{"id":10001,"gender":"U","club":0,"colorable":true,"selectable":false,"preselectable":false,"sellable":false,"parts":[{"id":10001,"type":"bds","colorable":true,"index":0,"colorindex":1},{"id":10001,"type":"lhs","colorable":true,"index":0,"colorindex":1},{"id":10001,"type":"rhs","colorable":true,"index":0,"colorindex":1}],"hiddenLayers":[{"partType":"bd"},{"partType":"rh"},{"partType":"lh"}]}]},{"type":"ss","paletteId":3,"mandatory_f_0":false,"mandatory_f_1":false,"mandatory_m_0":false,"mandatory_m_1":false,"sets":[{"id":10010,"gender":"F","club":0,"colorable":true,"selectable":false,"preselectable":false,"sellable":false,"parts":[{"id":10001,"type":"ss","colorable":true,"index":0,"colorindex":1}],"hiddenLayers":[{"partType":"ch"},{"partType":"lg"},{"partType":"ca"},{"partType":"wa"},{"partType":"sh"},{"partType":"ls"},{"partType":"rs"},{"partType":"lc"},{"partType":"rc"},{"partType":"cc"},{"partType":"cp"}]},{"id":10011,"gender":"M","club":0,"colorable":true,"selectable":false,"preselectable":false,"sellable":false,"parts":[{"id":10002,"type":"ss","colorable":true,"index":0,"colorindex":1}],"hiddenLayers":[{"partType":"ch"},{"partType":"lg"},{"partType":"ca"},{"partType":"wa"},{"partType":"sh"},{"partType":"ls"},{"partType":"rs"},{"partType":"lc"},{"partType":"rc"},{"partType":"cc"},{"partType":"cp"}]}]}]}, + "avatar.default.actions": { + "actions": [ + { + "id": "Default", + "state": "std", + "precedence": 1000, + "main": true, + "isDefault": true, + "geometryType": "vertical", + "activePartSet": "figure", + "assetPartDefinition": "std" + } + ] + }, + "pet.types": [ + "dog", + "cat", + "croco", + "terrier", + "bear", + "pig", + "lion", + "rhino", + "spider", + "turtle", + "chicken", + "frog", + "dragon", + "monster", + "monkey", + "horse", + "monsterplant", + "bunnyeaster", + "bunnyevil", + "bunnydepressed", + "bunnylove", + "pigeongood", + "pigeonevil", + "demonmonkey", + "bearbaby", + "terrierbaby", + "gnome", + "leprechaun", + "kittenbaby", + "puppybaby", + "pigletbaby", + "haloompa", + "fools", + "pterosaur", + "velociraptor", + "cow", + "dragondog" + ], + "preload.assets.urls": [ + "${asset.url}/bundled/generic/avatar_additions.nitro", + "${asset.url}/bundled/generic/group_badge.nitro", + "${asset.url}/bundled/generic/floor_editor.nitro", + "${images.url}/loading_icon.png", + "${images.url}/clear_icon.png", + "${images.url}/big_arrow.png" + ] +} "socket.url": "ws://192.168.1.52:2096", "asset.url": "https://client.paxxo.online/nitro/bundled", "image.library.url": "https://client.paxxo.online/c_images/", @@ -584,4 +693,4 @@ "${images.url}/clear_icon.png", "${images.url}/big_arrow.png" ] -} \ No newline at end of file +} diff --git a/src/components/furni-editor/FurniEditorView.tsx b/src/components/furni-editor/FurniEditorView.tsx index c9ef3ab..bbdf177 100644 --- a/src/components/furni-editor/FurniEditorView.tsx +++ b/src/components/furni-editor/FurniEditorView.tsx @@ -1,5 +1,5 @@ import { AddLinkEventTracker, GetSessionDataManager, ILinkEventTracker, RemoveLinkEventTracker } from '@nitrots/nitro-renderer'; -import { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { FC, useCallback, useEffect, useState } from 'react'; import { NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView } from '../../common'; import { useFurniEditor } from '../../hooks/furni-editor'; import { FurniEditorCreateView } from './views/FurniEditorCreateView'; @@ -19,8 +19,8 @@ export const FurniEditorView: FC<{}> = () => const { items, total, page, loading, error, clearError, selectedItem, catalogItems, furniDataEntry, - interactions, - searchItems, loadDetail, loadBySpriteId, updateItem, createItem, deleteItem, loadInteractions + interactions, lastResult, + searchItems, loadDetail, loadBySpriteId, updateItem, deleteItem, createItem, loadInteractions } = useFurniEditor(); useEffect(() => @@ -77,10 +77,10 @@ export const FurniEditorView: FC<{}> = () => const { spriteId } = e.detail; - if(!Number.isFinite(spriteId) || spriteId < 0) return; + if(!spriteId || spriteId <= 0) return; - pendingEditRef.current = true; loadBySpriteId(spriteId); + setActiveTab(TAB_EDIT); }; window.addEventListener('furni-editor:open', handler as EventListener); @@ -90,8 +90,8 @@ export const FurniEditorView: FC<{}> = () => const handleSelect = useCallback((id: number) => { - pendingEditRef.current = true; loadDetail(id); + setActiveTab(TAB_EDIT); }, [ loadDetail ]); const handleBack = useCallback(() => @@ -104,12 +104,17 @@ export const FurniEditorView: FC<{}> = () => setIsVisible(false); }, []); - const isMod = useMemo(() => GetSessionDataManager().isModerator, []); + const handleCreated = useCallback((id: number) => + { + loadDetail(id); + setActiveTab(TAB_EDIT); + }, [ loadDetail ]); - if(!isVisible || !isMod) return null; + if(!GetSessionDataManager()?.isModerator) return null; + if(!isVisible) return null; return ( - + setActiveTab(TAB_SEARCH) }> @@ -148,6 +153,7 @@ export const FurniEditorView: FC<{}> = () => furniDataEntry={ furniDataEntry } interactions={ interactions } loading={ loading } + lastResult={ lastResult } onUpdate={ updateItem } onDelete={ deleteItem } onBack={ handleBack } @@ -155,14 +161,6 @@ export const FurniEditorView: FC<{}> = () => /> } - { activeTab === TAB_CREATE && - - } diff --git a/src/components/furni-editor/views/FurniEditorCreateView.tsx b/src/components/furni-editor/views/FurniEditorCreateView.tsx index d512a64..5526b32 100644 --- a/src/components/furni-editor/views/FurniEditorCreateView.tsx +++ b/src/components/furni-editor/views/FurniEditorCreateView.tsx @@ -1,17 +1,23 @@ -import { FC, useCallback, useState } from 'react'; -import { Button, Column, Flex, Text } from '../../../common'; +import { FC, useCallback, useEffect, useState } from 'react'; +import { FaPlus } from 'react-icons/fa'; +import { Column } from '../../../common'; interface FurniEditorCreateViewProps { interactions: string[]; loading: boolean; + lastResult: { success: boolean; message: string; id: number } | null; onCreate: (fields: Record) => void; - onBack: () => void; + onCreated: (id: number) => void; } +const inputClass = 'text-[11px] border-2 border-card-grid-item-border rounded px-2 py-1 bg-white focus:outline-none focus:border-primary transition-colors w-full'; +const labelClass = 'text-[9px] text-[#666] uppercase font-bold mb-0.5 block'; + export const FurniEditorCreateView: FC = props => { - const { interactions, loading, onCreate, onBack } = props; + const { interactions, loading, lastResult, onCreate, onCreated } = props; + const [ toast, setToast ] = useState<{ type: 'success' | 'error'; message: string; id?: number } | null>(null); const [ form, setForm ] = useState({ itemName: '', @@ -48,6 +54,24 @@ export const FurniEditorCreateView: FC = props => rare: false, }); + useEffect(() => + { + if(!lastResult) return; + + if(lastResult.success && lastResult.id > 0) + { + setToast({ type: 'success', message: `Item created with ID #${ lastResult.id }`, id: lastResult.id }); + setTimeout(() => onCreated(lastResult.id), 1500); + } + else if(!lastResult.success) + { + setToast({ type: 'error', message: lastResult.message }); + } + + const timer = setTimeout(() => setToast(null), 3000); + return () => clearTimeout(timer); + }, [ lastResult ]); + const setField = useCallback((key: string, value: unknown) => { setForm(prev => ({ ...prev, [key]: value })); @@ -56,23 +80,24 @@ export const FurniEditorCreateView: FC = props => const handleCreate = useCallback(() => { if(!form.itemName || !form.publicName) return; - onCreate(form); }, [ form, onCreate ]); - const inputClass = 'form-control form-control-sm'; - const labelClass = 'text-[11px] font-bold text-[#333] mb-0'; - return ( - - - Create New Item - + { /* Toast */ } + { toast && +
+ { toast.message } +
+ } -
- Basic Info -
+ { /* Basic Info */ } +
+
+ Basic Info +
+
setField('itemName', e.target.value) } placeholder="my_custom_furni" /> @@ -91,7 +116,7 @@ export const FurniEditorCreateView: FC = props =>
- setField('type', e.target.value) }> @@ -99,9 +124,12 @@ export const FurniEditorCreateView: FC = props =>
-
- Dimensions -
+ { /* Dimensions */ } +
+
+ Dimensions +
+
setField('width', Number(e.target.value)) } /> @@ -121,14 +149,17 @@ export const FurniEditorCreateView: FC = props =>
-
- Permissions -
+ { /* Permissions */ } +
+
+ Permissions +
+
{ [ 'allowStack', 'allowWalk', 'allowSit', 'allowLay', 'allowGift', 'allowTrade', 'allowRecycle', 'allowMarketplaceSell', 'allowInventoryStack' ].map(key => ( -
-
- Interaction -
-
- - -
-
- - setField('interactionModesCount', Number(e.target.value)) } /> -
+ { /* Interaction */ } +
+
+ Interaction
-
- - setField('customparams', e.target.value) } /> +
+
+
+ + +
+
+ + setField('interactionModesCount', Number(e.target.value)) } /> +
+
+
+ + setField('customparams', e.target.value) } /> +
-
- FurniData.json -
-
- - setField('revision', Number(e.target.value)) } /> -
-
- - setField('category', e.target.value) } /> -
-
- - setField('offerid', Number(e.target.value)) } /> -
-
- - setField('rentofferid', Number(e.target.value)) } /> -
-
- - setField('furniline', e.target.value) } /> -
-
- - setField('environment', e.target.value) } /> -
-
-
- { [ - ['buyout', 'Buyout'], - ['rentbuyout', 'Rent Buyout'], - ['bc', 'BC'], - ['excludeddynamic', 'Excl. Dynamic'], - ['rare', 'Rare'] - ].map(([ key, label ]) => ( - - )) } -
+ { /* Create Button */ } +
+
- - - - ); }; diff --git a/src/components/furni-editor/views/FurniEditorEditView.tsx b/src/components/furni-editor/views/FurniEditorEditView.tsx index 71f539d..581bfbb 100644 --- a/src/components/furni-editor/views/FurniEditorEditView.tsx +++ b/src/components/furni-editor/views/FurniEditorEditView.tsx @@ -1,5 +1,7 @@ import { FC, useCallback, useEffect, useState } from 'react'; -import { Button, Column, Flex, Text } from '../../../common'; +import { FaSave, FaSync, FaTrash, FaArrowLeft } from 'react-icons/fa'; +import { Column } from '../../../common'; +import { LayoutFurniIconImageView } from '../../../common/layout/LayoutFurniIconImageView'; import { CatalogRef, FurniDetail } from '../../../hooks/furni-editor'; interface FurniEditorEditViewProps @@ -9,20 +11,24 @@ interface FurniEditorEditViewProps furniDataEntry: Record | null; interactions: string[]; loading: boolean; + lastResult: { success: boolean; message: string; id: number } | null; onUpdate: (id: number, fields: Record) => void; onDelete: (id: number) => void; onBack: () => void; onRefresh: (id: number) => void; } +const ic = 'text-[13px] border border-[#c5cdd6] rounded px-2 py-1 bg-white focus:outline-none focus:border-[#1e7295] focus:shadow-[0_0_0_1px_rgba(30,114,149,0.15)] transition-all w-full'; +const ro = 'text-[13px] border border-[#d5dbe0] rounded px-2 py-1 bg-[#f0f2f4] text-[#777] w-full cursor-not-allowed'; +const lb = 'text-[11px] text-[#1e7295] uppercase font-bold tracking-wider leading-none'; +const sectionTitle = 'text-[12px] text-[#1e7295] uppercase font-bold tracking-wider'; + export const FurniEditorEditView: FC = props => { - const { item, catalogItems, furniDataEntry, interactions, loading, onUpdate, onDelete, onBack, onRefresh } = props; + const { item, catalogItems, furniDataEntry, interactions, loading, lastResult, onUpdate, onDelete, onBack, onRefresh } = props; const [ form, setForm ] = useState({ - itemName: '', publicName: '', - spriteId: 0, type: 's', width: 1, length: 1, @@ -55,15 +61,14 @@ export const FurniEditorEditView: FC = props => }); const [ confirmDelete, setConfirmDelete ] = useState(false); + const [ toast, setToast ] = useState<{ type: 'success' | 'error'; message: string } | null>(null); useEffect(() => { if(!item) return; setForm({ - itemName: item.itemName || '', publicName: item.publicName || '', - spriteId: item.spriteId || 0, type: item.type || 's', width: item.width || 1, length: item.length || 1, @@ -98,6 +103,17 @@ export const FurniEditorEditView: FC = props => setConfirmDelete(false); }, [ item ]); + useEffect(() => + { + if(!lastResult) return; + + setToast({ type: lastResult.success ? 'success' : 'error', message: lastResult.message }); + if(lastResult.success && lastResult.id > 0) onRefresh(lastResult.id); + + const timer = setTimeout(() => setToast(null), 3000); + return () => clearTimeout(timer); + }, [ lastResult ]); + const setField = useCallback((key: string, value: unknown) => { setForm(prev => ({ ...prev, [key]: value })); @@ -111,71 +127,84 @@ export const FurniEditorEditView: FC = props => const handleDelete = useCallback(() => { if(!confirmDelete) return setConfirmDelete(true); - onDelete(item.id); - onBack(); - }, [ confirmDelete, item, onDelete, onBack ]); - - const inputClass = 'form-control form-control-sm'; - const labelClass = 'text-[11px] font-bold text-[#333] mb-0'; + }, [ confirmDelete, item, onDelete ]); return ( - - - - - ID: { item.id } - | - Sprite: { item.spriteId } - - ({ item.usageCount } in use) - + + { toast && +
+ { toast.message } +
+ } + + { /* Header */ } +
+
+ +
+
+
{ item.publicName }
+
+ { navigator.clipboard.writeText(String(item.id)); setToast({ type: 'success', message: `ID ${item.id} copied!` }); } }>#{item.id} + | + sprite:{ item.spriteId } + | + { item.itemName } + + { item.type === 's' ? 'FLOOR' : 'WALL' } + + { item.usageCount > 0 && { item.usageCount } in use } +
+
+
+ + +
+
{ /* Basic Info */ } -
- Basic Info -
-
- - setField('itemName', e.target.value) } /> -
-
- - setField('publicName', e.target.value) } /> -
-
- -