Enable character JSON file import and dynamic data loading

This commit is contained in:
2025-07-06 18:25:51 +02:00
parent a910e41588
commit 2b45b9b9f6
5 changed files with 86 additions and 296 deletions

View File

@@ -0,0 +1 @@
{"clientVersion":"1.5.2","dateCreated":"2025-06-27T19:06:06.297Z","dateModified":"2025-06-27T21:24:26.013Z","id":"H_1751051166297","phase":3,"locale":"de-DE","name":"Faelyn Eichenhauch","ap":{"total":1400},"el":"EL_5","r":"R_3","c":"C_5","p":"P_24","sex":"m","pers":{"family":"Eichenhauch","placeofbirth":"Fasar","age":"57","haircolor":8,"eyecolor":11,"size":"188","weight":"82","socialstatus":2,"cultureAreaKnowledge":"Fasar / Punin"},"attr":{"values":[{"id":"ATTR_1","value":12},{"id":"ATTR_2","value":14},{"id":"ATTR_3","value":14},{"id":"ATTR_4","value":12},{"id":"ATTR_5","value":10},{"id":"ATTR_6","value":13},{"id":"ATTR_7","value":14},{"id":"ATTR_8","value":11}],"attributeAdjustmentSelected":"ATTR_3","ae":0,"kp":0,"lp":0,"permanentAE":{"lost":0,"redeemed":0},"permanentKP":{"lost":0,"redeemed":0},"permanentLP":{"lost":0}},"activatable":{"ADV_50":[{}],"ADV_48":[],"ADV_11":[],"ADV_70":[{}],"ADV_6":[{"tier":1}],"ADV_13":[{"tier":1}],"ADV_18":[{"sid":4},{"sid":3}],"ADV_37":[{}],"ADV_23":[{"tier":1}],"DISADV_5":[],"DISADV_37":[{"sid":8}],"DISADV_46":[{}],"DISADV_44":[],"DISADV_36":[{"sid":1}],"DISADV_25":[],"DISADV_7":[],"DISADV_48":[],"DISADV_26":[],"SA_27":[{"sid":9},{"sid":14},{"sid":7}],"SA_29":[{"sid":8,"tier":4},{"sid":23,"tier":2},{"sid":1,"tier":2}],"SA_1":[{}],"SA_40":[{}],"SA_9":[{"sid2":2,"sid":"TAL_28"}],"SA_12":[{"sid":9}],"SA_1011":[{}],"SA_22":[{"sid":"Festum"},{"sid":"Waldgebiet westlich von Punin"},{"sid":"Punin"}],"SA_1035":[{}],"SA_70":[{"sid":"SPELL_30"}],"SA_76":[{}],"SA_681":[],"SA_83":[{}]},"talents":{"TAL_4":7,"TAL_8":5,"TAL_10":6,"TAL_20":5,"TAL_23":7,"TAL_27":5,"TAL_34":5,"TAL_36":10,"TAL_40":4,"TAL_41":5,"TAL_42":4,"TAL_47":3,"TAL_54":4,"TAL_16":4,"TAL_44":1,"TAL_18":4,"TAL_19":4,"TAL_32":3,"TAL_33":6,"TAL_46":1,"TAL_37":1,"TAL_38":6,"TAL_39":2,"TAL_11":1,"TAL_28":12,"TAL_3":5,"TAL_24":4,"TAL_30":5,"TAL_29":6,"TAL_50":5,"TAL_51":8,"TAL_48":2},"ct":{"CT_3":11,"CT_13":13},"spells":{"SPELL_6":7,"SPELL_7":5,"SPELL_9":5,"SPELL_20":6,"SPELL_30":5,"SPELL_35":6,"SPELL_40":6,"SPELL_113":6,"SPELL_81":5,"SPELL_43":6},"cantrips":["CANTRIP_1","CANTRIP_46","CANTRIP_4","CANTRIP_17","CANTRIP_34","CANTRIP_35","CANTRIP_38"],"liturgies":{},"blessings":[],"belongings":{"items":{},"armorZones":{},"purse":{"d":"","s":"","h":"","k":""}},"rules":{"higherParadeValues":0,"attributeValueLimit":false,"enableAllRuleBooks":true,"enabledRuleBooks":[],"enableLanguageSpecializations":false},"pets":{}}

View File

