Slightly better ui, added react and typescript

This commit is contained in:
2022-06-06 14:22:05 +02:00
parent 3e1a54a49e
commit 7ea3b514ce
22 changed files with 13924 additions and 8367 deletions

50
src/components/logger.ts Normal file
View File

@@ -0,0 +1,50 @@
import electronLog from 'electron-log';
import {dataPath} from './paths';
import {dialog} from '@electron/remote';
import path from 'path';
import {exec} from "child_process";
electronLog.transports.console.format = "{h}:{i}:{s} {text}";
electronLog.transports.file.getFile();
electronLog.transports.file.resolvePath = () => path.join(dataPath, "logs", "main.log");
const logger = {
info: function (message:string) {
electronLog.info(message);
},
error: function (message:string) {
electronLog.error(message);
},
warning: function (message:string) {
electronLog.warn(message);
},
errorMSG: function (message:string) {
logger.error(message);
dialog.showMessageBox({
type: 'error',
buttons: ['Open Log', 'OK'],
defaultId: 1,
title: 'Something went wrong!',
message: 'An error has occurred:',
detail: message
}).then(res => {
if(res.response === 0) {
exec('start "" "' + path.join(dataPath, "logs") + '"');
}
});
},
warningMSG: function (message:string) {
logger.warning(message);
dialog.showMessageBox({
type: 'warning',
buttons: ['OK'],
defaultId: 1,
title: 'Warning!',
message: message
});
}
}
export default logger;

9
src/components/paths.ts Normal file
View File

@@ -0,0 +1,9 @@
import path from 'path';
import {app} from '@electron/remote';
export const dataPath = app.getPath('userData');
export const SettingPath = path.join(dataPath, "settings.xml");
export const blenderPath = path.join("assets", "blender-win", "blender");
export const templatePath = path.join("assets", "template.blend");
export const blenderScriptPath = path.join("assets", "blenderScript.py");

43
src/components/render.ts Normal file
View File

@@ -0,0 +1,43 @@
import {blenderPath, blenderScriptPath, SettingPath, templatePath} from "./paths";
import {exec} from "child_process";
import logger from "./logger";
import React from "react";
function Render(setStatusDisplay:React.Dispatch<React.SetStateAction<string>>, setLogNumber:React.Dispatch<React.SetStateAction<string>>) {
const blenderCons = exec('"' + blenderPath + '" "' + templatePath + '" --background --python "' + blenderScriptPath + '" -- "' + SettingPath.replaceAll('\\', '/') + '"', {maxBuffer: Infinity});
let frames = "0";
let lastFrame = "0";
let lastData = "Initializing...";
setStatusDisplay(lastData);
blenderCons.stdout?.on("data", (data:string) => {
logger.info(data);
if(data.startsWith("Frames:")) {
frames = data.split(":")[1];
} else if(data.startsWith("Fra:")) {
lastFrame = data.split(":")[1].split(" ")[0]
lastData = "Render Frame " + lastFrame + "/" + frames;
} else if(data.startsWith("Finished")) {
if(lastFrame == frames) {
lastData = "Finished Render Successfully!"
} else {
logger.errorMSG("Render Failed!");
}
} else if(data.includes("Blender quit")) {
if(lastData != "Finished Render Successfully!") {
logger.errorMSG("Render Failed!");
}
} else if(data.startsWith("Init:")) {
lastData = "Initialize Frame " + data.split(":")[1] + "/" + frames;
} else if(data.startsWith("Lognr:")) {
setLogNumber(data.split(":")[1]);
}
setStatusDisplay(lastData);
});
}
export default Render;

View File

@@ -0,0 +1,98 @@
import formatXML from "xml-formatter";
import {SettingPath} from './paths';
import fs from "fs";
import logger from "./logger";
function getXMLChild(doc:Document, child:string) {
return String(doc.getElementsByTagName(child)[0].childNodes[0].nodeValue);
}
const settingList = await fetch(SettingPath).then(function(response){
return response.text();
}).then(function(data){
const parser = new DOMParser();
const xmlDoc = parser.parseFromString(data, 'text/xml');
return {
fps: parseInt(getXMLChild(xmlDoc, "fps")),
width: parseInt(getXMLChild(xmlDoc, "width")),
stickDistance: parseInt(getXMLChild(xmlDoc, "stickDistance")),
stickMode2: (getXMLChild(xmlDoc, "stickMode2") === "true"),
log: getXMLChild(xmlDoc, "log"),
output: getXMLChild(xmlDoc, "output")
}
}).catch(function(error) {
logger.errorMSG(error);
return {
fps: 30,
width: 540,
stickDistance: 5,
stickMode2: true,
log: '"None"',
output: "None"
}
});
function settingListLoadDefault() {
updateSettings({
fps: 30,
width: 540,
stickDistance: 5,
stickMode2: true
});
}
function updateSettings(optiones:{fps?:number, width?:number, stickDistance?:number, stickMode2?:boolean, log?:string, output?:string}) {
if(optiones.fps === undefined) {
optiones.fps = settingList.fps;
} else {
settingList.fps = optiones.fps;
}
if(optiones.width === undefined) {
optiones.width = settingList.width;
} else {
settingList.width = optiones.width;
}
if(optiones.stickDistance === undefined) {
optiones.stickDistance = settingList.stickDistance;
} else {
settingList.stickDistance = optiones.stickDistance;
}
if(optiones.stickMode2 === undefined) {
optiones.stickMode2 = settingList.stickMode2;
} else {
settingList.stickMode2 = optiones.stickMode2;
}
if(optiones.log === undefined) {
optiones.log = settingList.log;
} else {
settingList.log = optiones.log;
}
if(optiones.output === undefined) {
optiones.output = settingList.output;
} else {
settingList.output = optiones.output;
}
const xmlStr = '<?xml version="1.0"?><settings><fps>' + optiones.fps +
'</fps><width>' + optiones.width +
'</width><stickDistance>' + optiones.stickDistance +
'</stickDistance><stickMode2>' + ((optiones.stickMode2)?"true":"false") +
'</stickMode2><log>' + optiones.log +
'</log><output>' + optiones.output +
'</output></settings>';
fs.writeFile(SettingPath, formatXML(xmlStr, {collapseContent: true}), function(err) {
if(err) {
logger.errorMSG(String(err));
}
});
}
export {
updateSettings,
settingListLoadDefault,
settingList
}

