/* Copyright © XlXi 2022 */ import { Component, createRef, createElement } from 'react'; import axios from 'axios'; import classNames from 'classnames/bind'; import { buildGenericApiUrl } from '../util/HTTP.js'; import ProgressiveImage from './ProgressiveImage'; import Loader from './Loader'; import ThumbnailTool from './ThumbnailTool'; axios.defaults.withCredentials = true; const brickColors = [ {id: 1, name: 'White'}, {id: 2, name: 'Grey'}, {id: 3, name: 'Light yellow'}, {id: 5, name: 'Brick yellow'}, {id: 6, name: 'Light green (Mint)'}, {id: 9, name: 'Light reddish violet'}, {id: 11, name: 'Pastel Blue'}, {id: 12, name: 'Light orange brown'}, {id: 18, name: 'Nougat'}, {id: 21, name: 'Bright red'}, {id: 22, name: 'Med. reddish violet'}, {id: 23, name: 'Bright blue'}, {id: 24, name: 'Bright yellow'}, {id: 25, name: 'Earth orange'}, {id: 26, name: 'Black'}, {id: 27, name: 'Dark grey'}, {id: 28, name: 'Dark green'}, {id: 29, name: 'Medium green'}, {id: 36, name: 'Lig. Yellowich orange'}, {id: 37, name: 'Bright green'}, {id: 38, name: 'Dark orange'}, {id: 39, name: 'Light bluish violet'}, {id: 40, name: 'Transparent'}, {id: 41, name: 'Tr. Red'}, {id: 42, name: 'Tr. Lg blue'}, {id: 43, name: 'Tr. Blue'}, {id: 44, name: 'Tr. Yellow'}, {id: 45, name: 'Light blue'}, {id: 47, name: 'Tr. Flu. Reddish orange'}, {id: 48, name: 'Tr. Green'}, {id: 49, name: 'Tr. Flu. Green'}, {id: 50, name: 'Phosph. White'}, {id: 100, name: 'Light red'}, {id: 101, name: 'Medium red'}, {id: 102, name: 'Medium blue'}, {id: 103, name: 'Light grey'}, {id: 104, name: 'Bright violet'}, {id: 105, name: 'Br. yellowish orange'}, {id: 106, name: 'Bright orange'}, {id: 107, name: 'Bright bluish green'}, {id: 108, name: 'Earth yellow'}, {id: 110, name: 'Bright bluish violet'}, {id: 111, name: 'Tr. Brown'}, {id: 112, name: 'Medium bluish violet'}, {id: 113, name: 'Tr. Medi. reddish violet'}, {id: 115, name: 'Med. yellowish green'}, {id: 116, name: 'Med. bluish green'}, {id: 118, name: 'Light bluish green'}, {id: 119, name: 'Br. yellowish green'}, {id: 120, name: 'Lig. yellowish green'}, {id: 121, name: 'Med. yellowish orange'}, {id: 123, name: 'Br. reddish orange'}, {id: 124, name: 'Bright reddish violet'}, {id: 125, name: 'Light orange'}, {id: 126, name: 'Tr. Bright bluish violet'}, {id: 127, name: 'Gold'}, {id: 128, name: 'Dark nougat'}, {id: 131, name: 'Silver'}, {id: 133, name: 'Neon orange'}, {id: 134, name: 'Neon green'}, {id: 135, name: 'Sand blue'}, {id: 136, name: 'Sand violet'}, {id: 137, name: 'Medium orange'}, {id: 138, name: 'Sand yellow'}, {id: 140, name: 'Earth blue'}, {id: 141, name: 'Earth green'}, {id: 143, name: 'Tr. Flu. Blue'}, {id: 145, name: 'Sand blue metallic'}, {id: 146, name: 'Sand violet metallic'}, {id: 147, name: 'Sand yellow metallic'}, {id: 148, name: 'Dark grey metallic'}, {id: 149, name: 'Black metallic'}, {id: 150, name: 'Light grey metallic'}, {id: 151, name: 'Sand green'}, {id: 153, name: 'Sand red'}, {id: 154, name: 'Dark red'}, {id: 157, name: 'Tr. Flu. Yellow'}, {id: 158, name: 'Tr. Flu. Red'}, {id: 168, name: 'Gun metallic'}, {id: 176, name: 'Red flip/flop'}, {id: 178, name: 'Yellow flip/flop'}, {id: 179, name: 'Silver flip/flop'}, {id: 180, name: 'Curry'}, {id: 190, name: 'Fire Yellow'}, {id: 191, name: 'Flame yellowish orange'}, {id: 192, name: 'Reddish brown'}, {id: 193, name: 'Flame reddish orange'}, {id: 194, name: 'Medium stone grey'}, {id: 195, name: 'Royal blue'}, {id: 196, name: 'Dark Royal blue'}, {id: 198, name: 'Bright reddish lilac'}, {id: 199, name: 'Dark stone grey'}, {id: 200, name: 'Lemon metalic'}, {id: 208, name: 'Light stone grey'}, {id: 209, name: 'Dark Curry'}, {id: 210, name: 'Faded green'}, {id: 211, name: 'Turquoise'}, {id: 212, name: 'Light Royal blue'}, {id: 213, name: 'Medium Royal blue'}, {id: 216, name: 'Rust'}, {id: 217, name: 'Brown'}, {id: 218, name: 'Reddish lilac'}, {id: 219, name: 'Lilac'}, {id: 220, name: 'Light lilac'}, {id: 221, name: 'Bright purple'}, {id: 222, name: 'Light purple'}, {id: 223, name: 'Light pink'}, {id: 224, name: 'Light brick yellow'}, {id: 225, name: 'Warm yellowish orange'}, {id: 226, name: 'Cool yellow'}, {id: 232, name: 'Dove blue'}, {id: 268, name: 'Medium lilac'}, {id: 301, name: 'Slime green'}, {id: 302, name: 'Smoky grey'}, {id: 303, name: 'Dark blue'}, {id: 304, name: 'Parsley green'}, {id: 305, name: 'Steel blue'}, {id: 306, name: 'Storm blue'}, {id: 307, name: 'Lapis'}, {id: 308, name: 'Dark indigo'}, {id: 309, name: 'Sea green'}, {id: 310, name: 'Shamrock'}, {id: 311, name: 'Fossil'}, {id: 312, name: 'Mulberry'}, {id: 313, name: 'Forest green'}, {id: 314, name: 'Cadet blue'}, {id: 315, name: 'Electric blue'}, {id: 316, name: 'Eggplant'}, {id: 317, name: 'Moss'}, {id: 318, name: 'Artichoke'}, {id: 319, name: 'Sage green'}, {id: 320, name: 'Ghost grey'}, {id: 321, name: 'Lilac'}, {id: 322, name: 'Plum'}, {id: 323, name: 'Olivine'}, {id: 324, name: 'Laurel green'}, {id: 325, name: 'Quill grey'}, {id: 327, name: 'Crimson'}, {id: 328, name: 'Mint'}, {id: 329, name: 'Baby blue'}, {id: 330, name: 'Carnation pink'}, {id: 331, name: 'Persimmon'}, {id: 332, name: 'Maroon'}, {id: 333, name: 'Gold'}, {id: 334, name: 'Daisy orange'}, {id: 335, name: 'Pearl'}, {id: 336, name: 'Fog'}, {id: 337, name: 'Salmon'}, {id: 338, name: 'Terra Cotta'}, {id: 339, name: 'Cocoa'}, {id: 340, name: 'Wheat'}, {id: 341, name: 'Buttermilk'}, {id: 342, name: 'Mauve'}, {id: 343, name: 'Sunrise'}, {id: 344, name: 'Tawny'}, {id: 345, name: 'Rust'}, {id: 346, name: 'Cashmere'}, {id: 347, name: 'Khaki'}, {id: 348, name: 'Lily white'}, {id: 349, name: 'Seashell'}, {id: 350, name: 'Burgundy'}, {id: 351, name: 'Cork'}, {id: 352, name: 'Burlap'}, {id: 353, name: 'Beige'}, {id: 354, name: 'Oyster'}, {id: 355, name: 'Pine Cone'}, {id: 356, name: 'Fawn brown'}, {id: 357, name: 'Hurricane grey'}, {id: 358, name: 'Cloudy grey'}, {id: 359, name: 'Linen'}, {id: 360, name: 'Copper'}, {id: 361, name: 'Dirt brown'}, {id: 362, name: 'Bronze'}, {id: 363, name: 'Flint'}, {id: 364, name: 'Dark taupe'}, {id: 365, name: 'Burnt Sienna'}, {id: 1001, name: 'Institutional white'}, {id: 1002, name: 'Mid gray'}, {id: 1003, name: 'Really black'}, {id: 1004, name: 'Really red'}, {id: 1005, name: 'Deep orange'}, {id: 1006, name: 'Alder'}, {id: 1007, name: 'Dusty Rose'}, {id: 1008, name: 'Olive'}, {id: 1009, name: 'New Yeller'}, {id: 1010, name: 'Really blue'}, {id: 1011, name: 'Navy blue'}, {id: 1012, name: 'Deep blue'}, {id: 1013, name: 'Cyan'}, {id: 1014, name: 'CGA brown'}, {id: 1015, name: 'Magenta'}, {id: 1016, name: 'Pink'}, {id: 1017, name: 'Deep orange'}, {id: 1018, name: 'Teal'}, {id: 1019, name: 'Toothpaste'}, {id: 1020, name: 'Lime green'}, {id: 1021, name: 'Camo'}, {id: 1022, name: 'Grime'}, {id: 1023, name: 'Lavender'}, {id: 1024, name: 'Pastel light blue'}, {id: 1025, name: 'Pastel orange'}, {id: 1026, name: 'Pastel violet'}, {id: 1027, name: 'Pastel blue-green'}, {id: 1028, name: 'Pastel green'}, {id: 1029, name: 'Pastel yellow'}, {id: 1030, name: 'Pastel brown'}, {id: 1031, name: 'Royal purple'}, {id: 1032, name: 'Hot pink'} ]; class EditorItemCard extends Component { constructor(props) { super(props); this.state = { worn: false }; this.wearAssetId = this.wearAssetId.bind(this); } componentDidMount() { if(this.props.item.Wearing) this.setState({ worn: true }); } wearAssetId(assetId) { this.setState({ worn: !this.state.worn }); if(this.state.worn) this.props.unwearAssetId(assetId); else this.props.wearAssetId(assetId); } render() { var item = this.props.item; return (

{ item.Name }

); } } class WardrobeTab extends Component { constructor(props) { super(props); this.state = { loaded: false, assetTabs: [ {label: 'Heads', typeId: 17}, {label: 'Faces', typeId: 18}, {label: 'Hats', typeId: 8}, {label: 'T-Shirts', typeId: 2}, {label: 'Shirts', typeId: 11}, {label: 'Pants', typeId: 12}, {label: 'Gear', typeId: 19}, {label: 'Torsos', typeId: 27}, {label: 'Left Arms', typeId: 29}, {label: 'Right Arms', typeId: 28}, {label: 'Left Legs', typeId: 30}, {label: 'Right Legs', typeId: 31}, {label: 'Packages', typeId: 32} ], pageKey: 0, pageNumber: 1, pages: 0, items: [] }; this.setTypeId = this.setTypeId.bind(this); this.incrementPage = this.incrementPage.bind(this); this.loadPage = this.loadPage.bind(this); this.refresh = this.refresh.bind(this); } componentDidMount() { this.setTypeId(8, true); // XlXi: Bypass needed, else the initial page load isn't going to happen. } setTypeId(assetTypeId, forceLoad = false) { this.setState({ selectedTypeId: assetTypeId}, function() { this.loadPage(1, !forceLoad); }); } refresh() { this.loadPage(this.state.pageNumber); } incrementPage(amount) { this.loadPage(this.state.pageNumber + amount); } loadPage(pageNum, noBypass = true) { if(!this.state.loaded && noBypass) return; this.setState({ loaded: false }); let oldPageNum = this.state.pageNumber; this.setState({ pageNumber: pageNum }); axios.get(buildGenericApiUrl('api', `avatar/v1/list?assetTypeId=${this.state.selectedTypeId}${ pageNum > 1 ? '&page=' + pageNum : '' }`)) .then(res => { const items = res.data; let newKey = this.state.pageKey + 1; this.setState({ pageKey: newKey, items: items.data, pages: items.pages, loaded: true }); }) .catch(() => { this.props.setError('Error loading wardrobe page.'); this.setState({ pageNumber: oldPageNum, loaded: true }); }); } render() { return ( <> { !this.state.loaded ?
: null } { this.state.items.length == 0 ?

Nothing found.

:
{ this.state.items.map((item, index) => ) }
} { this.state.pages > 1 ? : null } ); } } class OutfitsTab extends Component { constructor(props) { super(props); } render() { return (<>

outfits

); } } // TODO: XlXi: Move this out of this component. I was too lazy to do it initially but it can be done. class BodyColorPaneBodyPart extends Component { constructor(props) { super(props); this.state = { color: 'vb-bc-194' } this.setColor = this.setColor.bind(this); } setColor(color) { this.setState({ color: color }); } render() { return ( ) } } class BodyColorSelectionModal extends Component { constructor(props) { super(props); this.state = { }; this.ModalRef = createRef(); this.Modal = null; } componentDidMount() { this.Modal = new Bootstrap.Modal(this.ModalRef.current); this.Modal.show(); this.ModalRef.current.addEventListener('hidden.bs.modal', (event) => { this.props.setModal(null); }); var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="bc-tooltip"]')); var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) { return new Bootstrap.Tooltip(tooltipTriggerEl) }); } componentWillUnmount() { this.Modal.dispose(); } render() { return (
Set Color
{ brickColors.map(({ id, name }) =>
this.props.setBodyColor(id) } data-bs-dismiss="modal" data-bs-toggle="bc-tooltip" data-bs-placement="top" title="" data-bs-original-title={ name }>
) }
); } } class BodyColorPane extends Component { constructor(props) { super(props); this.state = { loading: true, showModal: false }; this.bodyParts = [ {name: 'Head', className: 'head', ref: createRef()}, {name: 'Torso', className: 'torso', ref: createRef()}, {name: 'RightArm', className: 'limb', ref: createRef()}, {name: 'LeftArm', className: 'limb', ref: createRef()}, {name: 'RightLeg', className: 'limb', ref: createRef()}, {name: 'LeftLeg', className: 'limb', ref: createRef()} ]; this.findPart = this.findPart.bind(this); this.setModal = this.setModal.bind(this); this.load = this.load.bind(this); this.openColorModal = this.openColorModal.bind(this); this.setBodyColor = this.setBodyColor.bind(this); this.getBodyPartElem = this.getBodyPartElem.bind(this); } findPart(name) { return this.bodyParts.find(obj => { return obj.name === name }); } setModal(modal = null) { this.visibleModal = modal; if(modal) { this.setState({'showModal': true}); } else { this.setState({'showModal': false}); } } load() { this.setState({ loading: true }); axios.get(buildGenericApiUrl('api', 'avatar/v1/body-color')) .then(res => { Object.keys(res.data.data).map(key => { let part = this.findPart(key); part.ref.current.setColor(`vb-bc-${ res.data.data[key] }`); }); this.setState({ loading: false }); }) .catch(() => { this.props.setError('Error loading body colors pane.'); this.setState({ loading: false }); }); } componentDidMount() { this.load(); } openColorModal(name) { this.setModal( this.setBodyColor(name, color) } />); } setBodyColor(name, color) { let part = this.findPart(name); part.ref.current.setColor(`vb-bc-${ color }`); this.props.setBodyColor(name, color); } getBodyPartElem(name) { let part = this.findPart(name); if(!part.elem) { let elem = createElement( BodyColorPaneBodyPart, { className: part.className, onClick: (() => this.openColorModal(part.name)), ref: part.ref, key: part.name } ); part.elem = elem; return elem } return part.elem; } render() { return ( <> { this.state.loading ?
: null } { this.getBodyPartElem('Head') }
{ this.getBodyPartElem('RightArm') } { this.getBodyPartElem('Torso') } { this.getBodyPartElem('LeftArm') }
{ this.getBodyPartElem('RightLeg') } { this.getBodyPartElem('LeftLeg') }
{ this.state.showModal ? this.visibleModal : null } ); } } class WearingPane extends Component { constructor(props) { super(props); this.state = { loading: true, items: [], pageKey: 0 }; this.load = this.load.bind(this); } componentDidMount() { this.load(); } load() { this.setState({ loading: true }); axios.get(buildGenericApiUrl('api', 'avatar/v1/wearing')) .then(res => { const items = res.data; let newKey = this.state.pageKey + 1; this.setState({ pageKey: newKey, items: items.data, loading: false }); }) .catch(() => { this.props.setError('Error loading wearing pane.'); this.setState({ pageNumber: oldPageNum, loading: false }); }); } render() { return ( <> { (this.state.loading) ?
: null } { this.state.items.length == 0 ?

Nothing found.

:
{ this.state.items.map((item, index) => ) }
} ); } } class AvatarEditor extends Component { constructor(props) { super(props); this.state = { loading: false, thumbnailLoading: false, tabLoaded: false, tabs: [ {label: 'Wardrobe', name: 'wardrobe', ref: createRef()}, {label: 'Outfits', name: 'outfits', ref: createRef()} ], error: null, thumbKey: 0 }; this.tabIndex = 0; this.wearingPane = createRef(); this.bodyColorPane = createRef(); this.tabPane = createRef(); this.setCurrentTab = this.setCurrentTab.bind(this); this.setTab = this.setTab.bind(this); this.redrawCharacter = this.redrawCharacter.bind(this); this.reloadCharacter = this.reloadCharacter.bind(this); this.setError = this.setError.bind(this); this.thumbCallback = this.thumbCallback.bind(this); this.wearAssetId = this.wearAssetId.bind(this); this.unwearAssetId = this.unwearAssetId.bind(this); this.setBodyColor = this.setBodyColor.bind(this); } componentDidMount() { this.setTab('wardrobe'); } setCurrentTab(instance) { this.currentTab = instance; this.setState({tabLoaded: true}); } setTab(tabType) { this.setState({tabLoaded: false}); this.tabIndex += 1; let component = { wardrobe: WardrobeTab, outfits: OutfitsTab }[tabType]; this.setCurrentTab(createElement( component, { setError: this.setError, reloadCharacter: this.reloadCharacter, wearAssetId: this.wearAssetId, unwearAssetId: this.unwearAssetId, ref: this.tabPane, key: this.tabIndex } )); this.state.tabs.map(({ name, ref }) => { if(name == tabType) ref.current.classList.add('active'); else ref.current.classList.remove('active'); }); } redrawCharacter() { if(this.state.loading) return; this.setState({ loading: true }); axios.post(buildGenericApiUrl('api', 'avatar/v1/redraw')) .then(res => { this.reloadCharacter(); this.setState({ loading: false }); }) .catch(() => { this.setError('An error occurred while redrawing.'); this.setState({ loading: false }); }); } setError(state) { this.setState({ error: state }); setTimeout(function(){ this.setState({ error: null }); }.bind(this), 2000); } reloadCharacter() { let thumbKey = this.state.thumbKey + 1; this.wearingPane.current.load(); this.bodyColorPane.current.load(); this.tabPane.current.refresh(); this.setState({ thumbKey: thumbKey }); } thumbCallback(loading) { this.setState({ thumbnailLoading: loading }); } wearAssetId(assetId, callback) { if(this.state.thumbnailLoading) return; axios.post(buildGenericApiUrl('api', `avatar/v1/wear?id=${ assetId }`)) .then(res => { this.reloadCharacter(); if(callback) callback(); }) .catch(() => { this.setError('An error occurred while trying to wear this item.'); }); } unwearAssetId(assetId, callback) { if(this.state.thumbnailLoading) return; axios.post(buildGenericApiUrl('api', `avatar/v1/unwear?id=${ assetId }`)) .then(res => { this.reloadCharacter(); if(callback) callback(); }) .catch(() => { this.setError('An error occurred while trying to take off this item.'); }); } setBodyColor(name, color) { if(this.state.thumbnailLoading) return; axios.post(buildGenericApiUrl('api', `avatar/v1/set-body-color?part=${ name }&color=${ color }`)) .then(res => { this.reloadCharacter(); }) .catch(() => { this.setError('An error occurred while trying to change body color.'); }); } render() { return ( <> { this.state.error ?
{ this.state.error }
: null }
{ this.state.loading ?
: }

Is something wrong with your avatar?

Click here to re-draw it!

Colors

{ this.state.tabLoaded ? this.currentTab : }

Currently Wearing

); } } export default AvatarEditor;