@@ -3,6 +3,7 @@ import Main from "./modules/main/components/Main.tsx";
import Footer from "./modules/footer/components/Footer.tsx";
import {useEffect, useState} from "react";
import type {CharacterData} from "./types/CharacterJson.ts";
import Header from "./modules/header/components/Header.tsx";
function App() {
const [jsonData, setJsonData] = useState<CharacterData | null>(null);
@@ -11,8 +12,12 @@ function App() {
useEffect(() => {
const loadData = async () => {
try {
const data = await import('./assets/data/Faelyn Eichenhauch.json');
setJsonData(data.default);
const response = await fetch('/data/Faelyn Eichenhauch.json');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
setJsonData(data);
} catch (error) {
console.error('Error loading JSON file:', error);
} finally {
@@ -23,15 +28,24 @@ function App() {
loadData();
}, []);
const handleFileLoad = (data: CharacterData) => {
setJsonData(data);
setLoading(false);
};
if (loading) return <div>Loading...</div>;
if (!jsonData) return <div>Error loading JSON file.</div>;
if (!jsonData) return (
<>
<Header onFileLoad={handleFileLoad}/>
<Footer/>
</>
);
return (
<>
{/*<Header/>*/}
<Main jsonData={jsonData}/>
<Footer/>

View File

@@ -1,284 +0,0 @@
{
"clientVersion": "1.5.2",
"dateCreated": "2025-06-27T19:06:06.297Z",
"dateModified": "2025-06-27T21:24:26.013Z",
"id": "H_1751051166297",
"phase": 3,
"locale": "de-DE",
"name": "Faelyn Eichenhauch",
"ap": {
"total": 1400
},
"el": "EL_5",
"r": "R_3",
"c": "C_5",
"p": "P_24",
"sex": "m",
"pers": {
"family": "Eichenhauch",
"placeofbirth": "Fasar",
"age": "57",
"haircolor": 8,
"eyecolor": 11,
"size": "188",
"weight": "82",
"socialstatus": 2,
"cultureAreaKnowledge": "Fasar / Punin"
},
"attr": {
"values": [
{
"id": "ATTR_1",
"value": 12
},
{
"id": "ATTR_2",
"value": 14
},
{
"id": "ATTR_3",
"value": 14
},
{
"id": "ATTR_4",
"value": 12
},
{
"id": "ATTR_5",
"value": 10
},
{
"id": "ATTR_6",
"value": 13
},
{
"id": "ATTR_7",
"value": 14
},
{
"id": "ATTR_8",
"value": 11
}
],
"attributeAdjustmentSelected": "ATTR_3",
"ae": 0,
"kp": 0,
"lp": 0,
"permanentAE": {
"lost": 0,
"redeemed": 0
},
"permanentKP": {
"lost": 0,
"redeemed": 0
},
"permanentLP": {
"lost": 0
}
},
"activatable": {
"ADV_50": [
{}
],
"ADV_48": [],
"ADV_11": [],
"ADV_70": [
{}
],
"ADV_6": [
{
"tier": 1
}
],
"ADV_13": [
{
"tier": 1
}
],
"ADV_18": [
{
"sid": 4
},
{
"sid": 3
}
],
"ADV_37": [
{}
],
"ADV_23": [
{
"tier": 1
}
],
"DISADV_5": [],
"DISADV_37": [
{
"sid": 8
}
],
"DISADV_46": [
{}
],
"DISADV_44": [],
"DISADV_36": [
{
"sid": 1
}
],
"DISADV_25": [],
"DISADV_7": [],
"DISADV_48": [],
"DISADV_26": [],
"SA_27": [
{
"sid": 9
},
{
"sid": 14
},
{
"sid": 7
}
],
"SA_29": [
{
"sid": 8,
"tier": 4
},
{
"sid": 23,
"tier": 2
},
{
"sid": 1,
"tier": 2
}
],
"SA_1": [
{}
],
"SA_40": [
{}
],
"SA_9": [
{
"sid2": 2,
"sid": "TAL_28"
}
],
"SA_12": [
{
"sid": 9
}
],
"SA_1011": [
{}
],
"SA_22": [
{
"sid": "Festum"
},
{
"sid": "Waldgebiet westlich von Punin"
},
{
"sid": "Punin"
}
],
"SA_1035": [
{}
],
"SA_70": [
{
"sid": "SPELL_30"
}
],
"SA_76": [
{}
],
"SA_681": [],
"SA_83": [
{}
]
},
"talents": {
"TAL_4": 7,
"TAL_8": 5,
"TAL_10": 6,
"TAL_20": 5,
"TAL_23": 7,
"TAL_27": 5,
"TAL_34": 5,
"TAL_36": 10,
"TAL_40": 4,
"TAL_41": 5,
"TAL_42": 4,
"TAL_47": 3,
"TAL_54": 4,
"TAL_16": 4,
"TAL_44": 1,
"TAL_18": 4,
"TAL_19": 4,
"TAL_32": 3,
"TAL_33": 6,
"TAL_46": 1,
"TAL_37": 1,
"TAL_38": 6,
"TAL_39": 2,
"TAL_11": 1,
"TAL_28": 12,
"TAL_3": 5,
"TAL_24": 4,
"TAL_30": 5,
"TAL_29": 6,
"TAL_50": 5,
"TAL_51": 8,
"TAL_48": 2
},
"ct": {
"CT_3": 11,
"CT_13": 13
},
"spells": {
"SPELL_6": 7,
"SPELL_7": 5,
"SPELL_9": 5,
"SPELL_20": 6,
"SPELL_30": 5,
"SPELL_35": 6,
"SPELL_40": 6,
"SPELL_113": 6,
"SPELL_81": 5,
"SPELL_43": 6
},
"cantrips": [
"CANTRIP_1",
"CANTRIP_46",
"CANTRIP_4",
"CANTRIP_17",
"CANTRIP_34",
"CANTRIP_35",
"CANTRIP_38"
],
"liturgies": {},
"blessings": [],
"belongings": {
"items": {},
"armorZones": {},
"purse": {
"d": "",
"s": "",
"h": "",
"k": ""
}
},
"rules": {
"higherParadeValues": 0,
"attributeValueLimit": false,
"enableAllRuleBooks": true,
"enabledRuleBooks": [],
"enableLanguageSpecializations": false
},
"pets": {}
}

View File

@@ -1,10 +1,15 @@
import ImportButton from "./ImportButton";
import type { CharacterData } from '../../../types/CharacterJson.ts';
export default function Header() {
interface HeaderProps {
onFileLoad: (data: CharacterData) => void;
}
export default function Header({ onFileLoad }: HeaderProps) {
return (
<header className="bg-white dark:bg-gray-800">
<nav className="container mx-auto flex justify-between items-center py-6 px-6">
<ImportButton/>
<ImportButton onFileLoad={onFileLoad}/>
</nav>
</header>
)

View File

@@ -1,6 +1,60 @@
export default function ImportButton() {
return (
<input type="file"
className="file:cursor-pointer file:mr-4 file:rounded-full file:border-0 file:bg-violet-50 file:px-4 file:py-2 file:text-sm file:font-semibold file:text-violet-700 hover:file:bg-violet-100 dark:file:bg-violet-600 dark:file:text-violet-100 dark:hover:file:bg-violet-500"/>
)
import { useRef } from 'react';
import type { CharacterData } from '../../../../types/CharacterJson.ts';
interface ImportButtonProps {
onFileLoad: (data: CharacterData) => void;
}
export default function ImportButton({ onFileLoad }: ImportButtonProps) {
const fileInputRef = useRef<HTMLInputElement>(null);
const handleFileChange = async (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0];
if (!file) return;
// Check if the file is a JSON file
if (file.type !== 'application/json') {
alert('Please select a JSON file');
return;
}
try {
// Read the file content
const fileContent = await file.text();
// Parse and validate the JSON
const parsedData = JSON.parse(fileContent) as CharacterData;
// Call the callback to update the parent component's state
onFileLoad(parsedData);
} catch (error) {
alert('Invalid JSON file. Please select a valid JSON file.');
console.error('Error processing file:', error);
}
// Reset the input
if (fileInputRef.current) {
fileInputRef.current.value = '';
}
};
return (
<div className="relative">
<input
ref={fileInputRef}
type="file"
accept=".json"
onChange={handleFileChange}
className="hidden"
id="character-import"
/>
<label
htmlFor="character-import"
className="cursor-pointer bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded-lg transition-colors duration-200"
>
Charakter laden
</label>
</div>
);
}