mirror of
https://github.com/LinoSchmidt/StickExporterTX.git
synced 2026-03-21 01:51:15 +01:00
Compare commits
59 Commits
v0.8.1-alp
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a6c4ab37f7 | ||
|
|
c095f57f98 | ||
|
|
a28b678719 | ||
|
|
5b1445d507 | ||
|
|
476359e4ce | ||
|
|
7fefef0ea9 | ||
|
|
7b7560743f | ||
|
|
b2a44cec24 | ||
|
|
f9072092b5 | ||
|
|
0e526df376 | ||
|
|
8b1d5ae04f | ||
|
|
b081149759 | ||
|
ac8368adf4
|
|||
|
d90c811e83
|
|||
|
05d195f9d1
|
|||
|
d2d3ed696b
|
|||
|
59e5c4e150
|
|||
|
244d4c94b4
|
|||
|
2e9731e69a
|
|||
|
248f676185
|
|||
|
1cf9ca8541
|
|||
|
b72a44fd3b
|
|||
|
fd1d8e2b51
|
|||
|
|
f24ea21d8c | ||
|
|
365f7d8b34 | ||
|
|
8dff49751b | ||
|
|
2e55a6dc52 | ||
|
|
fb9dcabec2 | ||
|
|
7ac92ffef6 | ||
|
|
db48a8b04d | ||
|
|
6223624ff9 | ||
|
|
119066d649 | ||
|
|
88d56c0197 | ||
|
f496cad846
|
|||
|
|
4dc3bac6bf | ||
|
|
5aa61e4290 | ||
|
|
bb5ca04fd2 | ||
|
|
3a78bfdee3 | ||
|
477fccdb2e
|
|||
|
fac8118b28
|
|||
|
a4d504962d
|
|||
|
c9c3316437
|
|||
|
f2318cccf0
|
|||
|
efeea62def
|
|||
|
367f91fcb4
|
|||
|
c8397308a3
|
|||
|
ce0f03cff7
|
|||
|
13706757ee
|
|||
|
377e26c124
|
|||
|
57a6d7479b
|
|||
|
c768cf225f
|
|||
|
|
f585c81d38 | ||
|
|
b6b94e67b8 | ||
|
|
2938b98ab8 | ||
|
|
3a83fb77ca | ||
|
|
8f32025238 | ||
|
|
2541d5ff9a | ||
|
|
f5d45c3b9a | ||
|
|
a013bced01 |
20
README.md
20
README.md
@@ -2,7 +2,7 @@
|
||||
|
||||
StickExporterTX is a 3D Stick Exporter for EdgeTX/OpenTX logs.
|
||||
|
||||
## Quick Start Guide
|
||||
## Controller Setup Guide
|
||||
|
||||
To log your sticks on each model, go to `settings -> global functions` in your RC controller.
|
||||
|
||||
@@ -18,6 +18,24 @@ If you only want to set up logging for one model, go to `model -> special functi
|
||||
|
||||

|
||||
|
||||
## How to use
|
||||
### Importing logs
|
||||
1. Connect your RC controller to your computer via USB.
|
||||
2. Select `USB Storage (SD)` on the controller.
|
||||
3. Open the StickExporterTX app and click on `Add Log(s)`.
|
||||
4. Select the log files you want to import. They should be located in the `LOGS` folder on your controller.
|
||||
|
||||
## How to build (for developers)
|
||||
The following software is required to build the project:
|
||||
- [Node.js](https://nodejs.org/)
|
||||
- [Python 3](https://www.python.org/)
|
||||
|
||||
To build the project, run the following commands in the project directory:
|
||||
```bash
|
||||
npm install
|
||||
npm run bundle
|
||||
```
|
||||
If the commands were successful, a new folder called `dist` should have been created in the project directory containing the compiled files.
|
||||
## Licence:
|
||||
|
||||
This project is released under the MIT license, for more information, check the [LICENSE](LICENSE) file.
|
||||
73
dependencies/blenderScript.py
vendored
73
dependencies/blenderScript.py
vendored
@@ -5,10 +5,7 @@ import math
|
||||
import sys
|
||||
import time
|
||||
import bpy
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
argv = sys.argv
|
||||
argv = argv[argv.index("--") + 1:]
|
||||
import json
|
||||
|
||||
logger = logging.getLogger('simple_example')
|
||||
logger.setLevel(logging.INFO)
|
||||
@@ -43,28 +40,22 @@ def _map(x, in_min, in_max, out_min, out_max):
|
||||
logger.info("Blender started successfully!")
|
||||
|
||||
while True:
|
||||
command = input("Waiting for command: ")
|
||||
command = input("Waiting for command: ").split(" -- ")
|
||||
|
||||
time.sleep(0.5)
|
||||
|
||||
settingsRoot = ET.parse(argv[0]+"/settings.xml").getroot()
|
||||
settings = json.loads(command[1])
|
||||
|
||||
StickMode = settingsRoot[3].text
|
||||
if(StickMode == "true"):
|
||||
StickMode = 2
|
||||
else:
|
||||
StickMode = 1
|
||||
|
||||
width = int(settingsRoot[1].text)
|
||||
StickDistance = _map(int(settingsRoot[2].text), 0, 100, 5, 105)
|
||||
|
||||
if(command == "startRendering"):
|
||||
|
||||
fps = int(settingsRoot[0].text)
|
||||
videoFormat = settingsRoot[4].text
|
||||
logs = settingsRoot[5].text[1:][:-1].split("\"\"")
|
||||
output = settingsRoot[6].text
|
||||
stickMode2 = settings["stickMode2"]
|
||||
width = settings["width"]
|
||||
stickDistance = _map(settings["stickDistance"], 0, 100, 5, 105)
|
||||
fps = settings["fps"]
|
||||
videoFormat = settings["videoFormat"]
|
||||
logs = settings["logs"]
|
||||
output = settings["output"]
|
||||
dataPath = settings["dataPath"]
|
||||
|
||||
if(command[0] == "startRendering"):
|
||||
logCount = len(logs)
|
||||
logNumber = 1
|
||||
|
||||
@@ -129,14 +120,14 @@ while True:
|
||||
bpy.context.scene.render.image_settings.color_mode = 'RGBA'
|
||||
|
||||
scn.render.resolution_x = width
|
||||
GimbalCoverR.location[0] = StickDistance
|
||||
GimbalR.location[0] = StickDistance
|
||||
TrailR.location[0] = StickDistance
|
||||
Plane.location[0] = StickDistance
|
||||
Camera.location[0] = StickDistance/2
|
||||
Camera.data.ortho_scale = StickDistance+5
|
||||
scn.render.resolution_y = int(width/_map(StickDistance, 5, 105, 2, 21.6))
|
||||
bpy.context.scene.render.filepath = output + "\\" + log.split("/")[-1].split("\\")[-1].replace(".csv", "."+videoFormat)
|
||||
GimbalCoverR.location[0] = stickDistance
|
||||
GimbalR.location[0] = stickDistance
|
||||
TrailR.location[0] = stickDistance
|
||||
Plane.location[0] = stickDistance
|
||||
Camera.location[0] = stickDistance/2
|
||||
Camera.data.ortho_scale = stickDistance+5
|
||||
scn.render.resolution_y = int(width/_map(stickDistance, 5, 105, 2, 21.6))
|
||||
bpy.context.scene.render.filepath = output[logNumber-1]
|
||||
|
||||
scn.render.fps = 1000
|
||||
scn.render.fps_base = FPSxxx
|
||||
@@ -168,7 +159,7 @@ while True:
|
||||
StickR.rotation_euler=[0,0,0]
|
||||
GimbalR.rotation_euler=[0,0,0]
|
||||
|
||||
if StickMode == "1":
|
||||
if stickMode2 == False:
|
||||
StickL.rotation_euler.rotate_axis("Y", ailP)
|
||||
GimbalL.rotation_euler.rotate_axis("X", eleP)
|
||||
StickR.rotation_euler.rotate_axis("Y", rudP)
|
||||
@@ -194,19 +185,19 @@ while True:
|
||||
|
||||
logNumber+=1
|
||||
|
||||
elif(command == "getRender"):
|
||||
elif(command[0] == "getRender"):
|
||||
|
||||
bpy.context.scene.render.image_settings.file_format = 'PNG'
|
||||
bpy.context.scene.render.filepath = argv[0] + "\\render.png"
|
||||
bpy.context.scene.render.filepath = dataPath + "\\render.png"
|
||||
|
||||
scn.render.resolution_x = width
|
||||
GimbalCoverR.location[0] = StickDistance
|
||||
GimbalR.location[0] = StickDistance
|
||||
TrailR.location[0] = StickDistance
|
||||
Plane.location[0] = StickDistance
|
||||
Camera.location[0] = StickDistance/2
|
||||
Camera.data.ortho_scale = StickDistance+5
|
||||
scn.render.resolution_y = int(width/_map(StickDistance, 5, 105, 2, 21.6))
|
||||
GimbalCoverR.location[0] = stickDistance
|
||||
GimbalR.location[0] = stickDistance
|
||||
TrailR.location[0] = stickDistance
|
||||
Plane.location[0] = stickDistance
|
||||
Camera.location[0] = stickDistance/2
|
||||
Camera.data.ortho_scale = stickDistance+5
|
||||
scn.render.resolution_y = int(width/_map(stickDistance, 5, 105, 2, 21.6))
|
||||
|
||||
bpy.context.scene.frame_set(0)
|
||||
|
||||
@@ -215,7 +206,7 @@ while True:
|
||||
StickR.rotation_euler=[0,0,0]
|
||||
GimbalR.rotation_euler=[0,0,0]
|
||||
|
||||
if(StickMode == 2):
|
||||
if(stickMode2 == True):
|
||||
StickL.rotation_euler.rotate_axis("Y", 0)
|
||||
GimbalL.rotation_euler.rotate_axis("X", 0.436)
|
||||
StickR.rotation_euler.rotate_axis("Y", 0)
|
||||
@@ -238,7 +229,7 @@ while True:
|
||||
StickR.rotation_euler=[0,0,0]
|
||||
GimbalR.rotation_euler=[0,0,0]
|
||||
|
||||
if(StickMode == 2):
|
||||
if(stickMode2 == True):
|
||||
StickL.rotation_euler.rotate_axis("Y", 0)
|
||||
GimbalL.rotation_euler.rotate_axis("X", 0.436)
|
||||
StickR.rotation_euler.rotate_axis("Y", 0)
|
||||
|
||||
4123
package-lock.json
generated
4123
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -134,8 +134,8 @@
|
||||
"babel-loader": "^8.2.5",
|
||||
"cross-env": "^7.0.3",
|
||||
"css-loader": "^6.7.1",
|
||||
"electron": "18.1.0",
|
||||
"electron-builder": "^23.0.3",
|
||||
"electron": "22.3.25",
|
||||
"electron-builder": "^24.0.0",
|
||||
"eslint": "^8.16.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-react": "^7.30.0",
|
||||
@@ -150,7 +150,7 @@
|
||||
"ts-loader": "^9.3.0",
|
||||
"ts-node": "^10.8.0",
|
||||
"typescript": "^4.7.2",
|
||||
"webpack": "^5.72.1",
|
||||
"webpack": "^5.76.0",
|
||||
"webpack-cli": "^4.9.2",
|
||||
"webpack-dev-server": "^4.9.1",
|
||||
"webpack-merge": "^5.8.0"
|
||||
|
||||
@@ -34,7 +34,26 @@ if(platform.system() == 'Linux'):
|
||||
urllib.request.urlretrieve(linuxURL, './dependencies/linux/blender.tar.xz')
|
||||
print("Extracting linux version")
|
||||
with tarfile.open('./dependencies/linux/blender.tar.xz') as tfile:
|
||||
tfile.extractall('./dependencies/linux')
|
||||
def is_within_directory(directory, target):
|
||||
|
||||
abs_directory = os.path.abspath(directory)
|
||||
abs_target = os.path.abspath(target)
|
||||
|
||||
prefix = os.path.commonprefix([abs_directory, abs_target])
|
||||
|
||||
return prefix == abs_directory
|
||||
|
||||
def safe_extract(tar, path=".", members=None, *, numeric_owner=False):
|
||||
|
||||
for member in tar.getmembers():
|
||||
member_path = os.path.join(path, member.name)
|
||||
if not is_within_directory(path, member_path):
|
||||
raise Exception("Attempted Path Traversal in Tar File")
|
||||
|
||||
tar.extractall(path, members, numeric_owner=numeric_owner)
|
||||
|
||||
|
||||
safe_extract(tfile, "./dependencies/linux")
|
||||
|
||||
print("Adjust linux version")
|
||||
oldLinuxName = linuxURL.split('/')[-1].replace('.tar.xz', '')
|
||||
|
||||
@@ -1,28 +1,22 @@
|
||||
import { blenderPath, blenderScriptPath, dataPath, templatePath, finsishedIconPath } from "./paths";
|
||||
import { blenderPath, blenderScriptPath, templatePath, finsishedIconPath, dataPath } from "./paths";
|
||||
import {spawn} from "child_process";
|
||||
import logger from "./logger";
|
||||
import { setBlenderLoading, setBlenderStatus } from "./ui/menu";
|
||||
import { setLogNumber, setPastTime, setRemainingTime, setRenderDisplayProgress, setStatus, setPastTimeNow, setRemainingTimeNow } from "./ui/renderingPage";
|
||||
import { setLogNumber, setStatus, addTerminalLine } from "./ui/renderingPage";
|
||||
import {imageLoading, imageLoaded} from "./ui/settingsPage";
|
||||
import { getLogList, getLogSize, settingList } from "./settings";
|
||||
import isValid from "is-valid-path";
|
||||
import { getInOutSettings, getActiveProfile } from "./settings";
|
||||
import { pageSetRendering, setProgress, openPage, Page } from "../renderer";
|
||||
import { setLog, setRenderProgress, startProgress, stopProgress } from "./progressController";
|
||||
import { ipcRenderer } from "electron";
|
||||
import path from 'path';
|
||||
import fs from "fs";
|
||||
// import { getDoNotDisturb } from "electron-notification-state";
|
||||
|
||||
export const renderInfo = {
|
||||
time: "0min 0sec",
|
||||
startTime: 0,
|
||||
endTime: 0
|
||||
}
|
||||
|
||||
const blenderStartString = [
|
||||
templatePath,
|
||||
"--background",
|
||||
"--python",
|
||||
blenderScriptPath,
|
||||
"--",
|
||||
dataPath.replaceAll("\\", "/")
|
||||
blenderScriptPath
|
||||
]
|
||||
|
||||
let blenderConsole = spawn(blenderPath, blenderStartString).on('error', function(err) {
|
||||
@@ -33,41 +27,40 @@ let renderingPicture = false;
|
||||
let renderingVideo = false;
|
||||
let waitingForRender = false;
|
||||
|
||||
let logPortionList:number[] = [];
|
||||
let currentLogPortion = 0;
|
||||
function getOutPath(log:string) {
|
||||
let fullOutPath = path.join(getInOutSettings().output, log.substring(log.lastIndexOf("\\")).replace(".csv", "."+getActiveProfile().videoFormat));
|
||||
|
||||
const estimatedRenderPortion = 0.97;
|
||||
function setRenderProgress(log:number, init:boolean, frameCount:number, frame:number) {
|
||||
let progress = 0;
|
||||
if(init) {
|
||||
progress = logPortionList[log-1] * (frame / frameCount * (1 - estimatedRenderPortion)) + currentLogPortion;
|
||||
} else {
|
||||
progress = logPortionList[log-1] * (frame / frameCount * estimatedRenderPortion + (1 - estimatedRenderPortion)) + currentLogPortion;
|
||||
}
|
||||
setProgress(progress);
|
||||
setRenderDisplayProgress(parseFloat((progress*100).toFixed(2)));
|
||||
|
||||
const timeNow = new Date().getTime();
|
||||
const timeDiff = timeNow - renderInfo.startTime;
|
||||
let timeDiffSeconds = timeDiff / 1000;
|
||||
let timeDiffMinutes = 0;
|
||||
while(timeDiffSeconds > 60) {
|
||||
timeDiffMinutes++;
|
||||
timeDiffSeconds -= 60;
|
||||
}
|
||||
renderInfo.time = timeDiffMinutes + "m " + timeDiffSeconds.toFixed(0) + "s";
|
||||
setPastTimeNow(renderInfo.time);
|
||||
|
||||
if(progress > 0) {
|
||||
const timeRemaining = (timeDiff / progress) * (1 - progress);
|
||||
timeDiffSeconds = timeRemaining / 1000;
|
||||
timeDiffMinutes = 0;
|
||||
while(timeDiffSeconds > 60) {
|
||||
timeDiffMinutes++;
|
||||
timeDiffSeconds -= 60;
|
||||
if(fs.existsSync(fullOutPath)) {
|
||||
let i = 1;
|
||||
while(fs.existsSync(fullOutPath.replace("."+getActiveProfile().videoFormat, " ("+i+")."+getActiveProfile().videoFormat))) {
|
||||
i++;
|
||||
}
|
||||
setRemainingTimeNow(timeDiffMinutes + "m " + timeDiffSeconds.toFixed(0) + "s");
|
||||
fullOutPath = fullOutPath.replace("."+getActiveProfile().videoFormat, " ("+i+")."+getActiveProfile().videoFormat);
|
||||
}
|
||||
|
||||
return fullOutPath;
|
||||
}
|
||||
|
||||
let outputArgs:string[] = [];
|
||||
|
||||
function blenderArgs() {
|
||||
const outputList:string[] = [];
|
||||
getInOutSettings().logs.forEach(log => {
|
||||
outputList.push(getOutPath(log));
|
||||
});
|
||||
|
||||
outputArgs = outputList;
|
||||
|
||||
return JSON.stringify({
|
||||
stickMode2:getActiveProfile().stickMode2,
|
||||
width:getActiveProfile().width,
|
||||
stickDistance:getActiveProfile().stickDistance,
|
||||
fps:getActiveProfile().fps,
|
||||
videoFormat:getActiveProfile().videoFormat,
|
||||
logs:getInOutSettings().logs,
|
||||
output:outputList,
|
||||
dataPath:dataPath
|
||||
});
|
||||
}
|
||||
|
||||
function startBlender() {
|
||||
@@ -80,6 +73,10 @@ function startBlender() {
|
||||
|
||||
logger.info("Blender: " + dataStr);
|
||||
|
||||
if(renderingVideo) {
|
||||
addTerminalLine(dataStr);
|
||||
}
|
||||
|
||||
if (dataStr.includes("Blender started successfully")) {
|
||||
renderingPicture = false;
|
||||
renderingVideo = false;
|
||||
@@ -108,13 +105,13 @@ function startBlender() {
|
||||
setRenderProgress(parseInt(log), true, 0, 0);
|
||||
}
|
||||
if(dataStr.includes("Fra:") && renderingVideo) {
|
||||
lastFrame = dataStr.split(":")[1].split(" ")[0];
|
||||
lastFrame = dataStr.split("Fra:")[1].split(" ")[0];
|
||||
setStatus("Rendering Frame " + lastFrame + "/" + frames);
|
||||
setRenderProgress(parseInt(log), false, parseInt(frames), parseInt(lastFrame));
|
||||
}
|
||||
if(dataStr.includes("Finished") && renderingVideo) {
|
||||
pageSetRendering(false);
|
||||
renderInfo.endTime = new Date().getTime();
|
||||
stopProgress();
|
||||
if(lastFrame == frames) {
|
||||
openPage(Page.RenderFinish);
|
||||
ipcRenderer.send("renderFinished");
|
||||
@@ -136,7 +133,7 @@ function startBlender() {
|
||||
if(dataStr.includes("Lognr:") && renderingVideo) {
|
||||
log = dataStr.split(":")[1];
|
||||
if(log !== "1") {
|
||||
currentLogPortion += logPortionList[parseInt(log)-2];
|
||||
setLog(parseInt(log));
|
||||
}
|
||||
setLogNumber(log);
|
||||
}
|
||||
@@ -158,7 +155,7 @@ function startBlender() {
|
||||
} else {
|
||||
waitingForRender = false;
|
||||
renderingPicture = true;
|
||||
blenderConsole.stdin.write("getRender\n");
|
||||
blenderConsole.stdin.write("getRender -- "+blenderArgs()+"\n");
|
||||
setBlenderStatus("Rendering");
|
||||
setBlenderLoading(true);
|
||||
imageLoading();
|
||||
@@ -192,44 +189,23 @@ function blender(command:blenderCmd) {
|
||||
imageLoading();
|
||||
setBlenderStatus("Rendering");
|
||||
setBlenderLoading(true);
|
||||
blenderConsole.stdin.write("getRender\n");
|
||||
blenderConsole.stdin.write("getRender -- "+blenderArgs()+"\n");
|
||||
} else {
|
||||
waitingForRender = true;
|
||||
}
|
||||
} else if(command === blenderCmd.startRendering) {
|
||||
if(readyToAcceptCommand) {
|
||||
if(settingList.log == "") {
|
||||
if(getInOutSettings().logs.length === 0) {
|
||||
logger.warningMSG("No log selected!");
|
||||
} else if(!isValid(settingList.log)) {
|
||||
logger.warningMSG("Output path is invalid!");
|
||||
} else {
|
||||
currentLogPortion = 0;
|
||||
|
||||
const logSizeList:number[] = [];
|
||||
getLogList().forEach(function (value, index) {
|
||||
logSizeList.push(getLogSize(index));
|
||||
});
|
||||
|
||||
let fullLogSize = 0;
|
||||
for(let i = 0; i < logSizeList.length; i++) {
|
||||
fullLogSize += logSizeList[i];
|
||||
}
|
||||
|
||||
logPortionList = [];
|
||||
logSizeList.forEach(function (value) {
|
||||
logPortionList.push(value / fullLogSize);
|
||||
});
|
||||
|
||||
readyToAcceptCommand = false;
|
||||
renderingVideo = true;
|
||||
pageSetRendering(true);
|
||||
setBlenderStatus("Rendering");
|
||||
setBlenderLoading(true);
|
||||
blenderConsole.stdin.write("startRendering\n");
|
||||
blenderConsole.stdin.write("startRendering -- "+blenderArgs()+"\n");
|
||||
|
||||
renderInfo.startTime = new Date().getTime();
|
||||
setPastTime("0min 0sec");
|
||||
setRemainingTime("calculating...");
|
||||
startProgress();
|
||||
}
|
||||
}
|
||||
} else if(command === blenderCmd.stopRendering) {
|
||||
@@ -252,5 +228,6 @@ export {
|
||||
blender,
|
||||
blenderCmd,
|
||||
startBlender,
|
||||
renderingPicture
|
||||
renderingPicture,
|
||||
outputArgs
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
import logger from "./logger";
|
||||
import {parse as csvParse} from "csv-parse";
|
||||
import {settingList} from "./settings";
|
||||
import {getInOutSettings} from "./settings";
|
||||
import {platformCharacter} from "./paths";
|
||||
import {formatDate} from "./dateFormat";
|
||||
import fs from "fs";
|
||||
|
||||
async function openLogFile(filePath:string, rawData:boolean) {
|
||||
const data = await fetch(filePath).then(function(response) {
|
||||
@@ -20,7 +21,7 @@ async function openLogFile(filePath:string, rawData:boolean) {
|
||||
let logData:string[]|undefined = undefined;
|
||||
csvParse(data, {}, (err, output:string[]) => {
|
||||
if(err) {
|
||||
logger.errorMSG(`Error parsing csv file: ${err}`);
|
||||
logger.errorMSG(`Error parsing file "${filePath}": ${err}`);
|
||||
logData = [];
|
||||
} else {
|
||||
logData = output;
|
||||
@@ -162,10 +163,8 @@ async function getLogTime(filePath:string) {
|
||||
async function getAllLogs() {
|
||||
const loadList = [];
|
||||
|
||||
if(settingList.log.length > 0) {
|
||||
const logs = settingList.log.substring(1).slice(0, -1).split('""');
|
||||
|
||||
for(const log of logs) {
|
||||
if(getInOutSettings().logs.length > 0) {
|
||||
for(const log of getInOutSettings().logs) {
|
||||
loadList.push({
|
||||
name: log.split(platformCharacter())[log.split(platformCharacter()).length - 1].replace(".csv", ""),
|
||||
path: log,
|
||||
@@ -184,9 +183,8 @@ async function reloadAllLogs() {
|
||||
}
|
||||
|
||||
async function updateLogs() {
|
||||
if(settingList.log.length > 0) {
|
||||
const logs = settingList.log.substring(1).slice(0, -1).split('""');
|
||||
for(const log of logs) {
|
||||
if(getInOutSettings().logs.length > 0) {
|
||||
for(const log of getInOutSettings().logs) {
|
||||
if(!logList.some(x => x.path === log)) {
|
||||
logList.push({
|
||||
name: log.split(platformCharacter())[log.split(platformCharacter()).length - 1].replace(".csv", ""),
|
||||
@@ -197,7 +195,7 @@ async function updateLogs() {
|
||||
}
|
||||
|
||||
for(const log of logList) {
|
||||
if(!logs.some(x => x === log.path)) {
|
||||
if(!getInOutSettings().logs.some(x => x === log.path)) {
|
||||
logList.splice(logList.indexOf(log), 1);
|
||||
}
|
||||
}
|
||||
@@ -206,8 +204,13 @@ async function updateLogs() {
|
||||
}
|
||||
}
|
||||
|
||||
function getLogSize(index:number) {
|
||||
return fs.statSync(getInOutSettings().logs[index]).size;
|
||||
}
|
||||
|
||||
export {
|
||||
reloadAllLogs,
|
||||
logList,
|
||||
updateLogs
|
||||
updateLogs,
|
||||
getLogSize
|
||||
};
|
||||
@@ -4,7 +4,8 @@ import { platformFolder, platform, Platform } from './platform';
|
||||
|
||||
export const dataPath = app.getPath('userData');
|
||||
export const appPath = app.getAppPath().replace("app.asar", "");
|
||||
export const SettingPath = path.join(dataPath, "settings.xml");
|
||||
export const SettingPath = path.join(dataPath, "settings.json");
|
||||
export const OLDSettingPath = path.join(dataPath, "settings.xml");
|
||||
|
||||
export const defaultOutputPath = path.join(app.getPath('videos'), "StickExporterTX");
|
||||
|
||||
|
||||
92
src/components/progressController.ts
Normal file
92
src/components/progressController.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
import {setProgress} from "../renderer";
|
||||
import {getLogSize} from "./logReader";
|
||||
import {getInOutSettings} from "./settings";
|
||||
import { setRenderDisplayProgress, setPastTimeNow, setRemainingTimeNow } from "./ui/renderingPage";
|
||||
|
||||
const estimatedRenderPortion = 0.97;
|
||||
|
||||
const renderInfo = {
|
||||
time: "0min 0sec",
|
||||
startTime: 0,
|
||||
endTime: 0
|
||||
}
|
||||
|
||||
let logPortionList:number[] = [];
|
||||
let currentLogPortion = 0;
|
||||
|
||||
function setRenderProgress(log:number, init:boolean, frameCount:number, frame:number) {
|
||||
let progress = 0;
|
||||
if(init) {
|
||||
progress = logPortionList[log-1] * (frame / frameCount * (1 - estimatedRenderPortion)) + currentLogPortion;
|
||||
} else {
|
||||
progress = logPortionList[log-1] * (frame / frameCount * estimatedRenderPortion + (1 - estimatedRenderPortion)) + currentLogPortion;
|
||||
}
|
||||
setProgress(progress);
|
||||
setRenderDisplayProgress(parseFloat((progress*100).toFixed(2)));
|
||||
|
||||
const timeNow = new Date().getTime();
|
||||
const timeDiff = timeNow - renderInfo.startTime;
|
||||
let timeDiffSeconds = timeDiff / 1000;
|
||||
let timeDiffMinutes = 0;
|
||||
while(timeDiffSeconds > 60) {
|
||||
timeDiffMinutes++;
|
||||
timeDiffSeconds -= 60;
|
||||
}
|
||||
renderInfo.time = timeDiffMinutes + "m " + timeDiffSeconds.toFixed(0) + "s";
|
||||
setPastTimeNow(renderInfo.time);
|
||||
|
||||
if(progress > 0) {
|
||||
const timeRemaining = (timeDiff / progress) * (1 - progress);
|
||||
timeDiffSeconds = timeRemaining / 1000;
|
||||
timeDiffMinutes = 0;
|
||||
while(timeDiffSeconds > 60) {
|
||||
timeDiffMinutes++;
|
||||
timeDiffSeconds -= 60;
|
||||
}
|
||||
setRemainingTimeNow(timeDiffMinutes + "m " + timeDiffSeconds.toFixed(0) + "s");
|
||||
}
|
||||
}
|
||||
|
||||
function resetRenderProgress() {
|
||||
logPortionList = [];
|
||||
currentLogPortion = 0;
|
||||
|
||||
const logSizeList:number[] = [];
|
||||
getInOutSettings().logs.forEach(function (value, index) {
|
||||
logSizeList.push(getLogSize(index));
|
||||
});
|
||||
|
||||
let fullLogSize = 0;
|
||||
for(let i = 0; i < logSizeList.length; i++) {
|
||||
fullLogSize += logSizeList[i];
|
||||
}
|
||||
|
||||
logSizeList.forEach(function (value) {
|
||||
logPortionList.push(value / fullLogSize);
|
||||
});
|
||||
|
||||
setPastTimeNow("0min 0sec");
|
||||
setRemainingTimeNow("calculating...");
|
||||
}
|
||||
|
||||
function setLog(log:number) {
|
||||
currentLogPortion += logPortionList[log-2];
|
||||
}
|
||||
|
||||
function stopProgress() {
|
||||
renderInfo.endTime = new Date().getTime();
|
||||
}
|
||||
|
||||
function startProgress() {
|
||||
renderInfo.startTime = new Date().getTime();
|
||||
resetRenderProgress();
|
||||
}
|
||||
|
||||
export {
|
||||
setRenderProgress,
|
||||
resetRenderProgress,
|
||||
setLog,
|
||||
stopProgress,
|
||||
startProgress,
|
||||
renderInfo
|
||||
}
|
||||
@@ -1,11 +1,7 @@
|
||||
import formatXML from "xml-formatter";
|
||||
import {SettingPath, defaultOutputPath} from './paths';
|
||||
import fs from "fs";
|
||||
import {SettingPath, defaultOutputPath, OLDSettingPath} from './paths';
|
||||
import {dialog} from '@electron/remote';
|
||||
import logger from "./logger";
|
||||
|
||||
function getXMLChild(doc:Document, child:string) {
|
||||
return String(doc.getElementsByTagName(child)[0].childNodes[0].nodeValue);
|
||||
}
|
||||
import fs from "fs";
|
||||
|
||||
enum VideoFormat {
|
||||
mp4="mp4",
|
||||
@@ -15,138 +11,470 @@ enum VideoFormat {
|
||||
mkv="mkv",
|
||||
}
|
||||
|
||||
const defaultSettings = {
|
||||
fps: 30,
|
||||
width: 540,
|
||||
stickDistance: 5,
|
||||
stickMode2: true,
|
||||
videoFormat: VideoFormat.webm,
|
||||
log: '',
|
||||
output: defaultOutputPath
|
||||
type JSONProfile = {
|
||||
profileName: string,
|
||||
fps: number,
|
||||
width: number,
|
||||
stickDistance: number,
|
||||
stickMode2: boolean,
|
||||
videoFormat: VideoFormat
|
||||
};
|
||||
|
||||
type JSONSettings = {
|
||||
activeProfile: string,
|
||||
profiles: JSONProfile[],
|
||||
logs: string[],
|
||||
output: string,
|
||||
showRenderTerminal: boolean
|
||||
}
|
||||
|
||||
let allSettingsFound = true;
|
||||
const defaultSettings:JSONSettings = {
|
||||
activeProfile: "Default",
|
||||
profiles: [
|
||||
{
|
||||
profileName: "Default",
|
||||
fps: 30,
|
||||
width: 540,
|
||||
stickDistance: 5,
|
||||
stickMode2: true,
|
||||
videoFormat: VideoFormat.webm
|
||||
}
|
||||
],
|
||||
logs: [],
|
||||
output: defaultOutputPath,
|
||||
showRenderTerminal: false
|
||||
};
|
||||
|
||||
function catchSetting(tryFunc:()=>string, catchFunc:()=>string) {
|
||||
let val;
|
||||
try {
|
||||
val = tryFunc();
|
||||
} catch(err) {
|
||||
logger.info("Failed to get setting value. Using default value:" + String(err));
|
||||
allSettingsFound = false;
|
||||
val = catchFunc();
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
function getXMLChild(doc:Document, child:string) {
|
||||
return String(doc.getElementsByTagName(child)[0].childNodes[0].nodeValue);
|
||||
}
|
||||
|
||||
let fetchFailed = "";
|
||||
const settingList = await fetch(SettingPath).then(function(response) {
|
||||
return response.text();
|
||||
}).catch(function(err) {
|
||||
logger.info(err);
|
||||
return "fileLoadFailed";
|
||||
}).then(function(data) {
|
||||
}).then(async function(data) {
|
||||
if(data === "fileLoadFailed") {
|
||||
allSettingsFound = false;
|
||||
return defaultSettings;
|
||||
return await fetch(OLDSettingPath).then(function(response) {
|
||||
return response.text();
|
||||
}).catch(function(err) {
|
||||
logger.info(err);
|
||||
return "fileLoadFailed";
|
||||
}).then(function(data) {
|
||||
if(data === "fileLoadFailed") {
|
||||
fetchFailed = "fileLoadFailed";
|
||||
return defaultSettings;
|
||||
}
|
||||
|
||||
const parser = new DOMParser();
|
||||
const xmlDoc = parser.parseFromString(data, 'text/xml');
|
||||
|
||||
const allLogs = catchSetting(function() {return (getXMLChild(xmlDoc, "log") === "None")? "":getXMLChild(xmlDoc, "log");},function() {
|
||||
fetchFailed === "singleSetting"? fetchFailed = "multiSetting":fetchFailed = "singleSetting";
|
||||
return ""
|
||||
});
|
||||
const newLogs = defaultSettings.logs;
|
||||
if(allLogs !== "") {
|
||||
const allLogsList = allLogs.split("\"\"");
|
||||
allLogsList.forEach(log => {
|
||||
if(log !== "") {
|
||||
newLogs.push(log.replace('"', ''));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
activeProfile: defaultSettings.activeProfile,
|
||||
profiles: [
|
||||
{
|
||||
profileName: defaultSettings.profiles[0].profileName,
|
||||
fps: parseInt(catchSetting(function() {return getXMLChild(xmlDoc, "fps");},function() {
|
||||
fetchFailed === "singleSetting"? fetchFailed = "multiSetting":fetchFailed = "singleSetting";
|
||||
return defaultSettings.profiles[0].fps.toString();
|
||||
})),
|
||||
width: parseInt(catchSetting(function() {return getXMLChild(xmlDoc, "width");},function() {
|
||||
fetchFailed === "singleSetting"? fetchFailed = "multiSetting":fetchFailed = "singleSetting";
|
||||
return defaultSettings.profiles[0].width.toString();
|
||||
})),
|
||||
stickDistance: parseInt(catchSetting(function() {return getXMLChild(xmlDoc, "stickDistance");},function() {
|
||||
fetchFailed === "singleSetting"? fetchFailed = "multiSetting":fetchFailed = "singleSetting";
|
||||
return defaultSettings.profiles[0].stickDistance.toString();
|
||||
})),
|
||||
stickMode2: catchSetting(function() {return getXMLChild(xmlDoc, "stickMode2");},function() {
|
||||
fetchFailed === "singleSetting"? fetchFailed = "multiSetting":fetchFailed = "singleSetting";
|
||||
return defaultSettings.profiles[0].stickMode2.toString();
|
||||
}) === "true",
|
||||
videoFormat: catchSetting(function() {return getXMLChild(xmlDoc, "videoFormat");},function() {
|
||||
return defaultSettings.profiles[0].videoFormat.toString();
|
||||
}) as VideoFormat as VideoFormat
|
||||
}
|
||||
],
|
||||
logs: newLogs,
|
||||
output: catchSetting(function() {return getXMLChild(xmlDoc, "output");},function() {
|
||||
fetchFailed === "singleSetting"? fetchFailed = "multiSetting":fetchFailed = "singleSetting";
|
||||
return defaultSettings.output;
|
||||
}),
|
||||
showRenderTerminal: defaultSettings.showRenderTerminal
|
||||
} as JSONSettings;
|
||||
});
|
||||
}
|
||||
const parsedData = JSON.parse(data);
|
||||
|
||||
let profiles:JSONProfile[] = [];
|
||||
if(parsedData.profiles !== undefined) {
|
||||
parsedData.profiles.forEach((profile:JSONProfile) => {
|
||||
if(typeof profile.profileName !== "string") {
|
||||
fetchFailed === "singleSetting"? fetchFailed = "multiSetting":fetchFailed = "singleSetting";
|
||||
profile.profileName = defaultSettings.profiles[0].profileName;
|
||||
}
|
||||
if(typeof profile.fps !== "number") {
|
||||
fetchFailed === "singleSetting"? fetchFailed = "multiSetting":fetchFailed = "singleSetting";
|
||||
profile.fps = defaultSettings.profiles[0].fps;
|
||||
}
|
||||
if(typeof profile.width !== "number") {
|
||||
fetchFailed === "singleSetting"? fetchFailed = "multiSetting":fetchFailed = "singleSetting";
|
||||
profile.width = defaultSettings.profiles[0].width;
|
||||
}
|
||||
if(typeof profile.stickDistance !== "number") {
|
||||
fetchFailed === "singleSetting"? fetchFailed = "multiSetting":fetchFailed = "singleSetting";
|
||||
profile.stickDistance = defaultSettings.profiles[0].stickDistance;
|
||||
}
|
||||
if(typeof profile.stickMode2 !== "boolean") {
|
||||
fetchFailed === "singleSetting"? fetchFailed = "multiSetting":fetchFailed = "singleSetting";
|
||||
profile.stickMode2 = defaultSettings.profiles[0].stickMode2;
|
||||
}
|
||||
if(typeof profile.videoFormat !== "string") {
|
||||
fetchFailed === "singleSetting"? fetchFailed = "multiSetting":fetchFailed = "singleSetting";
|
||||
profile.videoFormat = defaultSettings.profiles[0].videoFormat;
|
||||
}
|
||||
|
||||
profiles.push({
|
||||
profileName: profile.profileName,
|
||||
fps: profile.fps,
|
||||
width: profile.width,
|
||||
stickDistance: profile.stickDistance,
|
||||
stickMode2: profile.stickMode2,
|
||||
videoFormat: profile.videoFormat
|
||||
});
|
||||
});
|
||||
} else {
|
||||
fetchFailed = "multiSetting";
|
||||
profiles = defaultSettings.profiles;
|
||||
}
|
||||
|
||||
const parser = new DOMParser();
|
||||
const xmlDoc = parser.parseFromString(data, 'text/xml');
|
||||
const logs:string[] = [];
|
||||
if(parsedData.logs !== undefined) {
|
||||
parsedData.logs.forEach((log:string) => {
|
||||
if(typeof log === "string") {
|
||||
logs.push(log);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
fetchFailed === "singleSetting"? fetchFailed = "multiSetting":fetchFailed = "singleSetting";
|
||||
}
|
||||
|
||||
return {
|
||||
fps: parseInt(catchSetting(function() {return getXMLChild(xmlDoc, "fps");},function() {return defaultSettings.fps.toString();})),
|
||||
width: parseInt(catchSetting(function() {return getXMLChild(xmlDoc, "width");},function() {return defaultSettings.width.toString();})),
|
||||
stickDistance: parseInt(catchSetting(function() {return getXMLChild(xmlDoc, "stickDistance");},function() {return defaultSettings.stickDistance.toString();})),
|
||||
stickMode2: catchSetting(function() {return getXMLChild(xmlDoc, "stickMode2");},function() {return defaultSettings.stickMode2.toString();}) === "true",
|
||||
videoFormat: catchSetting(function() {return getXMLChild(xmlDoc, "videoFormat");},function() {return defaultSettings.videoFormat.toString();}) as VideoFormat as VideoFormat,
|
||||
log: catchSetting(function() {return (getXMLChild(xmlDoc, "log") === "None")? "":getXMLChild(xmlDoc, "log");},function() {return defaultSettings.log;}),
|
||||
output: catchSetting(function() {return getXMLChild(xmlDoc, "output");},function() {return defaultSettings.output;})
|
||||
activeProfile: catchSetting(function() {return parsedData.activeProfile;},function() {
|
||||
fetchFailed === "singleSetting"? fetchFailed = "multiSetting":fetchFailed = "singleSetting";
|
||||
return defaultSettings.activeProfile;
|
||||
}),
|
||||
profiles,
|
||||
logs,
|
||||
output: catchSetting(function() {return parsedData.output;},function() {
|
||||
fetchFailed === "singleSetting"? fetchFailed = "multiSetting":fetchFailed = "singleSetting";
|
||||
return defaultSettings.output;
|
||||
}),
|
||||
showRenderTerminal: function() {
|
||||
if(typeof parsedData.showRenderTerminal === "boolean") {
|
||||
return parsedData.showRenderTerminal;
|
||||
} else {
|
||||
fetchFailed === "singleSetting"? fetchFailed = "multiSetting":fetchFailed = "singleSetting";
|
||||
return defaultSettings.showRenderTerminal;
|
||||
}
|
||||
}()
|
||||
}
|
||||
});
|
||||
if(!allSettingsFound) {
|
||||
updateSettings({});
|
||||
if(fetchFailed !== "") {
|
||||
writeSettings();
|
||||
}
|
||||
|
||||
function updateSettings(optiones:{fps?:number, width?:number, stickDistance?:number, stickMode2?:boolean, videoFormat?:VideoFormat, 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.videoFormat === undefined) {
|
||||
optiones.videoFormat = settingList.videoFormat;
|
||||
} else {
|
||||
settingList.videoFormat = optiones.videoFormat;
|
||||
}
|
||||
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;
|
||||
function getProfiles() {
|
||||
return settingList.profiles.map((profile) => {
|
||||
return profile.profileName;
|
||||
});
|
||||
}
|
||||
|
||||
function createProfile(profileName:string, clone:boolean) {
|
||||
settingList.profiles.push({
|
||||
profileName: profileName,
|
||||
fps: clone? getActiveProfile().fps:defaultSettings.profiles[0].fps,
|
||||
width: clone? getActiveProfile().width:defaultSettings.profiles[0].width,
|
||||
stickDistance: clone? getActiveProfile().stickDistance:defaultSettings.profiles[0].stickDistance,
|
||||
stickMode2: clone? getActiveProfile().stickMode2:defaultSettings.profiles[0].stickMode2,
|
||||
videoFormat: clone? getActiveProfile().videoFormat:defaultSettings.profiles[0].videoFormat
|
||||
});
|
||||
|
||||
writeSettings();
|
||||
}
|
||||
|
||||
function ProfileLoadDefault(reset:{fps?:boolean, width?:boolean, stickDistance?:boolean, stickMode2?:boolean, videoFormat?:boolean, all?:boolean}, profileName?:string) {
|
||||
if(profileName === undefined) {
|
||||
profileName = getActiveProfile().profileName;
|
||||
}
|
||||
|
||||
const xmlStr = `
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<settings>
|
||||
<fps>${optiones.fps}</fps>
|
||||
<width>${optiones.width}</width>
|
||||
<stickDistance>${optiones.stickDistance}</stickDistance>
|
||||
<stickMode2>${optiones.stickMode2}</stickMode2>
|
||||
<videoFormat>${optiones.videoFormat}</videoFormat>
|
||||
<log>${(optiones.log === "")?"None":optiones.log}</log>
|
||||
<output>${optiones.output}</output>
|
||||
</settings>
|
||||
`;
|
||||
settingList.profiles.forEach(profile => {
|
||||
if(profile.profileName === profileName) {
|
||||
if(reset.all || reset.fps) {
|
||||
profile.fps = defaultSettings.profiles[0].fps;
|
||||
}
|
||||
if(reset.all || reset.width) {
|
||||
profile.width = defaultSettings.profiles[0].width;
|
||||
}
|
||||
if(reset.all || reset.stickDistance) {
|
||||
profile.stickDistance = defaultSettings.profiles[0].stickDistance;
|
||||
}
|
||||
if(reset.all || reset.stickMode2) {
|
||||
profile.stickMode2 = defaultSettings.profiles[0].stickMode2;
|
||||
}
|
||||
if(reset.all || reset.videoFormat) {
|
||||
profile.videoFormat = defaultSettings.profiles[0].videoFormat;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
fs.writeFile(SettingPath, formatXML(xmlStr, {collapseContent: true}), function(err) {
|
||||
writeSettings();
|
||||
}
|
||||
|
||||
function editProfile(optiones:{profileName?:string, fps?:number, width?:number, stickDistance?:number, stickMode2?:boolean, videoFormat?:VideoFormat}, profileName?:string) {
|
||||
if(profileName === undefined) {
|
||||
profileName = getActiveProfile().profileName;
|
||||
}
|
||||
|
||||
settingList.profiles.forEach(profile => {
|
||||
if(profile.profileName === profileName) {
|
||||
if(optiones.profileName !== undefined) {
|
||||
profile.profileName = optiones.profileName;
|
||||
}
|
||||
if(optiones.fps !== undefined) {
|
||||
profile.fps = optiones.fps;
|
||||
}
|
||||
if(optiones.width !== undefined) {
|
||||
profile.width = optiones.width;
|
||||
}
|
||||
if(optiones.stickDistance !== undefined) {
|
||||
profile.stickDistance = optiones.stickDistance;
|
||||
}
|
||||
if(optiones.stickMode2 !== undefined) {
|
||||
profile.stickMode2 = optiones.stickMode2;
|
||||
}
|
||||
if(optiones.videoFormat !== undefined) {
|
||||
profile.videoFormat = optiones.videoFormat;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
writeSettings();
|
||||
}
|
||||
|
||||
function setInOutSettings(args:{logs?:string[], output?:string}) {
|
||||
if(args.logs !== undefined) {
|
||||
settingList.logs = args.logs;
|
||||
}
|
||||
if(args.output !== undefined) {
|
||||
settingList.output = args.output;
|
||||
}
|
||||
writeSettings();
|
||||
}
|
||||
function getInOutSettings() {
|
||||
return {logs: settingList.logs, output: settingList.output};
|
||||
}
|
||||
|
||||
function setShowRenderTerminal(show:boolean) {
|
||||
settingList.showRenderTerminal = show;
|
||||
writeSettings();
|
||||
}
|
||||
function getShowRenderTerminal() {
|
||||
return settingList.showRenderTerminal;
|
||||
}
|
||||
|
||||
function removeProfile(profileName?:string) {
|
||||
if(profileName === undefined) {
|
||||
profileName = getActiveProfile().profileName;
|
||||
}
|
||||
|
||||
settingList.profiles.forEach(profile => {
|
||||
if(profile.profileName === profileName) {
|
||||
settingList.profiles.splice(settingList.profiles.indexOf(profile), 1);
|
||||
}
|
||||
});
|
||||
|
||||
writeSettings();
|
||||
}
|
||||
|
||||
function setActiveProfile(profileName:string) {
|
||||
settingList.profiles.forEach(profile => {
|
||||
if(profile.profileName === profileName) {
|
||||
settingList.activeProfile = profile.profileName;
|
||||
}
|
||||
});
|
||||
|
||||
writeSettings();
|
||||
}
|
||||
|
||||
function writeSettings(settings?:JSONSettings) {
|
||||
if(settings === undefined) {
|
||||
settings = settingList;
|
||||
}
|
||||
|
||||
fs.writeFile(SettingPath, JSON.stringify(settings), function(err) {
|
||||
if(err) {
|
||||
logger.errorMSG(String(err));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function settingListLoadDefault() {
|
||||
updateSettings({
|
||||
fps:defaultSettings.fps,
|
||||
width:defaultSettings.width,
|
||||
stickDistance:defaultSettings.stickDistance,
|
||||
stickMode2:defaultSettings.stickMode2,
|
||||
videoFormat:defaultSettings.videoFormat,
|
||||
function getActiveProfile() {
|
||||
let activeProfile;
|
||||
settingList.profiles.forEach(profile => {
|
||||
if(profile.profileName === settingList.activeProfile) {
|
||||
activeProfile = profile;
|
||||
}
|
||||
});
|
||||
|
||||
if(activeProfile === undefined) {
|
||||
activeProfile = defaultSettings.profiles[0];
|
||||
logger.error("Active profile not found, using default profile");
|
||||
}
|
||||
|
||||
return activeProfile;
|
||||
}
|
||||
|
||||
function importProfile(importSucces:CallableFunction) {
|
||||
dialog.showOpenDialog({
|
||||
properties: [
|
||||
"openFile"
|
||||
],
|
||||
filters: [
|
||||
{
|
||||
name: "StickExporterTX-Profile",
|
||||
extensions: [
|
||||
"setp"
|
||||
]
|
||||
}
|
||||
]
|
||||
}).then(async result => {
|
||||
if(result.filePaths.length === 1) {
|
||||
const rawData = await fs.promises.readFile(result.filePaths[0], "utf8");
|
||||
const jsonData = JSON.parse(rawData) as JSONProfile[];
|
||||
const importProfiles:JSONProfile[] = [];
|
||||
jsonData.forEach(profile => {
|
||||
importProfiles.push({
|
||||
profileName: profile.profileName,
|
||||
fps: profile.fps,
|
||||
width: profile.width,
|
||||
stickDistance: profile.stickDistance,
|
||||
stickMode2: profile.stickMode2,
|
||||
videoFormat: profile.videoFormat
|
||||
});
|
||||
});
|
||||
|
||||
importProfiles.forEach(importProfile => {
|
||||
while(settingList.profiles.find(profile => profile.profileName === importProfile.profileName) !== undefined) {
|
||||
importProfile.profileName = importProfile.profileName + " (imported)";
|
||||
}
|
||||
settingList.profiles.push(importProfile);
|
||||
});
|
||||
writeSettings();
|
||||
importSucces();
|
||||
}
|
||||
}).catch(err => {
|
||||
logger.errorMSG("Import faulty: "+err);
|
||||
});
|
||||
}
|
||||
|
||||
function getLogList() {
|
||||
return settingList.log.split("\"\"");
|
||||
}
|
||||
|
||||
function getLogSize(index:number) {
|
||||
const logList = settingList.log.substring(1).slice(0, -1).split('""');
|
||||
|
||||
return fs.statSync(logList[index]).size;
|
||||
function exportProfile() {
|
||||
dialog.showMessageBox({
|
||||
type: "question",
|
||||
noLink: true,
|
||||
buttons: ["This", "All", "Cancel"],
|
||||
title: "Export profile",
|
||||
message: "Do you want to export all profiles or just the active one?"
|
||||
}).then(result => {
|
||||
if(result.response === 0) {
|
||||
dialog.showSaveDialog({
|
||||
filters: [
|
||||
{
|
||||
name: "StickExporterTX-Profile",
|
||||
extensions: [
|
||||
"setp"
|
||||
]
|
||||
}
|
||||
]
|
||||
}).then(result => {
|
||||
if(result.filePath !== undefined) {
|
||||
fs.writeFile(result.filePath, JSON.stringify([getActiveProfile()]), function(err) {
|
||||
if(err) {
|
||||
logger.errorMSG(String(err));
|
||||
}
|
||||
});
|
||||
}
|
||||
}).catch(err => {
|
||||
logger.errorMSG("Export faulty: "+err);
|
||||
});
|
||||
}
|
||||
else if(result.response === 1) {
|
||||
dialog.showSaveDialog({
|
||||
filters: [
|
||||
{
|
||||
name: "StickExporterTX-Profile",
|
||||
extensions: [
|
||||
"setp"
|
||||
]
|
||||
}
|
||||
]
|
||||
}).then(result => {
|
||||
if(result.filePath !== undefined) {
|
||||
fs.writeFile(result.filePath, JSON.stringify(settingList.profiles), function(err) {
|
||||
if(err) {
|
||||
logger.errorMSG(String(err));
|
||||
}
|
||||
});
|
||||
}
|
||||
}).catch(err => {
|
||||
logger.errorMSG("Export faulty: "+err);
|
||||
});
|
||||
}
|
||||
}).catch(err => {
|
||||
logger.errorMSG("Export faulty: "+err);
|
||||
});
|
||||
}
|
||||
|
||||
export {
|
||||
updateSettings,
|
||||
settingListLoadDefault,
|
||||
settingList,
|
||||
getLogList,
|
||||
getLogSize,
|
||||
getProfiles,
|
||||
createProfile,
|
||||
ProfileLoadDefault,
|
||||
editProfile,
|
||||
removeProfile,
|
||||
setActiveProfile,
|
||||
getActiveProfile,
|
||||
setInOutSettings,
|
||||
getInOutSettings,
|
||||
setShowRenderTerminal,
|
||||
getShowRenderTerminal,
|
||||
importProfile,
|
||||
exportProfile,
|
||||
VideoFormat
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, {useEffect, useState} from "react";
|
||||
import { dialog } from "@electron/remote";
|
||||
import { settingList, updateSettings } from "../settings";
|
||||
import { setInOutSettings, getInOutSettings } from "../settings";
|
||||
import logger from "../logger";
|
||||
import {blender, blenderCmd} from "../blenderController";
|
||||
import openFolder from "../openFolder";
|
||||
@@ -8,7 +8,7 @@ import {platformCharacter} from "../paths";
|
||||
import {logList, reloadAllLogs, updateLogs} from "../logReader";
|
||||
|
||||
function MainPage() {
|
||||
const [output, setOutput] = useState(settingList.output);
|
||||
const [output, setOutput] = useState(getInOutSettings().output);
|
||||
const [logTable, setLogTable] = useState([<tr key={0}></tr>]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -32,14 +32,14 @@ function MainPage() {
|
||||
<div className="dataDiv">
|
||||
<button id="openLogButton" onClick={() => addLog(setLogTable)}>Add Log(s)</button>
|
||||
<button id="deleteLogsButton" onClick={async () => {
|
||||
updateSettings({log:""});
|
||||
setInOutSettings({logs:[]});
|
||||
await reloadAllLogs();
|
||||
updateLogTable(setLogTable);
|
||||
}}>Delete All</button>
|
||||
</div>
|
||||
<div className="dataDiv" id="outputDiv">
|
||||
<h4>Output Folder:</h4>
|
||||
<p id="output" onClick={() => openFolder(settingList.output)}>{output}</p>
|
||||
<p id="output" onClick={() => openFolder(getInOutSettings().output)}>{output}</p>
|
||||
<button onClick={() => openVid(setOutput)}>Select Folder</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -60,8 +60,8 @@ function updateLogTable(setLogTable:React.Dispatch<React.SetStateAction<JSX.Elem
|
||||
fontWeight: "lighter"
|
||||
}}>({log.time.length.formatted})</td>
|
||||
<td><button className="listButton" onClick={async () => {
|
||||
const newLogs = settingList.log.replace('"'+log.path+'"', "");
|
||||
updateSettings({log:newLogs});
|
||||
const newLogs = getInOutSettings().logs.filter(value => value !== log.path);
|
||||
setInOutSettings({logs:newLogs});
|
||||
await updateLogs();
|
||||
updateLogTable(setLogTable);
|
||||
}}>Delete</button></td>
|
||||
@@ -69,7 +69,7 @@ function updateLogTable(setLogTable:React.Dispatch<React.SetStateAction<JSX.Elem
|
||||
}));
|
||||
}
|
||||
|
||||
if(settingList.log == "") {
|
||||
if(getInOutSettings().logs.length === 0) {
|
||||
setLogTable([]);
|
||||
} else {
|
||||
getData();
|
||||
@@ -90,17 +90,15 @@ function addLog(setLogTable:React.Dispatch<React.SetStateAction<JSX.Element[]>>)
|
||||
}
|
||||
]
|
||||
}).then(async result => {
|
||||
let logStr = "";
|
||||
const newLogs = getInOutSettings().logs;
|
||||
result.filePaths.forEach(value => {
|
||||
const logToAdd = "\"" + value + "\"";
|
||||
if(settingList.log.includes(logToAdd)) {
|
||||
logger.warningMSG("Log " + logToAdd + " already added.");
|
||||
if(getInOutSettings().logs.includes(value)) {
|
||||
logger.warningMSG("Log \"" + value + "\" already added.");
|
||||
} else {
|
||||
logStr += "\"" + String(value) + "\"";
|
||||
newLogs.push(value);
|
||||
}
|
||||
});
|
||||
const newLogs = settingList.log + logStr;
|
||||
updateSettings({log:newLogs});
|
||||
setInOutSettings({logs:newLogs});
|
||||
await updateLogs();
|
||||
updateLogTable(setLogTable);
|
||||
}).catch(err => {
|
||||
@@ -115,7 +113,7 @@ function openVid(updateHook:React.Dispatch<React.SetStateAction<string>>) {
|
||||
]
|
||||
}).then(result => {
|
||||
if(result.filePaths.length > 0) {
|
||||
updateSettings({output:String(result.filePaths)});
|
||||
setInOutSettings({output:String(result.filePaths)});
|
||||
updateHook(String(result.filePaths));
|
||||
}
|
||||
}).catch(err => {
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import React, { CSSProperties } from "react";
|
||||
import {openPage, Page} from "../../renderer";
|
||||
import {renderInfo} from "../blenderController";
|
||||
import {outputArgs} from "../blenderController";
|
||||
import openFolder from "../openFolder";
|
||||
import {settingList} from "../settings";
|
||||
import {getInOutSettings, getActiveProfile} from "../settings";
|
||||
import VideoPlayer from "./videoPlayer";
|
||||
import path from 'path';
|
||||
import {platformCharacter} from "../paths";
|
||||
import {VideoJsPlayerOptions} from "video.js";
|
||||
import {logList} from "../logReader";
|
||||
import {renderInfo} from "../progressController";
|
||||
|
||||
const detailsStyle:CSSProperties = {
|
||||
display: "flex",
|
||||
@@ -18,10 +18,21 @@ const detailsInnerStyle:CSSProperties = {
|
||||
marginRight: "5px",
|
||||
}
|
||||
|
||||
function RenderFinishPage() {
|
||||
const [logPlaying, setLogPlaying] = React.useState(path.join(settingList.output, settingList.log.substring(1).slice(0, -1).split('""')[0].split(platformCharacter())[settingList.log.substring(1).slice(0, -1).split('""')[0].split(platformCharacter()).length - 1].replace(".csv", "."+settingList.videoFormat)));
|
||||
const VideoSpanStyle:CSSProperties = {
|
||||
display: "flex",
|
||||
alignItems: "center"
|
||||
}
|
||||
const videoSelectStyle:CSSProperties = {
|
||||
padding: "4px 0",
|
||||
border: "0",
|
||||
borderRadius: "14px",
|
||||
cursor: "pointer",
|
||||
textAlign: "center",
|
||||
fontSize: "large"
|
||||
}
|
||||
|
||||
const [outputList, setOutputList] = React.useState([<li key={0}></li>]);
|
||||
function RenderFinishPage() {
|
||||
const [logPlaying, setLogPlaying] = React.useState(logList[0].name);
|
||||
|
||||
const videoPlayerOptions:VideoJsPlayerOptions = {
|
||||
controls: true,
|
||||
@@ -32,25 +43,17 @@ function RenderFinishPage() {
|
||||
}
|
||||
};
|
||||
|
||||
const [videoSource, setVideoSource] = React.useState({src: logPlaying, type: 'video/'+settingList.videoFormat.toUpperCase()});
|
||||
const [videoSource, setVideoSource] = React.useState({src: path.join(getInOutSettings().output, logPlaying+"."+getActiveProfile().videoFormat), type: 'video/'+getActiveProfile().videoFormat.toUpperCase()});
|
||||
|
||||
const OutputList = outputArgs.map((output, index) => {
|
||||
const outputName = output.substring(output.lastIndexOf("\\")+1);
|
||||
return <option key={index} value={outputName} title={output}>{outputName}</option>
|
||||
});
|
||||
|
||||
React.useEffect(() => {
|
||||
setOutputList(logList.map((inputLog, index) => {
|
||||
const outputLogPath = path.join(settingList.output, inputLog.name+"."+settingList.videoFormat);
|
||||
|
||||
return <li key={index}>
|
||||
<p style={{
|
||||
textDecoration: logPlaying === outputLogPath ? "underline" : "none",
|
||||
cursor: logPlaying === outputLogPath ? "default" : "pointer",
|
||||
}} onClick={() => {
|
||||
setLogPlaying(outputLogPath);
|
||||
}} title={outputLogPath}>{inputLog.name}</p>
|
||||
</li>
|
||||
}));
|
||||
|
||||
setVideoSource({
|
||||
src: logPlaying,
|
||||
type: 'video/'+settingList.videoFormat.toUpperCase()
|
||||
src: path.join(getInOutSettings().output, logPlaying.replace(".csv", "."+getActiveProfile().videoFormat)),
|
||||
type: 'video/'+getActiveProfile().videoFormat.toUpperCase()
|
||||
});
|
||||
}, [logPlaying]);
|
||||
|
||||
@@ -78,14 +81,20 @@ function RenderFinishPage() {
|
||||
}} onClick={() => {
|
||||
openPage(Page.Main);
|
||||
}}>Finish</button>
|
||||
<button onClick={() => openFolder(settingList.output)}>Open Output Folder</button>
|
||||
<button onClick={() => openFolder(getInOutSettings().output)}>Open Output Folder</button>
|
||||
<div style={{
|
||||
marginTop: "10px"
|
||||
}}>
|
||||
<span style={VideoSpanStyle}>
|
||||
<label htmlFor="vslct">
|
||||
<select style={videoSelectStyle} id="vslct" required={true} value={logPlaying} onChange={e => {
|
||||
setLogPlaying(e.target.value);
|
||||
}}>
|
||||
{OutputList}
|
||||
</select>
|
||||
</label>
|
||||
</span>
|
||||
<VideoPlayer options={videoPlayerOptions} src={videoSource} />
|
||||
<ol>
|
||||
{outputList}
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, {useEffect, useState} from "react";
|
||||
import { settingList, getLogList } from "../settings";
|
||||
import React, {useEffect, useRef, useState} from "react";
|
||||
import {getInOutSettings, getShowRenderTerminal, setShowRenderTerminal} from "../settings";
|
||||
import openFolder from "../openFolder";
|
||||
import { blenderCmd, blender } from "../blenderController";
|
||||
|
||||
@@ -8,11 +8,15 @@ let setStatus:React.Dispatch<React.SetStateAction<string>>;
|
||||
let setRenderDisplayProgress:React.Dispatch<React.SetStateAction<number>>;
|
||||
let setPastTime:React.Dispatch<React.SetStateAction<string>>;
|
||||
let setRemainingTime:React.Dispatch<React.SetStateAction<string>>;
|
||||
let setTerminalLines:React.Dispatch<React.SetStateAction<JSX.Element[]>>;
|
||||
|
||||
let pastTimeNow = "0m 0s";
|
||||
let remainingTimeNow = "calculating...";
|
||||
|
||||
function RenderingPage() {
|
||||
const [terminalHidden, setTerminalHidden] = useState(getShowRenderTerminal() ? "block" : "none");
|
||||
const [terminalScroll, setTerminalScroll] = useState(true);
|
||||
const [scrollButtonText, setScrollButtonText] = useState("pause scroll");
|
||||
const [logNumber, setLogNumberInner] = useState("0");
|
||||
setLogNumber = setLogNumberInner;
|
||||
const [status, setStatusInner] = useState("Idle");
|
||||
@@ -23,6 +27,24 @@ function RenderingPage() {
|
||||
setPastTime = setPastTimeInner;
|
||||
const [remainingTime, setRemainingTimeInner] = useState("calculating...");
|
||||
setRemainingTime = setRemainingTimeInner;
|
||||
const [terminalLines, setTerminalLinesInner] = useState([
|
||||
<tr key={0}>
|
||||
<td className="terminalTableNumber">0:</td>
|
||||
<td>==== Start ====</td>
|
||||
</tr>
|
||||
]);
|
||||
setTerminalLines = setTerminalLinesInner;
|
||||
|
||||
const messagesEndRef = useRef() as React.MutableRefObject<HTMLDivElement>;
|
||||
|
||||
useEffect(() => {
|
||||
if(terminalScroll) {
|
||||
messagesEndRef.current?.scrollIntoView();
|
||||
setScrollButtonText("pause scroll");
|
||||
} else {
|
||||
setScrollButtonText("continue scroll");
|
||||
}
|
||||
}, [terminalLines, messagesEndRef, terminalScroll]);
|
||||
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
@@ -34,7 +56,7 @@ function RenderingPage() {
|
||||
|
||||
return (
|
||||
<div id="content">
|
||||
<p>{"Log " + logNumber + "/" + getLogList().length}</p>
|
||||
<p>{"Log " + logNumber + "/" + getInOutSettings().logs.length}</p>
|
||||
<p>{status}</p>
|
||||
<div className="progress">
|
||||
<div className="progress-done" style={{
|
||||
@@ -69,7 +91,27 @@ function RenderingPage() {
|
||||
}>{remainingTime}</p>
|
||||
</div>
|
||||
<button id="stopRenderButton" onClick={() => blender(blenderCmd.stopRendering)}>Stop</button>
|
||||
<button onClick={() => openFolder(settingList.output)}>Open Output Folder</button>
|
||||
<button onClick={() => openFolder(getInOutSettings().output)}>Open Output Folder</button>
|
||||
<button onClick={() => {
|
||||
if (getShowRenderTerminal()) {
|
||||
setTerminalHidden("none");
|
||||
setShowRenderTerminal(false);
|
||||
} else {
|
||||
setTerminalHidden("block");
|
||||
setShowRenderTerminal(true);
|
||||
}
|
||||
}} style={{marginLeft:"10px"}}>Details</button>
|
||||
<div style={{display: terminalHidden}}>
|
||||
<div id="outerTerminal">
|
||||
<div id="innerTerminal">
|
||||
<table id="terminalTable">
|
||||
{terminalLines}
|
||||
</table>
|
||||
<div ref={messagesEndRef}/>
|
||||
</div>
|
||||
</div>
|
||||
<button style={{padding:"4px"}} onClick={() => setTerminalScroll(!terminalScroll)}>{scrollButtonText}</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -82,6 +124,17 @@ function setRemainingTimeNow(time:string) {
|
||||
remainingTimeNow = time;
|
||||
}
|
||||
|
||||
function addTerminalLine(line:string) {
|
||||
setTerminalLines((prev:JSX.Element[]) => {
|
||||
return [...prev,
|
||||
<tr key={prev.length}>
|
||||
<td className="terminalTableNumber">{prev.length}:</td>
|
||||
<td>{line}</td>
|
||||
</tr>
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
export default RenderingPage;
|
||||
export {
|
||||
setLogNumber,
|
||||
@@ -90,5 +143,6 @@ export {
|
||||
setPastTime,
|
||||
setRemainingTime,
|
||||
setPastTimeNow,
|
||||
setRemainingTimeNow
|
||||
setRemainingTimeNow,
|
||||
addTerminalLine
|
||||
};
|
||||
@@ -1,17 +1,162 @@
|
||||
import React, {useState, useEffect, CSSProperties} from "react";
|
||||
import { settingList, updateSettings, settingListLoadDefault, VideoFormat } from "../settings";
|
||||
import { VideoFormat, editProfile, getActiveProfile, ProfileLoadDefault, getProfiles, setActiveProfile, createProfile, removeProfile, exportProfile, importProfile } from "../settings";
|
||||
import {blender, blenderCmd, renderingPicture} from "../blenderController";
|
||||
import {dataPath} from "../paths";
|
||||
import path from "path";
|
||||
import logger from "../logger";
|
||||
|
||||
let setRenderImg:React.Dispatch<React.SetStateAction<string>>;
|
||||
let setRenderLoading:React.Dispatch<React.SetStateAction<boolean>>;
|
||||
let pageLoaded = false;
|
||||
|
||||
const VideoFormatOptions = Object.keys(VideoFormat).filter((el) => { return isNaN(Number(el)) }).map(key => {
|
||||
return <option key={key} value={key}>{key}</option>;
|
||||
});
|
||||
|
||||
function picturePath() {
|
||||
return path.join(dataPath, "render.png?t="+Date.now());
|
||||
}
|
||||
|
||||
const overlayArrowStyle:CSSProperties = {
|
||||
width: "50%",
|
||||
height: "50%",
|
||||
fill: "white"
|
||||
}
|
||||
const settingLabelStyle:CSSProperties = {
|
||||
position: "relative",
|
||||
paddingLeft: "10px",
|
||||
paddingRight: "10px",
|
||||
height: "100%",
|
||||
background: "#00c24a",
|
||||
borderTopLeftRadius: "19px",
|
||||
borderBottomLeftRadius: "19px",
|
||||
fontWeight: "bolder",
|
||||
overflow: "hidden",
|
||||
display: "flex",
|
||||
alignItems: "center"
|
||||
}
|
||||
|
||||
function InputSpan({name, value, min, step, onChange, onReset}:{name:string, value:number, min:number, step:number, onChange:React.ChangeEventHandler<HTMLInputElement>, onReset:CallableFunction}) {
|
||||
const [dispayOverlay, setDisplayOverlay] = useState("none");
|
||||
|
||||
return (
|
||||
<span className="inputSelectSpan">
|
||||
<div style={settingLabelStyle} onMouseEnter={() => {setDisplayOverlay("flex");}} onMouseLeave={() => {setDisplayOverlay("none");}}>
|
||||
{name}
|
||||
<div className="overlay" style={{display: dispayOverlay}} onClick={() => {onReset()}}>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" style={overlayArrowStyle}>
|
||||
{/* <!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --> */}
|
||||
<path d="M125.7 160H176c17.7 0 32 14.3 32 32s-14.3 32-32 32H48c-17.7 0-32-14.3-32-32V64c0-17.7 14.3-32 32-32s32 14.3 32 32v51.2L97.6 97.6c87.5-87.5 229.3-87.5 316.8 0s87.5 229.3 0 316.8s-229.3 87.5-316.8 0c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0c62.5 62.5 163.8 62.5 226.3 0s62.5-163.8 0-226.3s-163.8-62.5-226.3 0L125.7 160z"/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<input id={name+" Input"} type="number" value={value.toString()} min={min.toString()} step={step.toString()} onChange={onChange}/>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
function TextSpan({name, value, placeholder, onChange, onReset}:{name:string, value:string, placeholder:string, onChange:React.ChangeEventHandler<HTMLInputElement>, onReset?:CallableFunction}) {
|
||||
const [dispayOverlay, setDisplayOverlay] = useState("none");
|
||||
|
||||
const Overlay = (
|
||||
<div className="overlay" style={{display: dispayOverlay}} onClick={() => {
|
||||
if(onReset !== undefined) {
|
||||
onReset()
|
||||
}
|
||||
}}>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" style={overlayArrowStyle}>
|
||||
{/* <!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --> */}
|
||||
<path d="M125.7 160H176c17.7 0 32 14.3 32 32s-14.3 32-32 32H48c-17.7 0-32-14.3-32-32V64c0-17.7 14.3-32 32-32s32 14.3 32 32v51.2L97.6 97.6c87.5-87.5 229.3-87.5 316.8 0s87.5 229.3 0 316.8s-229.3 87.5-316.8 0c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0c62.5 62.5 163.8 62.5 226.3 0s62.5-163.8 0-226.3s-163.8-62.5-226.3 0L125.7 160z"/>
|
||||
</svg>
|
||||
</div>
|
||||
)
|
||||
|
||||
return (
|
||||
<span className="inputSelectSpan">
|
||||
<div style={settingLabelStyle} onMouseEnter={() => {setDisplayOverlay("flex");}} onMouseLeave={() => {setDisplayOverlay("none");}}>
|
||||
{name}
|
||||
{onReset !== undefined ? Overlay : null}
|
||||
</div>
|
||||
<input id={name+" Input"} type="text" value={value.toString()} placeholder={placeholder} onChange={onChange}/>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
function SelectSpan({name, value, optiones, onChange, onReset}:{name:string, value:string, optiones:JSX.Element[], onChange:React.ChangeEventHandler<HTMLSelectElement>, onReset?:CallableFunction}) {
|
||||
const [dispayOverlay, setDisplayOverlay] = useState("none");
|
||||
|
||||
const Overlay = (
|
||||
<div className="overlay" style={{display: dispayOverlay}} onClick={() => {
|
||||
if(onReset !== undefined) {
|
||||
onReset()
|
||||
}
|
||||
}}>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" style={overlayArrowStyle}>
|
||||
{/* <!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --> */}
|
||||
<path d="M125.7 160H176c17.7 0 32 14.3 32 32s-14.3 32-32 32H48c-17.7 0-32-14.3-32-32V64c0-17.7 14.3-32 32-32s32 14.3 32 32v51.2L97.6 97.6c87.5-87.5 229.3-87.5 316.8 0s87.5 229.3 0 316.8s-229.3 87.5-316.8 0c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0c62.5 62.5 163.8 62.5 226.3 0s62.5-163.8 0-226.3s-163.8-62.5-226.3 0L125.7 160z"/>
|
||||
</svg>
|
||||
</div>
|
||||
)
|
||||
|
||||
return (
|
||||
<span className="inputSelectSpan">
|
||||
<div style={settingLabelStyle} onMouseEnter={() => {setDisplayOverlay("flex");}} onMouseLeave={() => {setDisplayOverlay("none");}}>
|
||||
{name}
|
||||
{onReset !== undefined ? Overlay : null}
|
||||
</div>
|
||||
<select id={name+" slct"} required={true} value={value} onChange={onChange}>
|
||||
{optiones}
|
||||
</select>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
function ToggleSpan({name, state, checkedValue, uncheckedValue, onChange, onReset}:{name:string, state:boolean, checkedValue:string, uncheckedValue:string, onChange(checked:boolean):void, onReset:CallableFunction}) {
|
||||
const [dispayOverlay, setDisplayOverlay] = useState("none");
|
||||
const [checked, setChecked] = useState(state);
|
||||
|
||||
useEffect(() => {
|
||||
setChecked(state);
|
||||
}, [state]);
|
||||
|
||||
const toggleStyle:CSSProperties = {
|
||||
height: "100%",
|
||||
border: "0",
|
||||
borderTopRightRadius: "18px",
|
||||
borderBottomRightRadius: "18px",
|
||||
textAlign: "center",
|
||||
fontSize: "large",
|
||||
paddingLeft: "15px",
|
||||
paddingRight: "15px",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
backgroundColor: checked ? "#2196F3" : "white",
|
||||
color: checked ? "white" : "black",
|
||||
cursor: "pointer",
|
||||
userSelect: "none"
|
||||
}
|
||||
|
||||
return (
|
||||
<span className="inputSelectSpan">
|
||||
<div style={settingLabelStyle} onMouseEnter={() => {setDisplayOverlay("flex");}} onMouseLeave={() => {setDisplayOverlay("none");}}>
|
||||
{name}
|
||||
<div className="overlay" style={{display: dispayOverlay}} onClick={() => {onReset()}}>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" style={overlayArrowStyle}>
|
||||
{/* <!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --> */}
|
||||
<path d="M125.7 160H176c17.7 0 32 14.3 32 32s-14.3 32-32 32H48c-17.7 0-32-14.3-32-32V64c0-17.7 14.3-32 32-32s32 14.3 32 32v51.2L97.6 97.6c87.5-87.5 229.3-87.5 316.8 0s87.5 229.3 0 316.8s-229.3 87.5-316.8 0c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0c62.5 62.5 163.8 62.5 226.3 0s62.5-163.8 0-226.3s-163.8-62.5-226.3 0L125.7 160z"/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div style={toggleStyle} onClick={() => {
|
||||
onChange(!checked);
|
||||
setChecked(!checked);
|
||||
}}>
|
||||
{checked ? checkedValue : uncheckedValue}
|
||||
</div>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
const RenderLoadingSpinner = () => (
|
||||
<div id="renderLoadingDiv">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
||||
@@ -40,9 +185,9 @@ function VideoFormatWarning({videoFormat}:{videoFormat:VideoFormat}) {
|
||||
}
|
||||
|
||||
const style:CSSProperties = {
|
||||
height: "30px",
|
||||
width: "30px",
|
||||
fill: "orange",
|
||||
height: "35px",
|
||||
width: "35px",
|
||||
fill: "yellow",
|
||||
paddingLeft: "5px",
|
||||
|
||||
};
|
||||
@@ -57,21 +202,91 @@ function VideoFormatWarning({videoFormat}:{videoFormat:VideoFormat}) {
|
||||
);
|
||||
}
|
||||
|
||||
function ProfileSettings({setNameProfile, setProfileName, setNewProfileName, profileName, setNewProfileType, setProfileOptions}:{setNameProfile:CallableFunction, setProfileName:CallableFunction, setNewProfileName:CallableFunction, profileName:string, setNewProfileType:CallableFunction, setProfileOptions:CallableFunction}) {
|
||||
return (
|
||||
<>
|
||||
<button title="Rename" style={{width:"35px", height:"35px", marginLeft:"5px"}} onClick={() => {
|
||||
setNewProfileName(profileName);
|
||||
setNameProfile(true);
|
||||
setNewProfileType("rename");
|
||||
}}>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" style={{fill:"white"}}>
|
||||
{/* <!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --> */}
|
||||
<path d="M471.6 21.7c-21.9-21.9-57.3-21.9-79.2 0L362.3 51.7l97.9 97.9 30.1-30.1c21.9-21.9 21.9-57.3 0-79.2L471.6 21.7zm-299.2 220c-6.1 6.1-10.8 13.6-13.5 21.9l-29.6 88.8c-2.9 8.6-.6 18.1 5.8 24.6s15.9 8.7 24.6 5.8l88.8-29.6c8.2-2.8 15.7-7.4 21.9-13.5L437.7 172.3 339.7 74.3 172.4 241.7zM96 64C43 64 0 107 0 160V416c0 53 43 96 96 96H352c53 0 96-43 96-96V320c0-17.7-14.3-32-32-32s-32 14.3-32 32v96c0 17.7-14.3 32-32 32H96c-17.7 0-32-14.3-32-32V160c0-17.7 14.3-32 32-32h96c17.7 0 32-14.3 32-32s-14.3-32-32-32H96z"/>
|
||||
</svg>
|
||||
</button>
|
||||
<button title="Create" style={{width:"35px", height:"35px", marginLeft:"5px"}} onClick={() => {
|
||||
setNewProfileName("");
|
||||
setNameProfile(true);
|
||||
setNewProfileType("create");
|
||||
}}>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" style={{fill:"white"}}>
|
||||
{/* <!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --> */}
|
||||
<path d="M256 80c0-17.7-14.3-32-32-32s-32 14.3-32 32V224H48c-17.7 0-32 14.3-32 32s14.3 32 32 32H192V432c0 17.7 14.3 32 32 32s32-14.3 32-32V288H400c17.7 0 32-14.3 32-32s-14.3-32-32-32H256V80z"/>
|
||||
</svg>
|
||||
</button>
|
||||
<button title="Duplicate" style={{width:"35px", height:"35px", marginLeft:"5px"}} onClick={() => {
|
||||
setNewProfileName("");
|
||||
setNameProfile(true);
|
||||
setNewProfileType("duplicate");
|
||||
}}>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" style={{fill:"white"}}>
|
||||
{/* <!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --> */}
|
||||
<path d="M502.6 70.63l-61.25-61.25C435.4 3.371 427.2 0 418.7 0H255.1c-35.35 0-64 28.66-64 64l.0195 256C192 355.4 220.7 384 256 384h192c35.2 0 64-28.8 64-64V93.25C512 84.77 508.6 76.63 502.6 70.63zM464 320c0 8.836-7.164 16-16 16H255.1c-8.838 0-16-7.164-16-16L239.1 64.13c0-8.836 7.164-16 16-16h128L384 96c0 17.67 14.33 32 32 32h47.1V320zM272 448c0 8.836-7.164 16-16 16H63.1c-8.838 0-16-7.164-16-16L47.98 192.1c0-8.836 7.164-16 16-16H160V128H63.99c-35.35 0-64 28.65-64 64l.0098 256C.002 483.3 28.66 512 64 512h192c35.2 0 64-28.8 64-64v-32h-47.1L272 448z"/>
|
||||
</svg>
|
||||
</button>
|
||||
<button title="Delete" style={{width:"35px", height:"35px", backgroundColor:"#e1334e", marginLeft:"5px"}} onClick={() => {
|
||||
if (getProfiles().length === 1) {
|
||||
logger.warningMSG("You can't delete the last profile!");
|
||||
return;
|
||||
}
|
||||
removeProfile(profileName);
|
||||
const newActiveProfile = getProfiles()[getProfiles().length - 1];
|
||||
setActiveProfile(newActiveProfile);
|
||||
setProfileName(newActiveProfile);
|
||||
setProfileOptions(getProfiles().map(p => {
|
||||
return <option key={p} value={p}>{p}</option>;
|
||||
}));
|
||||
}}>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" style={{fill:"white"}}>
|
||||
{/* <!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --> */}
|
||||
<path d="M135.2 17.7L128 32H32C14.3 32 0 46.3 0 64S14.3 96 32 96H416c17.7 0 32-14.3 32-32s-14.3-32-32-32H320l-7.2-14.3C307.4 6.8 296.3 0 284.2 0H163.8c-12.1 0-23.2 6.8-28.6 17.7zM416 128H32L53.2 467c1.6 25.3 22.6 45 47.9 45H346.9c25.3 0 46.3-19.7 47.9-45L416 128z"/>
|
||||
</svg>
|
||||
</button>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
function SettingsPage() {
|
||||
|
||||
const [fps, setFps] = useState(settingList.fps);
|
||||
const [width, setWidth] = useState(settingList.width);
|
||||
const [stickDistance, setStickDistance] = useState(settingList.stickDistance);
|
||||
const [stickMode2, setStickMode2] = useState(settingList.stickMode2);
|
||||
const [videoFormat, setVideoFormat] = useState(settingList.videoFormat);
|
||||
const [fps, setFps] = useState(getActiveProfile().fps);
|
||||
const [width, setWidth] = useState(getActiveProfile().width);
|
||||
const [stickDistance, setStickDistance] = useState(getActiveProfile().stickDistance);
|
||||
const [stickMode2, setStickMode2] = useState(getActiveProfile().stickMode2);
|
||||
const [videoFormat, setVideoFormat] = useState(getActiveProfile().videoFormat);
|
||||
const [renderImg, setRenderImgInner] = useState(picturePath());
|
||||
setRenderImg = setRenderImgInner;
|
||||
const [renderLoading, setRenderLoadingInner] = useState(renderingPicture);
|
||||
setRenderLoading = setRenderLoadingInner;
|
||||
const [profileOptions, setProfileOptions] = useState(getProfiles().map(p => {
|
||||
return <option key={p} value={p}>{p}</option>;
|
||||
}));
|
||||
const [profileName, setProfileName] = useState(getActiveProfile().profileName);
|
||||
const [newProfileName, setNewProfileName] = useState("");
|
||||
const [nameProfile, setNameProfile] = useState(false);
|
||||
const [newProfileType, setNewProfileType] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
setFps(getActiveProfile().fps);
|
||||
setWidth(getActiveProfile().width);
|
||||
setStickDistance(getActiveProfile().stickDistance);
|
||||
setStickMode2(getActiveProfile().stickMode2);
|
||||
setVideoFormat(getActiveProfile().videoFormat);
|
||||
}, [profileName]);
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
updateSettings({width, stickDistance, stickMode2});
|
||||
editProfile({width, stickDistance, stickMode2});
|
||||
blender(blenderCmd.getRender);
|
||||
}, 500);
|
||||
|
||||
@@ -80,7 +295,7 @@ function SettingsPage() {
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
updateSettings({fps, videoFormat});
|
||||
editProfile({fps, videoFormat});
|
||||
}, 500);
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
@@ -88,66 +303,133 @@ function SettingsPage() {
|
||||
|
||||
pageLoaded = true;
|
||||
|
||||
const VideoFormatOptions = Object.keys(VideoFormat).filter((el) => { return isNaN(Number(el)) }).map(key => {
|
||||
return <option key={key} value={key}>{key}</option>;
|
||||
});
|
||||
|
||||
return (
|
||||
<div id="content">
|
||||
<div id="settingRow">
|
||||
<span className="inputSpan">
|
||||
<label>FPS</label>
|
||||
<input id="fpsInput" type="number" value={fps.toString()} min="1" step="1" onChange={e => {
|
||||
if(e.target.value.trim().length !== 0) setFps(parseInt(e.target.value));
|
||||
}}/>
|
||||
</span>
|
||||
<span className="inputSpan">
|
||||
<label>Width</label>
|
||||
<input id="widthInput" type="number" value={width.toString()} min="1" step="1" onChange={e => {
|
||||
if(e.target.value.trim().length !== 0) setWidth(parseInt(e.target.value));
|
||||
}}/>
|
||||
</span>
|
||||
<span className="inputSpan">
|
||||
<label>Stick Distance</label>
|
||||
<input id="stickDistanceInput" type="number" value={stickDistance.toString()} min="0" step="1" onChange={e => {
|
||||
if(e.target.value.trim().length !== 0) setStickDistance(parseInt(e.target.value));
|
||||
}}/>
|
||||
</span>
|
||||
<div style={{
|
||||
display: "flex",
|
||||
}}>
|
||||
{nameProfile? <TextSpan name="Set Profile Name" value={newProfileName} placeholder="Enter Profile Name Here" onChange={e => {
|
||||
setNewProfileName(e.target.value);
|
||||
}}/> : <SelectSpan name="Select Profile" value={profileName} optiones={profileOptions} onChange={ e => {
|
||||
setActiveProfile(e.target.value);
|
||||
setProfileName(e.target.value);
|
||||
}}/>
|
||||
}
|
||||
{nameProfile? <button title="Save" style={{width:"35px", height:"35px", marginLeft:"5px"}} onClick={() => {
|
||||
let profileExists = false;
|
||||
getProfiles().forEach(profile => {
|
||||
if (profile === newProfileName) {
|
||||
profileExists = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (profileExists) {
|
||||
logger.warningMSG("Profile with the name \""+newProfileName+"\" already exists");
|
||||
} else {
|
||||
setProfileName(newProfileName);
|
||||
if(newProfileType === "rename") {
|
||||
editProfile({profileName: newProfileName});
|
||||
} else if (newProfileType === "create") {
|
||||
createProfile(newProfileName, false);
|
||||
} else if (newProfileType === "duplicate") {
|
||||
createProfile(newProfileName, true);
|
||||
}
|
||||
setActiveProfile(newProfileName);
|
||||
}
|
||||
setProfileOptions(getProfiles().map(p => {
|
||||
return <option key={p} value={p}>{p}</option>;
|
||||
}));
|
||||
setNameProfile(false);
|
||||
}}>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" style={{fill:"white"}}>
|
||||
{/* <!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --> */}
|
||||
<path d="M224 256c-35.2 0-64 28.8-64 64c0 35.2 28.8 64 64 64c35.2 0 64-28.8 64-64C288 284.8 259.2 256 224 256zM433.1 129.1l-83.9-83.9C341.1 37.06 328.8 32 316.1 32H64C28.65 32 0 60.65 0 96v320c0 35.35 28.65 64 64 64h320c35.35 0 64-28.65 64-64V163.9C448 151.2 442.9 138.9 433.1 129.1zM128 80h144V160H128V80zM400 416c0 8.836-7.164 16-16 16H64c-8.836 0-16-7.164-16-16V96c0-8.838 7.164-16 16-16h16v104c0 13.25 10.75 24 24 24h192C309.3 208 320 197.3 320 184V83.88l78.25 78.25C399.4 163.2 400 164.8 400 166.3V416z"/>
|
||||
</svg>
|
||||
</button> : <ProfileSettings setNameProfile={setNameProfile} setProfileName={setProfileName} setNewProfileName={setNewProfileName} profileName={profileName} setNewProfileType={setNewProfileType} setProfileOptions={setProfileOptions}/>
|
||||
}
|
||||
</div>
|
||||
<div style={{
|
||||
display: "flex",
|
||||
}}>
|
||||
<button title="Import" style={{width:"35px", height:"35px"}} onClick={() => {
|
||||
importProfile(() => {
|
||||
setProfileOptions(getProfiles().map(p => {
|
||||
return <option key={p} value={p}>{p}</option>;
|
||||
}));
|
||||
});
|
||||
}}>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" style={{fill:"white"}}>
|
||||
{/* <!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --> */}
|
||||
<path d="M128 64c0-35.3 28.7-64 64-64H352V128c0 17.7 14.3 32 32 32H512V448c0 35.3-28.7 64-64 64H192c-35.3 0-64-28.7-64-64V336H302.1l-39 39c-9.4 9.4-9.4 24.6 0 33.9s24.6 9.4 33.9 0l80-80c9.4-9.4 9.4-24.6 0-33.9l-80-80c-9.4-9.4-24.6-9.4-33.9 0s-9.4 24.6 0 33.9l39 39H128V64zm0 224v48H24c-13.3 0-24-10.7-24-24s10.7-24 24-24H128zM512 128H384V0L512 128z"/>
|
||||
</svg>
|
||||
</button>
|
||||
<button title="Export" style={{width:"35px", height:"35px", marginLeft:"5px"}} onClick={() => {
|
||||
exportProfile();
|
||||
}}>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512" style={{fill:"white"}}>
|
||||
{/* <!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --> */}
|
||||
<path d="M32 64C32 28.7 60.7 0 96 0H256V128c0 17.7 14.3 32 32 32H416V288H248c-13.3 0-24 10.7-24 24s10.7 24 24 24H416V448c0 35.3-28.7 64-64 64H96c-35.3 0-64-28.7-64-64V64zM416 336V288H526.1l-39-39c-9.4-9.4-9.4-24.6 0-33.9s24.6-9.4 33.9 0l80 80c9.4 9.4 9.4 24.6 0 33.9l-80 80c-9.4 9.4-24.6 9.4-33.9 0s-9.4-24.6 0-33.9l39-39H416zm0-208H288V0L416 128z"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="settingRow">
|
||||
<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 => {
|
||||
setStickMode2(e.target.checked);
|
||||
}}/>
|
||||
<span className="toggle">
|
||||
<span className="switch"></span>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<span className="selectSpan">
|
||||
<label className="selectSpanLabel">Format</label>
|
||||
<label className="selectSpanSelect" htmlFor="slct">
|
||||
<select id="slct" required={true} value={videoFormat} onChange={e => {
|
||||
setVideoFormat(e.target.value as unknown as VideoFormat);
|
||||
}}>
|
||||
{VideoFormatOptions}
|
||||
</select>
|
||||
</label>
|
||||
{<InputSpan name={"FPS"} value={fps} min={1} step={1} onChange={
|
||||
e => {
|
||||
if(e.target.value.trim().length !== 0) setFps(parseInt(e.target.value));
|
||||
}
|
||||
} onReset={() => {
|
||||
ProfileLoadDefault({fps: true});
|
||||
setFps(getActiveProfile().fps);
|
||||
}}/>}
|
||||
{<InputSpan name="Width" value={width} min={1} step={1} onChange={
|
||||
e => {
|
||||
if(e.target.value.trim().length !== 0) setWidth(parseInt(e.target.value));
|
||||
}
|
||||
} onReset={() => {
|
||||
ProfileLoadDefault({width: true});
|
||||
setWidth(getActiveProfile().width);
|
||||
}}/>}
|
||||
{<InputSpan name="Stick Distance" value={stickDistance} min={0} step={1} onChange={
|
||||
e => {
|
||||
if(e.target.value.trim().length !== 0) setStickDistance(parseInt(e.target.value));
|
||||
}
|
||||
} onReset={() => {
|
||||
ProfileLoadDefault({stickDistance: true});
|
||||
setStickDistance(getActiveProfile().stickDistance);
|
||||
}}/>}
|
||||
</div>
|
||||
<div id="settingRow">
|
||||
{<ToggleSpan name="Stick Mode" state={stickMode2} checkedValue={"2"} uncheckedValue={"1"} onChange={checked => {
|
||||
setStickMode2(checked);
|
||||
}} onReset={() => {
|
||||
ProfileLoadDefault({stickMode2: true});
|
||||
setStickMode2(getActiveProfile().stickMode2);
|
||||
}}/>}
|
||||
<div style={{
|
||||
display: "flex",
|
||||
}}>
|
||||
{<SelectSpan name="Format" value={videoFormat} optiones={VideoFormatOptions} onChange={ e => {
|
||||
setVideoFormat(e.target.value as unknown as VideoFormat);
|
||||
}} onReset={() => {
|
||||
ProfileLoadDefault({videoFormat: true});
|
||||
setVideoFormat(getActiveProfile().videoFormat);
|
||||
}}/>}
|
||||
{videoFormat === VideoFormat.mov? <VideoFormatWarning videoFormat={videoFormat}/> : null}
|
||||
{videoFormat === VideoFormat.mp4? <VideoFormatWarning videoFormat={videoFormat}/> : null}
|
||||
{videoFormat === VideoFormat.avi? <VideoFormatWarning videoFormat={videoFormat}/> : null}
|
||||
</span>
|
||||
{videoFormat === VideoFormat.mkv? <VideoFormatWarning videoFormat={videoFormat}/> : null}
|
||||
</div>
|
||||
<button id="resetSettingsButton" onClick={() => {
|
||||
settingListLoadDefault();
|
||||
ProfileLoadDefault({all: true});
|
||||
|
||||
setFps(settingList.fps);
|
||||
setWidth(settingList.width);
|
||||
setStickDistance(settingList.stickDistance);
|
||||
setStickMode2(settingList.stickMode2);
|
||||
setVideoFormat(settingList.videoFormat);
|
||||
}}>Reset Settings</button>
|
||||
setFps(getActiveProfile().fps);
|
||||
setWidth(getActiveProfile().width);
|
||||
setStickDistance(getActiveProfile().stickDistance);
|
||||
setStickMode2(getActiveProfile().stickMode2);
|
||||
setVideoFormat(getActiveProfile().videoFormat);
|
||||
}}>Reset Profile</button>
|
||||
</div>
|
||||
<div id="renderImgDiv">
|
||||
<img id="render-ex" src={renderImg}></img>
|
||||
|
||||
108
src/index.css
108
src/index.css
@@ -1,3 +1,12 @@
|
||||
body {
|
||||
background-color: #172336;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
color: white;
|
||||
font-family: sans-serif;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.dataDiv {
|
||||
display: flex;
|
||||
}
|
||||
@@ -249,16 +258,18 @@ header h1 {
|
||||
}
|
||||
|
||||
#render-ex {
|
||||
width: 100%;
|
||||
width: auto;
|
||||
height: auto;
|
||||
max-width: 100%;
|
||||
display: block;
|
||||
border-radius: 10px;
|
||||
}
|
||||
#renderImgDiv {
|
||||
position: relative;
|
||||
margin-top: 15px;
|
||||
width: 100%;
|
||||
image-rendering: pixelated;
|
||||
width: fit-content;
|
||||
height: fit-content;
|
||||
}
|
||||
|
||||
.noMarginBottom {
|
||||
@@ -310,38 +321,64 @@ button:hover {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.inputSpan {
|
||||
.inputSelectSpan {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-bottom: 15px;
|
||||
height: 35px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.inputSpan input {
|
||||
.inputSelectSpan input {
|
||||
width: 75px;
|
||||
padding: 8px 0;
|
||||
height: 100%;
|
||||
border: 0;
|
||||
border-top-right-radius: 18px;
|
||||
border-bottom-right-radius: 18px;
|
||||
text-align: center;
|
||||
font-size: large;
|
||||
line-height: 0px;
|
||||
padding: 0;
|
||||
}
|
||||
.inputSpan label {
|
||||
padding: 10px 10px;
|
||||
.inputSelectSpan label {
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
height: 100%;
|
||||
background: #00c24a;
|
||||
border-top-left-radius: 18px;
|
||||
border-bottom-left-radius: 18px;
|
||||
font-weight: bolder;
|
||||
overflow: hidden;
|
||||
}
|
||||
.inputSelectSpan select {
|
||||
height: 100%;
|
||||
border: 0;
|
||||
border-top-right-radius: 18px;
|
||||
border-bottom-right-radius: 18px;
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
font-size: large;
|
||||
}
|
||||
|
||||
.overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0,0,0,0.8);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#settingRow {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#resetSettingsButton {
|
||||
height: 35px;
|
||||
background-color: #e1334e;
|
||||
}
|
||||
|
||||
#logList-Name:hover {
|
||||
@@ -410,22 +447,39 @@ button:hover {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.selectSpanSelect select {
|
||||
padding: 8px 0;
|
||||
border: 0;
|
||||
border-top-right-radius: 18px;
|
||||
border-bottom-right-radius: 18px;
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
font-size: large;
|
||||
}
|
||||
.selectSpanLabel {
|
||||
padding: 10px 10px;
|
||||
background: #00c24a;
|
||||
border-top-left-radius: 18px;
|
||||
border-bottom-left-radius: 18px;
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
#videoFormatWarning:hover {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
#outerTerminal {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 5px;
|
||||
width: 100%;
|
||||
height: 300px;
|
||||
background-color: #0d131e;
|
||||
color: white;
|
||||
font-family: monospace;
|
||||
font-size: 12px;
|
||||
border-radius: 10px;
|
||||
padding: 10px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
#outerTerminal p {
|
||||
margin: 2px;
|
||||
}
|
||||
|
||||
#innerTerminal {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
#terminalTable tr {
|
||||
display: flex;
|
||||
}
|
||||
.terminalTableNumber {
|
||||
color: #9DA8B9;
|
||||
background-color: #172336;
|
||||
margin-right: 5px;
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
<meta charset="UTF-8" />
|
||||
<title>StickExporterTX</title>
|
||||
</head>
|
||||
<body style="background-color: #172336;margin:0;padding:0;color:white;font-family:sans-serif;">
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
<script>
|
||||
|
||||
@@ -7,7 +7,7 @@ import RenderingPage from "./components/ui/renderingPage";
|
||||
import RenderFinishPage from "./components/ui/renderFinishPage";
|
||||
import "./index.css";
|
||||
import "./toggle-switchy.css";
|
||||
import { startBlender } from "./components/blenderController";
|
||||
import { blender, blenderCmd, startBlender } from "./components/blenderController";
|
||||
import {ipcRenderer} from "electron";
|
||||
|
||||
enum Page {
|
||||
@@ -40,6 +40,18 @@ function openPage(page:Page) {
|
||||
|
||||
openPage(currentPage);
|
||||
|
||||
window.addEventListener("keydown", (e:KeyboardEvent) => {
|
||||
if(e.key === "Escape") {
|
||||
if(currentPage === Page.Main) {
|
||||
ipcRenderer.send("closeApp");
|
||||
} else if(currentPage === Page.Rendering) {
|
||||
blender(blenderCmd.stopRendering);
|
||||
} else {
|
||||
openPage(Page.Main);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
startBlender();
|
||||
|
||||
function pageSetRendering(value:boolean) {
|
||||
|
||||
Reference in New Issue
Block a user