View File

@@ -0,0 +1,98 @@
import React, {useState, useEffect} from "react";
import { dialog } from "@electron/remote";
import { settingList, updateSettings } from "../settings";
import logger from "../logger";
import {exec} from "child_process";
import Render from "../render";
function MainSide() {
const [status, setStatus] = useState("Idle");
const [logNumber, setLogNumber] = useState("0");
const [logs, setLogs] = useState(settingList.log);
const [output, setOutput] = useState(settingList.output);
const [logTable, setLogTable] = useState(logs.substring(1).slice(0, -1).split('""').map((log, index) => {
return <tr key={index}>
<td>{log}</td>
</tr>
}));
useEffect(() => {
setLogTable(logs.substring(1).slice(0, -1).split('""').map((log, index) => {
return <tr key={index}>
<td>{log}</td>
</tr>
}));
}, [logs]);
return (
<div id="content">
<button onClick={() => Render(setStatus, setLogNumber)}>Start Render</button>
<p>{"Log " + logNumber + "/" + String(settingList.log.split("\"\"").length)}</p>
<div className="dataDiv">
<p>{status}</p>
<button onClick={() => openOutputFolder()}>Open Output Folder</button>
</div>
<hr/>
<div className="dataDiv">
<p>Logs:</p>
<table>
<tbody>
{logTable}
</tbody>
</table>
<button onClick={() => openLog(setLogs)}>Open Log</button>
</div>
<div className="dataDiv">
<p id="output">{"Output Folder: " + output}</p>
<button onClick={() => openVid(setOutput)}>Open Video</button>
</div>
</div>
)
}
function openLog(updateHook:React.Dispatch<React.SetStateAction<string>>) {
dialog.showOpenDialog({
properties: [
"multiSelections"
],
filters: [
{
name: "TX-Logs",
extensions: [
"csv"
]
}
]
}).then(result => {
let logStr = "";
result.filePaths.forEach(value => {
logStr += "\"" + String(value) + "\"";
});
updateSettings({log:logStr});
updateHook(logStr);
}).catch(err => {
logger.errorMSG(err);
});
}
function openVid(updateHook:React.Dispatch<React.SetStateAction<string>>) {
dialog.showOpenDialog({
properties: [
"openDirectory"
]
}).then(result => {
updateSettings({output:String(result.filePaths)});
updateHook(String(result.filePaths));
}).catch(err => {
logger.errorMSG(err);
});
}
function openOutputFolder() {
if(settingList.output == "None") {
logger.warningMSG("No output folder set!");
} else {
exec('start "" "' + settingList.output + '"');
}
}
export default MainSide;

View File

