diff --git a/src/components/Editor.jsx b/src/components/Editor.jsx index ab6d65fc..fc668743 100644 --- a/src/components/Editor.jsx +++ b/src/components/Editor.jsx @@ -14,12 +14,13 @@ import { import styles from "./Editor.module.css" import Selector from "./Selector" import TraitInformation from "./TraitInformation" +import JsonAttributes from "./JsonAttributes" import { TokenBox } from "./token-box/TokenBox" import { LanguageContext } from "../context/LanguageContext" import MenuTitle from "./MenuTitle" -export default function Editor({confirmDialog,animationManager, blinkManager, lookatManager, effectManager}) { +export default function Editor({confirmDialog,animationManager, blinkManager, lookatManager, effectManager, jsonSelectionArray}) { const { currentTraitName, setCurrentTraitName, @@ -115,6 +116,7 @@ export default function Editor({confirmDialog,animationManager, blinkManager, lo + ) diff --git a/src/components/FileDropComponent.jsx b/src/components/FileDropComponent.jsx index bb9792d0..67a3b9ed 100644 --- a/src/components/FileDropComponent.jsx +++ b/src/components/FileDropComponent.jsx @@ -2,16 +2,16 @@ import React, { useEffect, useState } from 'react'; import styles from './FileDropComponent.module.css'; -export default function FileDropComponent ({onFileDrop}){ +export default function FileDropComponent ({onFilesDrop}){ const [isDragging, setIsDragging] = useState(false); useEffect(() => { const handleDrop = (event) => { event.preventDefault(); setIsDragging(false); - const file = event.dataTransfer.files[0]; - if (onFileDrop) { - onFileDrop(file); + const files = event.dataTransfer.files; + if (onFilesDrop) { + onFilesDrop(files); } }; diff --git a/src/components/JsonAttributes.jsx b/src/components/JsonAttributes.jsx new file mode 100644 index 00000000..f2fcda26 --- /dev/null +++ b/src/components/JsonAttributes.jsx @@ -0,0 +1,87 @@ +import React, {useEffect,useState,useContext} from "react" +import styles from "./JsonAttributes.module.css" +import { SceneContext } from "../context/SceneContext" +import MenuTitle from "./MenuTitle" + +export default function JsonAttributes({jsonSelectionArray}){ + const { + setSelectedOptions + } = useContext(SceneContext); + const [index, setIndex] = useState(0); + + useEffect(() => { + if (jsonSelectionArray?.length >0) + setSelectedOptions(jsonSelectionArray[0].options) + setIndex(0); + }, [jsonSelectionArray]) + + const nextJson = async () => { + if (index >= jsonSelectionArray.length -1){ + setSelectedOptions(jsonSelectionArray[0].options) + setIndex(0); + } + else{ + const newIndex = index + 1; + setSelectedOptions(jsonSelectionArray[newIndex].options) + setIndex(newIndex); + } + } + const prevJson = async () => { + console.log("prev") + if (index <= 0){ + setSelectedOptions(jsonSelectionArray[jsonSelectionArray.length].options) + setIndex(jsonSelectionArray.length -1); + } + else{ + const newIndex = index-1; + setSelectedOptions(jsonSelectionArray[newIndex].options) + setIndex(newIndex); + } + } + + return ( + jsonSelectionArray?.length > 0 ? ( +
+ +
+
+ {jsonSelectionArray?.length > 1 ?
:<>} + {jsonSelectionArray[index].name && ( +
+
+ {jsonSelectionArray[index].name} +
+
+ )} + {jsonSelectionArray?.length > 1 ?
:<>} +
+ {jsonSelectionArray[index].thumb && ( + Selection Thumbnail + )} + {jsonSelectionArray[index].attributes.map((attribute) => ( +
+
+ {`${attribute.trait} : ${attribute.id}`} +
+
+ ))} +
+
+ ) : (<>) + ); +} \ No newline at end of file diff --git a/src/components/JsonAttributes.module.css b/src/components/JsonAttributes.module.css new file mode 100644 index 00000000..b50296f1 --- /dev/null +++ b/src/components/JsonAttributes.module.css @@ -0,0 +1,143 @@ + +.InformationContainerPos { + position: fixed; + right: 32px; + top: 98px; + width:350px; + height: -webkit-calc(100vh - 176px); + height: calc(100vh - 176px); + backdrop-filter: blur(22.5px); + background: rgba(5, 11, 14, 0.8); + z-index: 1000; + user-select: none; +} + +.scrollContainer { + height: 100%; + width: 80%; + overflow-y: scroll; + position: relative; + overflow-x: hidden !important; + margin: 30px; + height: -webkit-calc(100% - 40px); + height: calc(100% - 40px); + padding-top: 5px; +} + +.traitInfoTitle { + color: white; + text-transform: uppercase; + text-shadow: 1px 1px 2px black; + font-size: 16px; + word-spacing: 2px; + margin-bottom: 10px; +} +.traitInfoText { + color: rgb(179, 179, 179); + /* text-transform: uppercase; */ + text-shadow: 1px 1px 2px black; + font-size: 16px; + word-spacing: 2px; + margin-bottom: 30px; +} +.input-box { + width: 60px; /* Adjust as needed */ + height: 20px; + color: #5eb086; + background-color:rgba(5, 11, 14, 0.5); + border: none; + font-size: medium; + font-weight: 500; + margin-left: 15px; +} +.input-box:focus { + outline: none; +} + +.input-box::selection { + background-color: #111f17; + color: #5eb086; +} + +.flexSelect{ + display: flex; + justify-content: space-between; + width: 100%; + height:40px; + align-items: center; +} + +.arrow-button { + + cursor: pointer; + overflow: hidden; + opacity: 0.8; + width: 32px; + height: 32px; + margin: 2px; + text-align: center; + outline-color: #3b434f; + outline-width: 2px; + outline-style: solid; + align-items: center; + margin-bottom: 10px; + background-color: #1e2530; +} +.left-button{ + background: url('/ui/backButton_small.png'); + background-position: center; + background-repeat: no-repeat; + background-size: cover; +} + +.right-button{ + background: url('/ui/nextButton_small.png'); + background-position: center; + background-repeat: no-repeat; + background-size: cover; +} + +.anim-button:hover { + opacity: 1; +} + +/* Hide the default checkbox */ +.custom-checkbox input[type="checkbox"] { + display: none; +} + +/* Style the custom checkbox */ +.custom-checkbox .checkbox-container { + display: inline-block; + width: 20px; + height: 20px; + border: 2px solid #284b39; /* Change border color as needed */ + border-radius: 5px; + cursor: pointer; +} + +.custom-checkbox .checkbox-container.checked { + background-color: #5eb086; /* Change background color when checked */ +} + +.custom-checkbox .checkbox-container .checkmark { + display: none; +} + +/* Style the checkmark when the checkbox is checked */ +.custom-checkbox input[type="checkbox"]:checked + .checkbox-container { + background-color: #5eb086; /* Change background color when checked */ +} + +.custom-checkbox input[type="checkbox"]:checked + .checkbox-container .checkmark { + display: block; +} + +.checkboxHolder { + display: flex; + gap: 30px; + align-items: center; + justify-content: center; + align-content: center; + height: 40px; +} \ No newline at end of file diff --git a/src/components/TraitInformation.jsx b/src/components/TraitInformation.jsx index c7f570ec..a2ae238f 100644 --- a/src/components/TraitInformation.jsx +++ b/src/components/TraitInformation.jsx @@ -127,15 +127,15 @@ export default function TraitInformation({currentVRM, animationManager, lookatMa Animation

-
+
{animationName}
diff --git a/src/components/TraitInformation.module.css b/src/components/TraitInformation.module.css index 5708bc45..9b7e2753 100644 --- a/src/components/TraitInformation.module.css +++ b/src/components/TraitInformation.module.css @@ -58,7 +58,7 @@ color: #5eb086; } -.animationSelect{ +.flexSelect{ display: flex; justify-content: space-between; width: 100%; @@ -66,7 +66,7 @@ align-items: center; } -.anim-button { +.arrow-button { cursor: pointer; overflow: hidden; diff --git a/src/library/option-utils.js b/src/library/option-utils.js index 790a961e..92649195 100644 --- a/src/library/option-utils.js +++ b/src/library/option-utils.js @@ -76,7 +76,7 @@ export function getTraitOption(traitId, traitName, template){ const trait = template.traits.find(trait => trait.trait === traitName) if (trait == null){ - console.warn("No trait with id: " + traitName + " was found."); + //console.warn("No trait with id: " + traitName + " was found."); return null; } let index = trait.collection?.findIndex(item => item.id === traitId); @@ -91,7 +91,8 @@ export function getTraitOption(traitId, traitName, template){ return getOption(key, trait, item, thumbnailBaseDir + item.thumbnail); } else{ - console.warn("No object with id: " + traitId + " was found in trait category " + traitName); + //console.warn(traitName + " : " + traitId); + console.log(traitName + " : " + traitId); return null; } diff --git a/src/pages/Appearance.jsx b/src/pages/Appearance.jsx index d01fcc91..9aba5c3a 100644 --- a/src/pages/Appearance.jsx +++ b/src/pages/Appearance.jsx @@ -28,6 +28,7 @@ function Appearance({ templateInfo, setSelectedOptions } = React.useContext(SceneContext) + const { playSound } = React.useContext(SoundContext) const { isMute } = React.useContext(AudioContext) @@ -36,6 +37,8 @@ function Appearance({ resetAvatar() setViewMode(ViewMode.CREATE) } + + const [jsonSelectionArray, setJsonSelectionArray] = React.useState(null) const next = () => { @@ -80,7 +83,8 @@ function Appearance({ // Translate hook const { t } = useContext(LanguageContext) - const handleFileDrop = async(file) => { + const handleFilesDrop = async(files) => { + const file = files[0]; // Check if the file has the .fbx extension if (file && file.name.toLowerCase().endsWith('.fbx')) { const animName = getFileNameWithoutExtension(file.name); @@ -90,48 +94,65 @@ function Appearance({ await animationManager.loadAnimation(path, true, "", animName); // Handle the dropped .fbx file } - if (file && file.name.toLowerCase().endsWith('.json')) { - console.log('Dropped .json file:', file); - const reader = new FileReader(); - - reader.onload = function(e) { - try { - const jsonContent = JSON.parse(e.target.result); // Parse the JSON content - // Now you can work with the JSON data in the 'jsonContent' variable - - const options = []; - jsonContent.attributes.forEach(attribute => { - if (attribute.trait_type != "BRACE") - options.push(getTraitOption(attribute.value, attribute.trait_type , templateInfo)); - }); - const filteredOptions = options.filter(element => element !== null); - - templateInfo.traits.map(trait => { - const coincidence = filteredOptions.some(option => option.trait.trait == trait.trait); - // find if trait.trait has coincidence in any of the filteredOptions[].trait - // if no coincidence was foud add to filteredOptions {item:null, trait:templateInfo.traits.find((t) => t.name === currentTraitName} - if (!coincidence) { - // If no coincidence was found, add to filteredOptions - filteredOptions.push({ item: null, trait: trait }); - } - }); - - if (filteredOptions.length > 0){ - setSelectedOptions(filteredOptions) - } - - + const filesArray = Array.from(files); + const jsonDataArray = []; + const processFile = (file) => { + return new Promise((resolve, reject) => { + if (file && file.name.toLowerCase().endsWith('.json')) { + const reader = new FileReader(); + const thumbLocation = `${templateInfo.assetsLocation}/anata/_thumbnails/t_${file.name.split('_')[0]}.jpg`; + const jsonName = file.name.split('.')[0]; + + reader.onload = function (e) { + try { + const jsonContent = JSON.parse(e.target.result); + const options = []; + const jsonAttributes = jsonContent.attributes.map((attribute) => ({ trait: attribute.trait_type, id: attribute.value })); + + jsonContent.attributes.forEach((attribute) => { + if (attribute.trait_type !== "BRACE") { + options.push(getTraitOption(attribute.value, attribute.trait_type, templateInfo)); + } + }); + + const filteredOptions = options.filter((element) => element !== null); + + templateInfo.traits.forEach((trait) => { + const coincidence = filteredOptions.some((option) => option.trait.trait === trait.trait); + if (!coincidence) { + filteredOptions.push({ item: null, trait: trait }); + } + }); + + const jsonSelection = { name: jsonName, thumb: thumbLocation, attributes: jsonAttributes, options: filteredOptions }; + jsonDataArray.push(jsonSelection); + + resolve(); // Resolve the promise when processing is complete + } catch (error) { + console.error("Error parsing the JSON file:", error); + reject(error); + } + }; - } catch (error) { - console.error("Error parsing the JSON file:", error); + reader.readAsText(file); } - }; - - reader.readAsText(file); // Read the file as text - - // Handle the dropped .fbx file - } + }); + }; + + // Use Promise.all to wait for all promises to resolve + Promise.all(filesArray.map(processFile)) + .then(() => { + if (jsonDataArray.length > 0){ + // This code will run after all files are processed + setJsonSelectionArray(jsonDataArray); + setSelectedOptions(jsonDataArray[0].options); + } + }) + .catch((error) => { + console.error("Error processing files:", error); + }); + }; return ( @@ -141,7 +162,7 @@ function Appearance({
{t("pageTitles.chooseAppearance")}