@@ -0,0 +1,36 @@
import React from "react";
import { openSide, Side } from "../../renderer";
const UpdateButton = () => (
<div id="update-available">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
{/* Font Awesome Pro 6.1.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. */}
<path
d="M480 352h-133.5l-45.25 45.25C289.2 409.3 273.1 416 256 416s-33.16-6.656-45.25-18.75L165.5 352H32c-17.67 0-32 14.33-32 32v96c0 17.67 14.33 32 32 32h448c17.67 0 32-14.33 32-32v-96C512 366.3 497.7 352 480 352zM432 456c-13.2 0-24-10.8-24-24c0-13.2 10.8-24 24-24s24 10.8 24 24C456 445.2 445.2 456 432 456zM233.4 374.6C239.6 380.9 247.8 384 256 384s16.38-3.125 22.62-9.375l128-128c12.49-12.5 12.49-32.75 0-45.25c-12.5-12.5-32.76-12.5-45.25 0L288 274.8V32c0-17.67-14.33-32-32-32C238.3 0 224 14.33 224 32v242.8L150.6 201.4c-12.49-12.5-32.75-12.5-45.25 0c-12.49 12.5-12.49 32.75 0 45.25L233.4 374.6z" />
</svg>
</div>
)
const MainSideButtons = () => (
<div id="settings-button" onClick={() => openSide(Side.Settings)}>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
{/* Font Awesome Pro 6.1.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. */}
<path
d="M495.9 166.6C499.2 175.2 496.4 184.9 489.6 191.2L446.3 230.6C447.4 238.9 448 247.4 448 256C448 264.6 447.4 273.1 446.3 281.4L489.6 320.8C496.4 327.1 499.2 336.8 495.9 345.4C491.5 357.3 486.2 368.8 480.2 379.7L475.5 387.8C468.9 398.8 461.5 409.2 453.4 419.1C447.4 426.2 437.7 428.7 428.9 425.9L373.2 408.1C359.8 418.4 344.1 427 329.2 433.6L316.7 490.7C314.7 499.7 307.7 506.1 298.5 508.5C284.7 510.8 270.5 512 255.1 512C241.5 512 227.3 510.8 213.5 508.5C204.3 506.1 197.3 499.7 195.3 490.7L182.8 433.6C167 427 152.2 418.4 138.8 408.1L83.14 425.9C74.3 428.7 64.55 426.2 58.63 419.1C50.52 409.2 43.12 398.8 36.52 387.8L31.84 379.7C25.77 368.8 20.49 357.3 16.06 345.4C12.82 336.8 15.55 327.1 22.41 320.8L65.67 281.4C64.57 273.1 64 264.6 64 256C64 247.4 64.57 238.9 65.67 230.6L22.41 191.2C15.55 184.9 12.82 175.3 16.06 166.6C20.49 154.7 25.78 143.2 31.84 132.3L36.51 124.2C43.12 113.2 50.52 102.8 58.63 92.95C64.55 85.8 74.3 83.32 83.14 86.14L138.8 103.9C152.2 93.56 167 84.96 182.8 78.43L195.3 21.33C197.3 12.25 204.3 5.04 213.5 3.51C227.3 1.201 241.5 0 256 0C270.5 0 284.7 1.201 298.5 3.51C307.7 5.04 314.7 12.25 316.7 21.33L329.2 78.43C344.1 84.96 359.8 93.56 373.2 103.9L428.9 86.14C437.7 83.32 447.4 85.8 453.4 92.95C461.5 102.8 468.9 113.2 475.5 124.2L480.2 132.3C486.2 143.2 491.5 154.7 495.9 166.6V166.6zM256 336C300.2 336 336 300.2 336 255.1C336 211.8 300.2 175.1 256 175.1C211.8 175.1 176 211.8 176 255.1C176 300.2 211.8 336 256 336z" />
</svg>
</div>
)
const OtherSideButtons = () => (
<button id="settings-back" onClick={() => openSide(Side.Main)}>Back</button>
)
const Menu = ({updateAvailable, side}:{updateAvailable:boolean, side:Side}) => (
<header>
<h1 id="main-headline">{(side == Side.Main)? "StickExporterTX" : "Settings"}</h1>
{updateAvailable? <UpdateButton/> : null}
{(side == Side.Main)? <MainSideButtons/> : <OtherSideButtons/>}
</header>
)
export default Menu;

View File

@@ -0,0 +1,58 @@
import React, {useState} from "react";
import { settingList, updateSettings, settingListLoadDefault } from "../settings";
function SettingsSide() {
const [fps, setFps] = useState(settingList.fps);
const [width, setWidth] = useState(settingList.width);
const [stickDistance, setStickDistance] = useState(settingList.stickDistance);
const [stickMode2, setStickMode2] = useState(settingList.stickMode2);
return (
<div id="content">
<div className="dataDiv">
<p>FPS: </p>
<input id="fpsInput" type="number" value={fps.toString()} min="1" step="1" onChange={e => {
updateSettings({fps:parseInt(e.target.value)});
setFps(settingList.fps);
}}/>
</div>
<div className="dataDiv">
<p>Width: </p>
<input id="widthInput" type="number" value={width.toString()} min="1" step="1" onChange={e => {
updateSettings({width:parseInt(e.target.value)});
setWidth(settingList.width);
}}/>
</div>
<div className="dataDiv">
<p>Stick Distance: </p>
<input id="stickDistanceInput" type="number" value={stickDistance.toString()} min="0" step="1" onChange={e => {
updateSettings({stickDistance:parseInt(e.target.value)});
setStickDistance(settingList.stickDistance);
}}/>
</div>
<div className="dataDiv">
<p>Stick Mode:</p>
<label htmlFor="stickMode" className="toggle-switchy" data-style="rounded" data-text="12">
<input checked={stickMode2} type="checkbox" id="stickMode" onChange={e => {
updateSettings({stickMode2:e.target.checked});
setStickMode2(settingList.stickMode2);
}}/>
<span className="toggle">
<span className="switch"></span>
</span>
</label>
</div>
<button onClick={() => {
settingListLoadDefault();
setFps(settingList.fps);
setWidth(settingList.width);
setStickDistance(settingList.stickDistance);
setStickMode2(settingList.stickMode2);
}}>Reset Settings</button>
</div>
)
}
export default SettingsSide;