Compare commits
142 Commits
v0.6.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 | ||
|
fdfa9a1bb4
|
|||
|
19f13237da
|
|||
|
77fe9cfd6e
|
|||
|
|
39b36b59ce | ||
|
|
3421c632ee | ||
|
1a1d5cc03e
|
|||
|
2c89830bb7
|
|||
|
2c0e374ed2
|
|||
|
50897e005d
|
|||
|
3c70be134a
|
|||
|
f6d1ed0477
|
|||
|
f8c2d56c8f
|
|||
|
2f087eb7c6
|
|||
|
c4061822a2
|
|||
|
c009a098c7
|
|||
|
2809f2d4e8
|
|||
|
a213f06961
|
|||
|
7607d41652
|
|||
|
df414f1a47
|
|||
|
557f37aaa4
|
|||
|
1a0f0fda89
|
|||
|
a746e6aea6
|
|||
|
5d53da42f9
|
|||
|
aac54a6cf4
|
|||
|
28a778e0ca
|
|||
|
|
aa4e557561 | ||
|
|
274f622d6c | ||
|
3e98ff73f7
|
|||
|
1a22c55572
|
|||
|
3607233df6
|
|||
|
790f27d7d6
|
|||
|
7b67559589
|
|||
|
ade3cdd6fe
|
|||
|
9db98a12b4
|
|||
|
a639d8e50f
|
|||
|
0485b9c0aa
|
|||
|
a99da677c2
|
|||
|
7fc8c2efa2
|
|||
|
92df42bbee
|
|||
|
25cc105057
|
|||
|
bd14104f2f
|
|||
|
cd586ed0ec
|
|||
|
f1b35ed671
|
|||
|
7c082ee546
|
|||
|
5239cdb8f7
|
|||
|
fc97efade5
|
|||
|
243ec6aecf
|
|||
|
b6515aadf6
|
|||
|
e4b758bb11
|
|||
|
9d6daa069e
|
|||
|
62f414a9d3
|
|||
|
e3cfa63745
|
|||
|
4d84861b62
|
|||
|
c8c06c42bc
|
|||
|
fe0778fe71
|
|||
|
fc14114e02
|
|||
|
7c9ed18252
|
|||
|
999b90e54d
|
|||
|
7c58e244e5
|
|||
|
96223ba81b
|
|||
|
0aba2a0de0
|
|||
|
179d7f9d7b
|
|||
|
2b0bfb9429
|
|||
|
0e76ddb42d
|
|||
|
9628d2029c
|
|||
|
4546532e68
|
|||
|
3c57b44d8f
|
|||
|
f0c48eac9a
|
|||
|
6f6c84dc46
|
|||
|
7ea3b514ce
|
|||
|
|
3e1a54a49e | ||
|
e76f2d4b2a
|
|||
|
a730c36c13
|
|||
|
9eb63142c8
|
|||
|
30c4f6c81e
|
|||
|
41b6470f7a
|
|||
|
ce9b2adb22
|
|||
|
|
27db1432e7 | ||
|
b425eded3b
|
|||
|
429e4a28a3
|
|||
|
bf131e1f13
|
|||
|
|
195e153403 | ||
|
|
87290b8437 |
15
.babelrc
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"presets": [
|
||||||
|
"@babel/preset-env",
|
||||||
|
"@babel/preset-react",
|
||||||
|
"@babel/preset-typescript"
|
||||||
|
],
|
||||||
|
"plugins": [
|
||||||
|
[
|
||||||
|
"@babel/plugin-transform-runtime",
|
||||||
|
{
|
||||||
|
"regenerator": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
29
.eslintrc
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"parser": "@typescript-eslint/parser",
|
||||||
|
"parserOptions": {
|
||||||
|
"ecmaVersion": "latest",
|
||||||
|
"sourceType": "module"
|
||||||
|
},
|
||||||
|
"plugins": [
|
||||||
|
"@typescript-eslint",
|
||||||
|
"react-hooks",
|
||||||
|
"import"
|
||||||
|
],
|
||||||
|
"extends": [
|
||||||
|
"plugin:react/recommended",
|
||||||
|
"plugin:@typescript-eslint/recommended"
|
||||||
|
],
|
||||||
|
"rules": {
|
||||||
|
"react-hooks/rules-of-hooks": "error",
|
||||||
|
"react-hooks/exhaustive-deps": "warn",
|
||||||
|
"react/prop-types": "off",
|
||||||
|
"global-require": "off",
|
||||||
|
"import/no-dynamic-require": "off"
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"react": {
|
||||||
|
"pragma": "React",
|
||||||
|
"version": "detect"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
7
.gitignore
vendored
@@ -263,6 +263,9 @@ out/
|
|||||||
|
|
||||||
output/
|
output/
|
||||||
|
|
||||||
blender/
|
dependencies/windows/
|
||||||
|
dependencies/darwin/
|
||||||
|
dependencies/linux/
|
||||||
|
|
||||||
release-builds/
|
index.build.js
|
||||||
|
index.build.js.LICENSE.txt
|
||||||
6
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"python.analysis.extraPaths": [
|
||||||
|
"./dependencies/windows/blender/3.2/scripts/modules",
|
||||||
|
"./dependencies/linux/blender/3.2/scripts/modules"
|
||||||
|
]
|
||||||
|
}
|
||||||
128
2D.py
@@ -1,128 +0,0 @@
|
|||||||
import csv
|
|
||||||
from tkinter.filedialog import askopenfilename
|
|
||||||
import pygame
|
|
||||||
import time
|
|
||||||
|
|
||||||
Pixelsize = 10
|
|
||||||
Size = 500
|
|
||||||
|
|
||||||
Height = Size/2
|
|
||||||
Width = Size
|
|
||||||
HeightMiddle = Height/2
|
|
||||||
WidthMiddle = Width/2
|
|
||||||
joystickScaleHeight = Height/2048
|
|
||||||
joystickScaleWidth = Width/2048
|
|
||||||
|
|
||||||
screen = pygame.display.set_mode((Width,Height+1))
|
|
||||||
pygame.display.set_caption('Log Test')
|
|
||||||
pygame.draw.rect(screen, (0,0,255), (WidthMiddle, 0, 1, Height))
|
|
||||||
pygame.display.flip()
|
|
||||||
|
|
||||||
log = askopenfilename()
|
|
||||||
|
|
||||||
ltime = []
|
|
||||||
|
|
||||||
rud = []
|
|
||||||
ele = []
|
|
||||||
thr = []
|
|
||||||
ail = []
|
|
||||||
|
|
||||||
try:
|
|
||||||
with open(log, newline='') as csvfile:
|
|
||||||
reader = csv.DictReader(csvfile)
|
|
||||||
for row in reader:
|
|
||||||
ltime.append(row['Time'])
|
|
||||||
rud.append(row['Rud'])
|
|
||||||
ele.append(row['Ele'])
|
|
||||||
thr.append(row['Thr'])
|
|
||||||
ail.append(row['Ail'])
|
|
||||||
except:
|
|
||||||
print("Can't read Log File!")
|
|
||||||
exit()
|
|
||||||
|
|
||||||
i = 0
|
|
||||||
while i != len(rud):
|
|
||||||
iRud = int(rud[i])
|
|
||||||
iEle = int(ele[i])
|
|
||||||
iThr = int(thr[i])
|
|
||||||
iAil = int(ail[i])
|
|
||||||
|
|
||||||
if iRud >= 0:
|
|
||||||
rud[i] = (iRud + 1024)*(joystickScaleWidth/2)
|
|
||||||
else:
|
|
||||||
rud[i] = (1024 - abs(iRud))*(joystickScaleWidth/2)
|
|
||||||
|
|
||||||
if iEle >= 0:
|
|
||||||
ele[i] = (iEle + 1024)*joystickScaleHeight
|
|
||||||
else:
|
|
||||||
ele[i] = (1024 - abs(iEle))*joystickScaleHeight
|
|
||||||
|
|
||||||
if iThr >= 0:
|
|
||||||
thr[i] = (iThr + 1024)*joystickScaleHeight
|
|
||||||
else:
|
|
||||||
thr[i] = (1024 - abs(iThr))*joystickScaleHeight
|
|
||||||
|
|
||||||
if iAil >= 0:
|
|
||||||
ail[i] = (iAil + 1024)*(joystickScaleWidth/2)
|
|
||||||
else:
|
|
||||||
ail[i] = (1024 - abs(iAil))*(joystickScaleWidth/2)
|
|
||||||
|
|
||||||
i+=1
|
|
||||||
|
|
||||||
splitTime = []
|
|
||||||
for e in ltime:
|
|
||||||
splitTime.append(e.split(":").pop(2).replace(".", ""))
|
|
||||||
|
|
||||||
meanTime = []
|
|
||||||
i = 0
|
|
||||||
f = False
|
|
||||||
for e in splitTime:
|
|
||||||
if f != False:
|
|
||||||
if int(e) < int(splitTime[i]):
|
|
||||||
meanTime.append(60000 - int(splitTime[i]) + int(e))
|
|
||||||
else:
|
|
||||||
meanTime.append(int(e) - int(splitTime[i]))
|
|
||||||
|
|
||||||
i+=1
|
|
||||||
else:
|
|
||||||
f = True
|
|
||||||
|
|
||||||
totalTime = 0
|
|
||||||
for e in meanTime:
|
|
||||||
totalTime+=e
|
|
||||||
|
|
||||||
timelineStep = Width/totalTime
|
|
||||||
|
|
||||||
while True:
|
|
||||||
i = 0
|
|
||||||
lastTime = 0
|
|
||||||
while i < len(meanTime):
|
|
||||||
if(i == 0):
|
|
||||||
pygame.draw.circle(screen, (255,0,0), (WidthMiddle+ail[0], Height-ele[0]), Pixelsize)
|
|
||||||
pygame.draw.circle(screen, (255,0,0), (rud[0], Height-thr[0]), Pixelsize)
|
|
||||||
pygame.display.flip()
|
|
||||||
startTime = int(time.time()*1000)
|
|
||||||
i+=1
|
|
||||||
else:
|
|
||||||
while(startTime+lastTime+meanTime[i-1] >= int(time.time()*1000)):
|
|
||||||
multiplier = (int(time.time()*1000)-(startTime+lastTime))/meanTime[i-1]
|
|
||||||
|
|
||||||
ailP = (WidthMiddle+ail[i-1])+((WidthMiddle+ail[i])-(WidthMiddle+ail[i-1]))*multiplier
|
|
||||||
eleP = (Height-ele[i-1])+((Height-ele[i])-(Height-ele[i-1]))*multiplier
|
|
||||||
rudP = (rud[i-1])+((rud[i])-(rud[i-1]))*multiplier
|
|
||||||
thrP = (Height-thr[i-1])+((Height-thr[i])-(Height-thr[i-1]))*multiplier
|
|
||||||
|
|
||||||
pygame.draw.rect(screen, (0,0,0), (0, 0, Width, Height+1))
|
|
||||||
pygame.draw.rect(screen, (0,0,255), (WidthMiddle, 0, 1, Height))
|
|
||||||
pygame.draw.circle(screen, (255,0,0), (ailP, eleP), Pixelsize)
|
|
||||||
pygame.draw.circle(screen, (255,0,0), (rudP, thrP), Pixelsize)
|
|
||||||
pygame.draw.rect(screen, (0,255,0), (0, Height, (int(time.time()*1000)-startTime)*timelineStep, 1))
|
|
||||||
pygame.display.flip()
|
|
||||||
i+=1
|
|
||||||
if i < len(meanTime):
|
|
||||||
lastTime += meanTime[i]
|
|
||||||
|
|
||||||
for e in pygame.event.get():
|
|
||||||
if e.type == pygame.QUIT:
|
|
||||||
pygame.quit()
|
|
||||||
exit()
|
|
||||||
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2022 Lino Schmidt
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
48
README.md
@@ -1,21 +1,41 @@
|
|||||||
# StickExporterTX
|
# StickExporterTX
|
||||||
|
|
||||||
A 3D Sticks Exporter for EdgeTX/OpenTX logs.
|
StickExporterTX is a 3D Stick Exporter for EdgeTX/OpenTX logs.
|
||||||
|
|
||||||
Setup:
|
## Controller Setup Guide
|
||||||
------
|
|
||||||
- Download [Blender (Portable)](https://www.blender.org/download) and unpack it with the name `blender` in the folder `<Project folder>/assets`.
|
|
||||||
- Start a console in the project folder and execute the command `npm install` to install the necessary packages.
|
|
||||||
|
|
||||||
Test:
|
To log your sticks on each model, go to `settings -> global functions` in your RC controller.
|
||||||
------
|
|
||||||
To test the program, execute the command `npm start` in the project folder.
|
|
||||||
|
|
||||||
Build (Windows):
|

|
||||||
------
|
|
||||||
To build the program, execute the command `npm run package-win` in the project folder.
|
|
||||||
After that, open the file `installer-builder.iss` with the program [Inno Setup Compiler](https://jrsoftware.org/isdl.php#stable) and compile it.
|
|
||||||
The finished installer should be inside `<Project Folder>/output`.
|
|
||||||
|
|
||||||
|
There you can select and edit one of the empty fields.
|
||||||
|
With `Switch` you select the one that should activate logging. For example, you can set your drone's arm switch to log it as long as the drone is armed.
|
||||||
|
`Func` must be set to `SD Logs` and `Value` should be as low as possible to get a smoother animation.
|
||||||
|
|
||||||
Copyright © 2022 Lino Schmidt. All rights reserved.
|

|
||||||
|
|
||||||
|
If you only want to set up logging for one model, go to `model -> special functions` in your RC controller and repeat the previous step.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## 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.
|
||||||
@@ -1,165 +0,0 @@
|
|||||||
import csv
|
|
||||||
from importlib.resources import path
|
|
||||||
import logging
|
|
||||||
import math
|
|
||||||
import sys
|
|
||||||
import bpy
|
|
||||||
import xml.etree.ElementTree as ET
|
|
||||||
|
|
||||||
settings = ET.parse("")
|
|
||||||
|
|
||||||
logger = logging.getLogger('simple_example')
|
|
||||||
logger.setLevel(logging.INFO)
|
|
||||||
formatter = logging.Formatter('%(message)s')
|
|
||||||
console_handler = logging.StreamHandler(sys.stdout)
|
|
||||||
console_handler.setFormatter(formatter)
|
|
||||||
console_handler.setLevel(logging.INFO)
|
|
||||||
logger.addHandler(console_handler)
|
|
||||||
|
|
||||||
def _map(x, in_min, in_max, out_min, out_max):
|
|
||||||
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min
|
|
||||||
|
|
||||||
settingsRoot = settings.getroot()
|
|
||||||
|
|
||||||
FPS = int(settingsRoot[0].text)
|
|
||||||
Width = int(settingsRoot[1].text)
|
|
||||||
StickDistance = _map(int(settingsRoot[2].text), 0, 100, 5, 105)
|
|
||||||
StickMode = settingsRoot[3].text
|
|
||||||
if(StickMode == "true"):
|
|
||||||
StickMode = 2
|
|
||||||
else:
|
|
||||||
StickMode = 1
|
|
||||||
|
|
||||||
lyMax = 0.436
|
|
||||||
lyMin = -0.436
|
|
||||||
lxMax = -0.436
|
|
||||||
lxMin = 0.436
|
|
||||||
ryMax = -0.436
|
|
||||||
ryMin = 0.436
|
|
||||||
rxMax = 0.436
|
|
||||||
rxMin = -0.436
|
|
||||||
|
|
||||||
logs = settingsRoot[4].text[1:][:-1].split("\"\"")
|
|
||||||
|
|
||||||
logCount = len(logs)
|
|
||||||
logNumber = 1
|
|
||||||
|
|
||||||
for log in logs:
|
|
||||||
logger.info("Lognr:" + ((str)(logNumber)) + ":")
|
|
||||||
|
|
||||||
logTime = []
|
|
||||||
rud = []
|
|
||||||
ele = []
|
|
||||||
thr = []
|
|
||||||
ail = []
|
|
||||||
|
|
||||||
try:
|
|
||||||
with open(log, newline='') as csvFile:
|
|
||||||
reader = csv.DictReader(csvFile)
|
|
||||||
for row in reader:
|
|
||||||
logTime.append(row['Time'].split(":").pop(2).replace(".", ""))
|
|
||||||
rud.append(int(row['Rud']))
|
|
||||||
ele.append(int(row['Ele']))
|
|
||||||
thr.append(int(row['Thr']))
|
|
||||||
ail.append(int(row['Ail']))
|
|
||||||
|
|
||||||
meanTime = []
|
|
||||||
i = 0
|
|
||||||
while i < len(logTime)-1:
|
|
||||||
if int(logTime[i]) > int(logTime[i+1]):
|
|
||||||
meanTime.append(60000 - int(logTime[i]) + int(logTime[i+1]))
|
|
||||||
else:
|
|
||||||
meanTime.append(int(logTime[i+1]) - int(logTime[i]))
|
|
||||||
i+=1
|
|
||||||
|
|
||||||
totalTime = 0
|
|
||||||
for e in meanTime:
|
|
||||||
totalTime+=e
|
|
||||||
|
|
||||||
frameCount = math.floor(totalTime/1000*FPS-1)
|
|
||||||
FPSxxx = 1000/FPS
|
|
||||||
except Exception as e:
|
|
||||||
print("Can't read Log!")
|
|
||||||
exit()
|
|
||||||
|
|
||||||
GimbalL = bpy.data.objects["GimbalL"]
|
|
||||||
StickL = bpy.data.objects["StickL"]
|
|
||||||
GimbalR = bpy.data.objects["GimbalR"]
|
|
||||||
StickR = bpy.data.objects["StickR"]
|
|
||||||
GimbalCoverR = bpy.data.objects["GimbalCoverR"]
|
|
||||||
TrailR = bpy.data.objects["TrailR"]
|
|
||||||
Camera = bpy.data.objects["Camera"]
|
|
||||||
Plane = bpy.data.objects["Plane.001"]
|
|
||||||
scn = bpy.context.scene
|
|
||||||
|
|
||||||
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 = settingsRoot[5].text + "\\" + log.split("/")[-1].split("\\")[-1].replace(".csv", ".mov")
|
|
||||||
|
|
||||||
scn.render.fps = 1000
|
|
||||||
scn.render.fps_base = FPSxxx
|
|
||||||
|
|
||||||
scn.frame_start = 0
|
|
||||||
scn.frame_end = frameCount+1
|
|
||||||
logger.info("Frames:" + str(frameCount+1) + ":")
|
|
||||||
|
|
||||||
frame = 0
|
|
||||||
log = 0
|
|
||||||
pastTime = 0
|
|
||||||
while frame <= frameCount:
|
|
||||||
currentTime = math.floor(FPSxxx*frame)
|
|
||||||
while currentTime >= pastTime+meanTime[log]:
|
|
||||||
pastTime+=meanTime[log]
|
|
||||||
log+=1
|
|
||||||
|
|
||||||
multiplier = (currentTime-pastTime)/meanTime[log]
|
|
||||||
|
|
||||||
ailP = _map(ail[log]+(ail[log+1]-ail[log])*multiplier, -1024, 1024, rxMin, rxMax)
|
|
||||||
eleP = _map(ele[log]+(ele[log+1]-ele[log])*multiplier, -1024, 1024, ryMin, ryMax)
|
|
||||||
rudP = _map(rud[log]+(rud[log+1]-rud[log])*multiplier, -1024, 1024, lyMin, lyMax)
|
|
||||||
thrP = _map(thr[log]+(thr[log+1]-thr[log])*multiplier, -1024, 1024, lxMin, lxMax)
|
|
||||||
|
|
||||||
bpy.context.scene.frame_set(frame)
|
|
||||||
|
|
||||||
if StickMode == "1":
|
|
||||||
StickL.rotation_euler=[0,0,0]
|
|
||||||
StickL.rotation_euler.rotate_axis("Y", ailP)
|
|
||||||
StickL.keyframe_insert(data_path="rotation_euler", index=-1)
|
|
||||||
GimbalL.rotation_euler=[0,0,0]
|
|
||||||
GimbalL.rotation_euler.rotate_axis("X", eleP)
|
|
||||||
GimbalL.keyframe_insert(data_path="rotation_euler", index=-1)
|
|
||||||
StickR.rotation_euler=[0,0,0]
|
|
||||||
StickR.rotation_euler.rotate_axis("Y", rudP)
|
|
||||||
StickR.keyframe_insert(data_path="rotation_euler", index=-1)
|
|
||||||
GimbalR.rotation_euler=[0,0,0]
|
|
||||||
GimbalR.rotation_euler.rotate_axis("X", thrP)
|
|
||||||
GimbalR.keyframe_insert(data_path="rotation_euler", index=-1)
|
|
||||||
else:
|
|
||||||
StickL.rotation_euler=[0,0,0]
|
|
||||||
StickL.rotation_euler.rotate_axis("Y", rudP)
|
|
||||||
StickL.keyframe_insert(data_path="rotation_euler", index=-1)
|
|
||||||
GimbalL.rotation_euler=[0,0,0]
|
|
||||||
GimbalL.rotation_euler.rotate_axis("X", thrP)
|
|
||||||
GimbalL.keyframe_insert(data_path="rotation_euler", index=-1)
|
|
||||||
StickR.rotation_euler=[0,0,0]
|
|
||||||
StickR.rotation_euler.rotate_axis("Y", ailP)
|
|
||||||
StickR.keyframe_insert(data_path="rotation_euler", index=-1)
|
|
||||||
GimbalR.rotation_euler=[0,0,0]
|
|
||||||
GimbalR.rotation_euler.rotate_axis("X", eleP)
|
|
||||||
GimbalR.keyframe_insert(data_path="rotation_euler", index=-1)
|
|
||||||
|
|
||||||
logger.info("Init:" + ((str)(frame)) + ":")
|
|
||||||
frame+=1
|
|
||||||
|
|
||||||
bpy.ops.render.render(animation=True)
|
|
||||||
|
|
||||||
if(logCount <= logNumber):
|
|
||||||
logger.info("Finished")
|
|
||||||
|
|
||||||
logNumber+=1
|
|
||||||
BIN
assets/icon.icns
Normal file
BIN
assets/icon.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
assets/icons/128x128.png
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
BIN
assets/icons/16x16.png
Normal file
|
After Width: | Height: | Size: 995 B |
BIN
assets/icons/24x24.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
assets/icons/256x256.png
Normal file
|
After Width: | Height: | Size: 9.6 KiB |
BIN
assets/icons/32x32.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
assets/icons/48x48.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
assets/icons/512x512.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
assets/icons/64x64.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
assets/icons/96x96.png
Normal file
|
After Width: | Height: | Size: 4.4 KiB |
BIN
assets/render_finished_icon.png
Normal file
|
After Width: | Height: | Size: 58 KiB |
50
configs/webpack.base.config.js
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import path from 'path';
|
||||||
|
import ESLintPlugin from "eslint-webpack-plugin";
|
||||||
|
import ForkTsCheckerWebpackPlugin from 'fork-ts-checker-webpack-plugin';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
test: /\.(ts|js)x?$/,
|
||||||
|
exclude: /node_modules/,
|
||||||
|
use: {
|
||||||
|
loader: "babel-loader",
|
||||||
|
options: {
|
||||||
|
presets: [
|
||||||
|
"@babel/preset-env",
|
||||||
|
"@babel/preset-react",
|
||||||
|
"@babel/preset-typescript",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.css$/i,
|
||||||
|
use: ["style-loader", "css-loader"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
path: path.join(__dirname, '../src/.webpack'),
|
||||||
|
libraryTarget: 'commonjs2',
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
extensions: ['.js', '.ts', '.jsx', '.tsx', '.json'],
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
new ForkTsCheckerWebpackPlugin({
|
||||||
|
async: false
|
||||||
|
}),
|
||||||
|
new ESLintPlugin({
|
||||||
|
extensions: ["js", "jsx", "ts", "tsx"],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
node: {
|
||||||
|
__dirname: false,
|
||||||
|
__filename: false,
|
||||||
|
},
|
||||||
|
experiments: {
|
||||||
|
topLevelAwait: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
11
configs/webpack.main.dev.config.babel.js
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import baseConfig from "./webpack.base.config.js";
|
||||||
|
import {merge} from 'webpack-merge';
|
||||||
|
|
||||||
|
export default merge(baseConfig, {
|
||||||
|
mode: 'development',
|
||||||
|
target: 'electron-main',
|
||||||
|
entry: './src/index.ts',
|
||||||
|
output: {
|
||||||
|
filename: '../index.build.js'
|
||||||
|
},
|
||||||
|
});
|
||||||
11
configs/webpack.main.prod.config.babel.js
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import baseConfig from "./webpack.base.config.js";
|
||||||
|
import {merge} from 'webpack-merge';
|
||||||
|
|
||||||
|
export default merge(baseConfig, {
|
||||||
|
mode: 'production',
|
||||||
|
target: 'electron-main',
|
||||||
|
entry: './src/index.ts',
|
||||||
|
output: {
|
||||||
|
filename: '../index.build.js'
|
||||||
|
},
|
||||||
|
});
|
||||||
31
configs/webpack.renderer.dev.config.babel.js
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import baseConfig from "./webpack.base.config.js";
|
||||||
|
import {merge} from 'webpack-merge';
|
||||||
|
import { spawn } from 'child_process';
|
||||||
|
|
||||||
|
const host = process.env.HOST || 'localhost';
|
||||||
|
const port = process.env.PORT || 3000;
|
||||||
|
|
||||||
|
export default merge(baseConfig, {
|
||||||
|
mode: 'development',
|
||||||
|
target: 'electron-renderer',
|
||||||
|
entry: './src/renderer.tsx',
|
||||||
|
output: {
|
||||||
|
filename: 'renderer.js',
|
||||||
|
},
|
||||||
|
devServer: {
|
||||||
|
compress: true,
|
||||||
|
hot: true,
|
||||||
|
host,
|
||||||
|
port,
|
||||||
|
onBeforeSetupMiddleware() {
|
||||||
|
console.log('Starting Main Process...');
|
||||||
|
spawn('npm', ['run', 'start:main'], {
|
||||||
|
shell: true,
|
||||||
|
env: process.env,
|
||||||
|
stdio: 'inherit',
|
||||||
|
})
|
||||||
|
.on('close', (code) => process.exit(code))
|
||||||
|
.on('error', (spawnError) => console.error(spawnError));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
11
configs/webpack.renderer.prod.config.babel.js
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import baseConfig from "./webpack.base.config.js";
|
||||||
|
import {merge} from 'webpack-merge';
|
||||||
|
|
||||||
|
export default merge(baseConfig, {
|
||||||
|
mode: 'production',
|
||||||
|
target: 'electron-renderer',
|
||||||
|
entry: './src/renderer.tsx',
|
||||||
|
output: {
|
||||||
|
filename: 'renderer.js'
|
||||||
|
},
|
||||||
|
});
|
||||||
250
dependencies/blenderScript.py
vendored
Normal file
@@ -0,0 +1,250 @@
|
|||||||
|
from ast import Str
|
||||||
|
import csv
|
||||||
|
import logging
|
||||||
|
import math
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import bpy
|
||||||
|
import json
|
||||||
|
|
||||||
|
logger = logging.getLogger('simple_example')
|
||||||
|
logger.setLevel(logging.INFO)
|
||||||
|
formatter = logging.Formatter('%(message)s')
|
||||||
|
console_handler = logging.StreamHandler(sys.stdout)
|
||||||
|
console_handler.setFormatter(formatter)
|
||||||
|
console_handler.setLevel(logging.INFO)
|
||||||
|
logger.addHandler(console_handler)
|
||||||
|
|
||||||
|
GimbalL = bpy.data.objects["GimbalL"]
|
||||||
|
StickL = bpy.data.objects["StickL"]
|
||||||
|
GimbalR = bpy.data.objects["GimbalR"]
|
||||||
|
StickR = bpy.data.objects["StickR"]
|
||||||
|
GimbalCoverR = bpy.data.objects["GimbalCoverR"]
|
||||||
|
TrailR = bpy.data.objects["TrailR"]
|
||||||
|
Camera = bpy.data.objects["Camera"]
|
||||||
|
Plane = bpy.data.objects["Plane.001"]
|
||||||
|
scn = bpy.context.scene
|
||||||
|
|
||||||
|
lyMax = 0.436
|
||||||
|
lyMin = -0.436
|
||||||
|
lxMax = -0.436
|
||||||
|
lxMin = 0.436
|
||||||
|
ryMax = -0.436
|
||||||
|
ryMin = 0.436
|
||||||
|
rxMax = 0.436
|
||||||
|
rxMin = -0.436
|
||||||
|
|
||||||
|
def _map(x, in_min, in_max, out_min, out_max):
|
||||||
|
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min
|
||||||
|
|
||||||
|
logger.info("Blender started successfully!")
|
||||||
|
|
||||||
|
while True:
|
||||||
|
command = input("Waiting for command: ").split(" -- ")
|
||||||
|
|
||||||
|
time.sleep(0.5)
|
||||||
|
|
||||||
|
settings = json.loads(command[1])
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
for log in logs:
|
||||||
|
logger.info("Lognr:" + ((str)(logNumber)) + ":")
|
||||||
|
|
||||||
|
logTime = []
|
||||||
|
rud = []
|
||||||
|
ele = []
|
||||||
|
thr = []
|
||||||
|
ail = []
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(log, newline='') as csvFile:
|
||||||
|
reader = csv.DictReader(csvFile)
|
||||||
|
for row in reader:
|
||||||
|
logTime.append(row['Time'].split(":").pop(2).replace(".", ""))
|
||||||
|
rud.append(int(row['Rud']))
|
||||||
|
ele.append(int(row['Ele']))
|
||||||
|
thr.append(int(row['Thr']))
|
||||||
|
ail.append(int(row['Ail']))
|
||||||
|
|
||||||
|
meanTime = []
|
||||||
|
i = 0
|
||||||
|
while i < len(logTime)-1:
|
||||||
|
if int(logTime[i]) > int(logTime[i+1]):
|
||||||
|
meanTime.append(60000 - int(logTime[i]) + int(logTime[i+1]))
|
||||||
|
else:
|
||||||
|
meanTime.append(int(logTime[i+1]) - int(logTime[i]))
|
||||||
|
i+=1
|
||||||
|
|
||||||
|
totalTime = 0
|
||||||
|
for e in meanTime:
|
||||||
|
totalTime+=e
|
||||||
|
|
||||||
|
frameCount = math.floor(totalTime/1000*fps-1)
|
||||||
|
FPSxxx = 1000/fps
|
||||||
|
except Exception as e:
|
||||||
|
logger.error("Can't read Log: " + (Str)(e))
|
||||||
|
|
||||||
|
bpy.context.scene.render.image_settings.file_format = 'FFMPEG'
|
||||||
|
|
||||||
|
if(videoFormat == "mp4"):
|
||||||
|
bpy.context.scene.render.ffmpeg.format = 'MPEG4'
|
||||||
|
bpy.context.scene.render.ffmpeg.codec = 'H264'
|
||||||
|
bpy.context.scene.render.image_settings.color_mode = 'RGB'
|
||||||
|
elif(videoFormat == "mov"):
|
||||||
|
bpy.context.scene.render.ffmpeg.format = 'QUICKTIME'
|
||||||
|
bpy.context.scene.render.ffmpeg.codec = 'QTRLE'
|
||||||
|
bpy.context.scene.render.image_settings.color_mode = 'RGBA'
|
||||||
|
elif(videoFormat == "avi"):
|
||||||
|
bpy.context.scene.render.ffmpeg.format = 'AVI'
|
||||||
|
bpy.context.scene.render.ffmpeg.codec = 'FFV1'
|
||||||
|
bpy.context.scene.render.image_settings.color_mode = 'RGBA'
|
||||||
|
elif(videoFormat == "webm"):
|
||||||
|
bpy.context.scene.render.ffmpeg.format = 'WEBM'
|
||||||
|
bpy.context.scene.render.ffmpeg.codec = 'WEBM'
|
||||||
|
bpy.context.scene.render.image_settings.color_mode = 'RGBA'
|
||||||
|
elif(videoFormat == "mkv"):
|
||||||
|
bpy.context.scene.render.ffmpeg.format = 'MKV'
|
||||||
|
bpy.context.scene.render.ffmpeg.codec = 'WEBM'
|
||||||
|
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[logNumber-1]
|
||||||
|
|
||||||
|
scn.render.fps = 1000
|
||||||
|
scn.render.fps_base = FPSxxx
|
||||||
|
|
||||||
|
scn.frame_start = 0
|
||||||
|
scn.frame_end = frameCount
|
||||||
|
logger.info("Frames:" + str(frameCount) + ":")
|
||||||
|
|
||||||
|
frame = 0
|
||||||
|
log = 0
|
||||||
|
pastTime = 0
|
||||||
|
while frame <= frameCount:
|
||||||
|
currentTime = math.floor(FPSxxx*frame)
|
||||||
|
while currentTime >= pastTime+meanTime[log]:
|
||||||
|
pastTime+=meanTime[log]
|
||||||
|
log+=1
|
||||||
|
|
||||||
|
multiplier = (currentTime-pastTime)/meanTime[log]
|
||||||
|
|
||||||
|
ailP = _map(ail[log]+(ail[log+1]-ail[log])*multiplier, -1024, 1024, rxMin, rxMax)
|
||||||
|
eleP = _map(ele[log]+(ele[log+1]-ele[log])*multiplier, -1024, 1024, ryMin, ryMax)
|
||||||
|
rudP = _map(rud[log]+(rud[log+1]-rud[log])*multiplier, -1024, 1024, lyMin, lyMax)
|
||||||
|
thrP = _map(thr[log]+(thr[log+1]-thr[log])*multiplier, -1024, 1024, lxMin, lxMax)
|
||||||
|
|
||||||
|
bpy.context.scene.frame_set(frame)
|
||||||
|
|
||||||
|
StickL.rotation_euler=[0,0,0]
|
||||||
|
GimbalL.rotation_euler=[0,0,0]
|
||||||
|
StickR.rotation_euler=[0,0,0]
|
||||||
|
GimbalR.rotation_euler=[0,0,0]
|
||||||
|
|
||||||
|
if stickMode2 == False:
|
||||||
|
StickL.rotation_euler.rotate_axis("Y", ailP)
|
||||||
|
GimbalL.rotation_euler.rotate_axis("X", eleP)
|
||||||
|
StickR.rotation_euler.rotate_axis("Y", rudP)
|
||||||
|
GimbalR.rotation_euler.rotate_axis("X", thrP)
|
||||||
|
else:
|
||||||
|
StickL.rotation_euler.rotate_axis("Y", rudP)
|
||||||
|
GimbalL.rotation_euler.rotate_axis("X", thrP)
|
||||||
|
StickR.rotation_euler.rotate_axis("Y", ailP)
|
||||||
|
GimbalR.rotation_euler.rotate_axis("X", eleP)
|
||||||
|
|
||||||
|
StickL.keyframe_insert(data_path="rotation_euler", index=-1)
|
||||||
|
GimbalL.keyframe_insert(data_path="rotation_euler", index=-1)
|
||||||
|
StickR.keyframe_insert(data_path="rotation_euler", index=-1)
|
||||||
|
GimbalR.keyframe_insert(data_path="rotation_euler", index=-1)
|
||||||
|
|
||||||
|
logger.info("Init:" + ((str)(frame)) + ":")
|
||||||
|
frame+=1
|
||||||
|
|
||||||
|
bpy.ops.render.render(animation=True)
|
||||||
|
|
||||||
|
if(logCount <= logNumber):
|
||||||
|
logger.info("Finished")
|
||||||
|
|
||||||
|
logNumber+=1
|
||||||
|
|
||||||
|
elif(command[0] == "getRender"):
|
||||||
|
|
||||||
|
bpy.context.scene.render.image_settings.file_format = '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))
|
||||||
|
|
||||||
|
bpy.context.scene.frame_set(0)
|
||||||
|
|
||||||
|
StickL.rotation_euler=[0,0,0]
|
||||||
|
GimbalL.rotation_euler=[0,0,0]
|
||||||
|
StickR.rotation_euler=[0,0,0]
|
||||||
|
GimbalR.rotation_euler=[0,0,0]
|
||||||
|
|
||||||
|
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)
|
||||||
|
GimbalR.rotation_euler.rotate_axis("X", 0)
|
||||||
|
else:
|
||||||
|
StickL.rotation_euler.rotate_axis("Y", 0)
|
||||||
|
GimbalL.rotation_euler.rotate_axis("X", 0)
|
||||||
|
StickR.rotation_euler.rotate_axis("Y", 0)
|
||||||
|
GimbalR.rotation_euler.rotate_axis("X", 0.436)
|
||||||
|
|
||||||
|
StickL.keyframe_insert(data_path="rotation_euler", index=-1)
|
||||||
|
GimbalL.keyframe_insert(data_path="rotation_euler", index=-1)
|
||||||
|
StickR.keyframe_insert(data_path="rotation_euler", index=-1)
|
||||||
|
GimbalR.keyframe_insert(data_path="rotation_euler", index=-1)
|
||||||
|
|
||||||
|
bpy.context.scene.frame_set(1)
|
||||||
|
|
||||||
|
StickL.rotation_euler=[0,0,0]
|
||||||
|
GimbalL.rotation_euler=[0,0,0]
|
||||||
|
StickR.rotation_euler=[0,0,0]
|
||||||
|
GimbalR.rotation_euler=[0,0,0]
|
||||||
|
|
||||||
|
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)
|
||||||
|
GimbalR.rotation_euler.rotate_axis("X", 0)
|
||||||
|
else:
|
||||||
|
StickL.rotation_euler.rotate_axis("Y", 0)
|
||||||
|
GimbalL.rotation_euler.rotate_axis("X", 0)
|
||||||
|
StickR.rotation_euler.rotate_axis("Y", 0)
|
||||||
|
GimbalR.rotation_euler.rotate_axis("X", 0.436)
|
||||||
|
|
||||||
|
StickL.keyframe_insert(data_path="rotation_euler", index=-1)
|
||||||
|
GimbalL.keyframe_insert(data_path="rotation_euler", index=-1)
|
||||||
|
StickR.keyframe_insert(data_path="rotation_euler", index=-1)
|
||||||
|
GimbalR.keyframe_insert(data_path="rotation_euler", index=-1)
|
||||||
|
|
||||||
|
bpy.context.scene.frame_set(0)
|
||||||
|
|
||||||
|
bpy.ops.render.render(write_still=True)
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
; Script generated by the Inno Setup Script Wizard.
|
|
||||||
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
|
|
||||||
|
|
||||||
#define MyAppName "StickExporterTX"
|
|
||||||
#define MyAppVersion "0.6.1"
|
|
||||||
#define MyAppPublisher "Lino Schmidt"
|
|
||||||
#define MyAppURL "https://stickexportertx.lino3d.de"
|
|
||||||
#define MyAppExeName "stickexportertx.exe"
|
|
||||||
|
|
||||||
[Setup]
|
|
||||||
; NOTE: The value of AppId uniquely identifies this application. Do not use the same AppId value in installers for other applications.
|
|
||||||
; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
|
|
||||||
AppId={{11FB4A05-6479-45DE-8B5C-436F0E63B2D3}
|
|
||||||
AppName={#MyAppName}
|
|
||||||
AppVersion={#MyAppVersion}
|
|
||||||
;AppVerName={#MyAppName} {#MyAppVersion}
|
|
||||||
AppPublisher={#MyAppPublisher}
|
|
||||||
AppPublisherURL={#MyAppURL}
|
|
||||||
AppSupportURL={#MyAppURL}
|
|
||||||
AppUpdatesURL={#MyAppURL}
|
|
||||||
DefaultDirName={autopf}\{#MyAppName}
|
|
||||||
DisableProgramGroupPage=yes
|
|
||||||
; Uncomment the following line to run in non administrative install mode (install for current user only.)
|
|
||||||
;PrivilegesRequired=lowest
|
|
||||||
OutputDir=output
|
|
||||||
OutputBaseFilename=stickexportertx-setup
|
|
||||||
SetupIconFile=icon.ico
|
|
||||||
Compression=lzma
|
|
||||||
SolidCompression=yes
|
|
||||||
WizardStyle=modern
|
|
||||||
|
|
||||||
[Languages]
|
|
||||||
Name: "english"; MessagesFile: "compiler:Default.isl"
|
|
||||||
Name: "german"; MessagesFile: "compiler:Languages\German.isl"
|
|
||||||
|
|
||||||
[Tasks]
|
|
||||||
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
|
|
||||||
|
|
||||||
[Files]
|
|
||||||
Source: "release-builds\stickexportertx-win32-ia32\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
|
|
||||||
Source: "assets\*"; DestDir: "{app}\assets"; Flags: ignoreversion recursesubdirs createallsubdirs
|
|
||||||
; NOTE: Don't use "Flags: ignoreversion" on any shared system files
|
|
||||||
|
|
||||||
[Icons]
|
|
||||||
Name: "{autoprograms}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"
|
|
||||||
Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon
|
|
||||||
|
|
||||||
[Run]
|
|
||||||
Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent
|
|
||||||
22110
package-lock.json
generated
176
package.json
@@ -1,64 +1,158 @@
|
|||||||
{
|
{
|
||||||
|
"version": "0.8.1",
|
||||||
"name": "stickexportertx",
|
"name": "stickexportertx",
|
||||||
"productName": "StickExporterTX",
|
"productName": "StickExporterTX",
|
||||||
"version": "0.6.1",
|
"description": "3D stick exporter for EdgeTX/OpenTX logs",
|
||||||
"description": "A 3D Stick Exporter for EdgeTX/OpenTX Logs.",
|
"main": "src/index.build.js",
|
||||||
"main": "src/index.js",
|
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "electron-forge start",
|
"install:win32": "python ./scripts/download-blender.py",
|
||||||
"package": "electron-forge package",
|
"install:linux": "python3 ./scripts/download-blender.py",
|
||||||
"make": "electron-forge make",
|
"install:darwin": "python3 ./scripts/download-blender.py",
|
||||||
"publish": "electron-forge publish",
|
"install": "run-script-os",
|
||||||
"lint": "echo \"No linting configured\"",
|
"build:main": "cross-env NODE_ENV=production webpack --config configs/webpack.main.prod.config.babel.js",
|
||||||
"package-win": "electron-packager . stickexportertx --overwrite --asar=true --platform=win32 --arch=ia32 --icon=icon.ico --prune=true --out=release-builds --version-string.CompanyName=\"Lino Schmidt\" --version-string.FileDescription=\"3D Stick Exporter for EdgeTX/OpenTX Logs\" --version-string.ProductName=StickExporterTX"
|
"build:renderer": "cross-env NODE_ENV=production webpack --config configs/webpack.renderer.prod.config.babel.js",
|
||||||
|
"build": "cross-env npm run build:main && cross-env npm run build:renderer",
|
||||||
|
"bundle": "cross-env npm run build && electron-builder build",
|
||||||
|
"start:renderer": "cross-env NODE_ENV=development webpack serve --config configs/webpack.renderer.dev.config.babel.js",
|
||||||
|
"start:main": "cross-env NODE_ENV=development webpack --config configs/webpack.main.dev.config.babel.js && cross-env NODE_ENV=development electron .",
|
||||||
|
"start": "cross-env npm run start:renderer"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Lino Schmidt",
|
"name": "Lino Schmidt",
|
||||||
"email": "linoschmidt@lino3d.de"
|
"email": "linoschmidt@lino3d.de",
|
||||||
|
"url": "https://link.lino3d.de"
|
||||||
},
|
},
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"config": {
|
"build": {
|
||||||
"forge": {
|
"appId": "de.lino3d.stickexportertx",
|
||||||
"packagerConfig": {},
|
"productName": "StickExporterTX",
|
||||||
"makers": [
|
"files": [
|
||||||
|
"src/.webpack/renderer.js",
|
||||||
|
"src/index.build.js",
|
||||||
|
"src/index.html"
|
||||||
|
],
|
||||||
|
"extraResources": [
|
||||||
|
"dependencies/template.blend",
|
||||||
|
"dependencies/blenderScript.py",
|
||||||
|
"assets/icon.png",
|
||||||
|
"assets/render_finished_icon.png"
|
||||||
|
],
|
||||||
|
"win": {
|
||||||
|
"icon": "assets/icon.png",
|
||||||
|
"target": [
|
||||||
|
"nsis"
|
||||||
|
],
|
||||||
|
"extraResources": [
|
||||||
{
|
{
|
||||||
"name": "@electron-forge/maker-squirrel",
|
"from": "dependencies/windows/",
|
||||||
"config": {
|
"to": "dependencies/windows/",
|
||||||
"name": "stickexportertx"
|
"filter": [
|
||||||
}
|
"**/*"
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "@electron-forge/maker-zip",
|
|
||||||
"platforms": [
|
|
||||||
"darwin"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "@electron-forge/maker-deb",
|
|
||||||
"config": {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "@electron-forge/maker-rpm",
|
|
||||||
"config": {}
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"nsis": {
|
||||||
|
"oneClick": false,
|
||||||
|
"allowToChangeInstallationDirectory": true,
|
||||||
|
"license": "LICENSE"
|
||||||
|
},
|
||||||
|
"linux": {
|
||||||
|
"icon": "assets/icons",
|
||||||
|
"category": "graphics",
|
||||||
|
"target": [
|
||||||
|
"zip",
|
||||||
|
"deb",
|
||||||
|
"rpm"
|
||||||
|
],
|
||||||
|
"extraResources": [
|
||||||
|
{
|
||||||
|
"from": "dependencies/linux/",
|
||||||
|
"to": "dependencies/linux/",
|
||||||
|
"filter": [
|
||||||
|
"**/*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"mac": {
|
||||||
|
"icon": "assets/icon.icns",
|
||||||
|
"category": "public.app-category.graphics-design",
|
||||||
|
"target": [
|
||||||
|
"dmg"
|
||||||
|
],
|
||||||
|
"extraResources": [
|
||||||
|
{
|
||||||
|
"from": "dependencies/darwin/",
|
||||||
|
"to": "dependencies/darwin/",
|
||||||
|
"filter": [
|
||||||
|
"**/*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"publish": {
|
||||||
|
"provider": "github",
|
||||||
|
"owner": "LinoSchmidt",
|
||||||
|
"repo": "StickExporterTX"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/LinoSchmidt/StickExporterTX"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@electron/remote": "^2.0.8",
|
"@electron/remote": "^2.0.8",
|
||||||
|
"csv-parse": "^5.2.0",
|
||||||
"electron-log": "^4.4.7",
|
"electron-log": "^4.4.7",
|
||||||
"electron-squirrel-startup": "^1.0.0",
|
"electron-squirrel-startup": "^1.0.0",
|
||||||
"line-replace": "^2.0.1",
|
"electron-updater": "^5.0.1",
|
||||||
|
"is-valid-path": "^0.1.1",
|
||||||
|
"react": "^18.1.0",
|
||||||
|
"react-dom": "^18.1.0",
|
||||||
|
"video.js": "^7.19.2",
|
||||||
"xml-formatter": "^2.6.1"
|
"xml-formatter": "^2.6.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@electron-forge/cli": "^6.0.0-beta.63",
|
"@babel/core": "^7.18.2",
|
||||||
"@electron-forge/maker-deb": "^6.0.0-beta.63",
|
"@babel/plugin-transform-runtime": "^7.18.2",
|
||||||
"@electron-forge/maker-rpm": "^6.0.0-beta.63",
|
"@babel/preset-env": "^7.18.2",
|
||||||
"@electron-forge/maker-squirrel": "^6.0.0-beta.63",
|
"@babel/preset-react": "^7.17.12",
|
||||||
"@electron-forge/maker-zip": "^6.0.0-beta.63",
|
"@babel/preset-typescript": "^7.17.12",
|
||||||
"electron": "18.1.0",
|
"@babel/register": "^7.17.7",
|
||||||
"electron-packager": "^15.5.1"
|
"@babel/runtime": "^7.18.3",
|
||||||
|
"@types/is-valid-path": "^0.1.0",
|
||||||
|
"@types/react": "^18.0.9",
|
||||||
|
"@types/react-dom": "^18.0.5",
|
||||||
|
"@types/video.js": "^7.3.42",
|
||||||
|
"@types/webpack": "^5.28.0",
|
||||||
|
"@types/webpack-dev-server": "^4.7.2",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^5.27.0",
|
||||||
|
"@typescript-eslint/parser": "^5.27.0",
|
||||||
|
"@vercel/webpack-asset-relocator-loader": "^1.7.2",
|
||||||
|
"babel-loader": "^8.2.5",
|
||||||
|
"cross-env": "^7.0.3",
|
||||||
|
"css-loader": "^6.7.1",
|
||||||
|
"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",
|
||||||
|
"eslint-plugin-react-hooks": "^4.5.0",
|
||||||
|
"eslint-webpack-plugin": "^3.1.1",
|
||||||
|
"fork-ts-checker-webpack-plugin": "^7.2.11",
|
||||||
|
"node-loader": "^2.0.0",
|
||||||
|
"run-script-os": "^1.1.6",
|
||||||
|
"sass": "^1.52.1",
|
||||||
|
"sass-loader": "^13.0.0",
|
||||||
|
"style-loader": "^3.3.1",
|
||||||
|
"ts-loader": "^9.3.0",
|
||||||
|
"ts-node": "^10.8.0",
|
||||||
|
"typescript": "^4.7.2",
|
||||||
|
"webpack": "^5.76.0",
|
||||||
|
"webpack-cli": "^4.9.2",
|
||||||
|
"webpack-dev-server": "^4.9.1",
|
||||||
|
"webpack-merge": "^5.8.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
readme/pictures/function-edit.bmp
Normal file
|
After Width: | Height: | Size: 510 KiB |
BIN
readme/pictures/global-functions.bmp
Normal file
|
After Width: | Height: | Size: 510 KiB |
BIN
readme/pictures/special-functions.bmp
Normal file
|
After Width: | Height: | Size: 510 KiB |
79
scripts/download-blender.py
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
from io import BytesIO
|
||||||
|
import platform
|
||||||
|
import urllib.request
|
||||||
|
from zipfile import ZipFile
|
||||||
|
import tarfile
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
windowsURL = 'https://ftp.halifax.rwth-aachen.de/blender/release/Blender3.2/blender-3.2.0-windows-x64.zip'
|
||||||
|
linuxURL = 'https://ftp.halifax.rwth-aachen.de/blender/release/Blender3.2/blender-3.2.0-linux-x64.tar.xz'
|
||||||
|
|
||||||
|
if(platform.system() == 'Windows'):
|
||||||
|
if(os.path.exists('./dependencies/windows')):
|
||||||
|
print("Removing old windows folder")
|
||||||
|
shutil.rmtree('./dependencies/windows')
|
||||||
|
|
||||||
|
print("Downloading windows version")
|
||||||
|
with urllib.request.urlopen(windowsURL) as zipresp:
|
||||||
|
with ZipFile(BytesIO(zipresp.read())) as zfile:
|
||||||
|
zfile.extractall('./dependencies/windows')
|
||||||
|
|
||||||
|
print("Adjust windows version")
|
||||||
|
oldWindowsName = windowsURL.split('/')[-1].replace('.zip', '')
|
||||||
|
os.rename('./dependencies/windows/' + oldWindowsName, './dependencies/windows/blender')
|
||||||
|
|
||||||
|
if(platform.system() == 'Linux'):
|
||||||
|
if(os.path.exists('./dependencies/linux')):
|
||||||
|
print("Removing old linux folder")
|
||||||
|
shutil.rmtree('./dependencies/linux')
|
||||||
|
|
||||||
|
print("Downloading linux version")
|
||||||
|
os.mkdir('./dependencies/linux')
|
||||||
|
|
||||||
|
urllib.request.urlretrieve(linuxURL, './dependencies/linux/blender.tar.xz')
|
||||||
|
print("Extracting linux version")
|
||||||
|
with tarfile.open('./dependencies/linux/blender.tar.xz') as tfile:
|
||||||
|
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', '')
|
||||||
|
os.rename('./dependencies/linux/' + oldLinuxName, './dependencies/linux/blender')
|
||||||
|
|
||||||
|
print("Clean up linux folder")
|
||||||
|
os.remove('./dependencies/linux/blender.tar.xz')
|
||||||
|
|
||||||
|
if(platform.system() == 'Darwin'):
|
||||||
|
if(os.path.exists('./dependencies/darwin')):
|
||||||
|
print("Removing old darwin folder")
|
||||||
|
shutil.rmtree('./dependencies/darwin')
|
||||||
|
|
||||||
|
print("Darwin is not supported yet!")
|
||||||
|
|
||||||
|
# TODO: Add darwin support as following:
|
||||||
|
# - look for blender
|
||||||
|
# -- if blender not found, ask user to install blender or let the script do it
|
||||||
|
# --- if user wants the script to install blender, download the installer
|
||||||
|
# --- ask the user to follow the installer
|
||||||
|
# - try copy the blender files into the dependencies blender folder
|
||||||
|
# -- if it doesn't work, ask the user for admin rights and try to copy again
|
||||||
|
# remove the "Darwin is not supported yet!" comment
|
||||||
233
src/components/blenderController.ts
Normal file
@@ -0,0 +1,233 @@
|
|||||||
|
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, setStatus, addTerminalLine } from "./ui/renderingPage";
|
||||||
|
import {imageLoading, imageLoaded} from "./ui/settingsPage";
|
||||||
|
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";
|
||||||
|
|
||||||
|
const blenderStartString = [
|
||||||
|
templatePath,
|
||||||
|
"--background",
|
||||||
|
"--python",
|
||||||
|
blenderScriptPath
|
||||||
|
]
|
||||||
|
|
||||||
|
let blenderConsole = spawn(blenderPath, blenderStartString).on('error', function(err) {
|
||||||
|
logger.errorMSG("Could not start blender: " + err.toString());
|
||||||
|
});
|
||||||
|
let readyToAcceptCommand = false;
|
||||||
|
let renderingPicture = false;
|
||||||
|
let renderingVideo = false;
|
||||||
|
let waitingForRender = false;
|
||||||
|
|
||||||
|
function getOutPath(log:string) {
|
||||||
|
let fullOutPath = path.join(getInOutSettings().output, log.substring(log.lastIndexOf("\\")).replace(".csv", "."+getActiveProfile().videoFormat));
|
||||||
|
|
||||||
|
if(fs.existsSync(fullOutPath)) {
|
||||||
|
let i = 1;
|
||||||
|
while(fs.existsSync(fullOutPath.replace("."+getActiveProfile().videoFormat, " ("+i+")."+getActiveProfile().videoFormat))) {
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
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() {
|
||||||
|
let frames = "0";
|
||||||
|
let lastFrame = "0";
|
||||||
|
let log = "0";
|
||||||
|
|
||||||
|
blenderConsole.stdout.on('data', function(data) {
|
||||||
|
const dataStr = data.toString();
|
||||||
|
|
||||||
|
logger.info("Blender: " + dataStr);
|
||||||
|
|
||||||
|
if(renderingVideo) {
|
||||||
|
addTerminalLine(dataStr);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dataStr.includes("Blender started successfully")) {
|
||||||
|
renderingPicture = false;
|
||||||
|
renderingVideo = false;
|
||||||
|
setBlenderStatus("Started");
|
||||||
|
}
|
||||||
|
if (dataStr.includes("Blender quit")) {
|
||||||
|
if(renderingPicture) {
|
||||||
|
logger.errorMSG("Rendering preview Failed!");
|
||||||
|
} else if(renderingVideo) {
|
||||||
|
logger.errorMSG("Rendering video Failed!");
|
||||||
|
}
|
||||||
|
|
||||||
|
readyToAcceptCommand = false;
|
||||||
|
renderingPicture = false;
|
||||||
|
renderingVideo = false;
|
||||||
|
setBlenderStatus("Restarting");
|
||||||
|
setBlenderLoading(true);
|
||||||
|
setProgress();
|
||||||
|
restartBlender();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(dataStr.includes("Frames:")) {
|
||||||
|
frames = dataStr.split(":")[1];
|
||||||
|
renderingVideo = true;
|
||||||
|
readyToAcceptCommand = false;
|
||||||
|
setRenderProgress(parseInt(log), true, 0, 0);
|
||||||
|
}
|
||||||
|
if(dataStr.includes("Fra:") && renderingVideo) {
|
||||||
|
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);
|
||||||
|
stopProgress();
|
||||||
|
if(lastFrame == frames) {
|
||||||
|
openPage(Page.RenderFinish);
|
||||||
|
ipcRenderer.send("renderFinished");
|
||||||
|
// TODO: only show notification if not in do not disturb mode, currently not working. Details: https://github.com/felixrieseberg/macos-notification-state/issues/30
|
||||||
|
new Notification("Render Finished", {
|
||||||
|
body: "Rendering finished successfully!",
|
||||||
|
icon: finsishedIconPath
|
||||||
|
}).onclick = function() {
|
||||||
|
ipcRenderer.send("openApp");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.errorMSG("Render Failed!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(dataStr.includes("Init:") && renderingVideo) {
|
||||||
|
setStatus("Initialize Frame " + dataStr.split(":")[1] + "/" + frames);
|
||||||
|
setRenderProgress(parseInt(log), true, parseInt(frames), parseInt(dataStr.split(":")[1]));
|
||||||
|
}
|
||||||
|
if(dataStr.includes("Lognr:") && renderingVideo) {
|
||||||
|
log = dataStr.split(":")[1];
|
||||||
|
if(log !== "1") {
|
||||||
|
setLog(parseInt(log));
|
||||||
|
}
|
||||||
|
setLogNumber(log);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(dataStr.includes("Waiting for command")) {
|
||||||
|
pageSetRendering(false);
|
||||||
|
setProgress();
|
||||||
|
|
||||||
|
if(renderingPicture) {
|
||||||
|
imageLoaded();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!waitingForRender) {
|
||||||
|
readyToAcceptCommand = true;
|
||||||
|
renderingPicture = false;
|
||||||
|
renderingVideo = false;
|
||||||
|
setBlenderStatus("Ready");
|
||||||
|
setBlenderLoading(false);
|
||||||
|
} else {
|
||||||
|
waitingForRender = false;
|
||||||
|
renderingPicture = true;
|
||||||
|
blenderConsole.stdin.write("getRender -- "+blenderArgs()+"\n");
|
||||||
|
setBlenderStatus("Rendering");
|
||||||
|
setBlenderLoading(true);
|
||||||
|
imageLoading();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
blenderConsole.stderr.on('data', function(data:string) {
|
||||||
|
logger.errorMSG("Blender: " + data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function restartBlender() {
|
||||||
|
pageSetRendering(false);
|
||||||
|
blenderConsole.kill();
|
||||||
|
blenderConsole = spawn(blenderPath, blenderStartString);
|
||||||
|
startBlender();
|
||||||
|
}
|
||||||
|
|
||||||
|
enum blenderCmd {
|
||||||
|
getRender,
|
||||||
|
startRendering,
|
||||||
|
stopRendering,
|
||||||
|
}
|
||||||
|
|
||||||
|
function blender(command:blenderCmd) {
|
||||||
|
if(command === blenderCmd.getRender) {
|
||||||
|
if(readyToAcceptCommand) {
|
||||||
|
readyToAcceptCommand = false;
|
||||||
|
renderingPicture = true;
|
||||||
|
imageLoading();
|
||||||
|
setBlenderStatus("Rendering");
|
||||||
|
setBlenderLoading(true);
|
||||||
|
blenderConsole.stdin.write("getRender -- "+blenderArgs()+"\n");
|
||||||
|
} else {
|
||||||
|
waitingForRender = true;
|
||||||
|
}
|
||||||
|
} else if(command === blenderCmd.startRendering) {
|
||||||
|
if(readyToAcceptCommand) {
|
||||||
|
if(getInOutSettings().logs.length === 0) {
|
||||||
|
logger.warningMSG("No log selected!");
|
||||||
|
} else {
|
||||||
|
readyToAcceptCommand = false;
|
||||||
|
renderingVideo = true;
|
||||||
|
pageSetRendering(true);
|
||||||
|
setBlenderStatus("Rendering");
|
||||||
|
setBlenderLoading(true);
|
||||||
|
blenderConsole.stdin.write("startRendering -- "+blenderArgs()+"\n");
|
||||||
|
|
||||||
|
startProgress();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if(command === blenderCmd.stopRendering) {
|
||||||
|
readyToAcceptCommand = false;
|
||||||
|
renderingPicture = false;
|
||||||
|
renderingVideo = false;
|
||||||
|
restartBlender();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ipcRenderer.on("isRenderActiveClose", () => {
|
||||||
|
if(renderingVideo) {
|
||||||
|
ipcRenderer.send("renderActiveClose");
|
||||||
|
} else {
|
||||||
|
ipcRenderer.send("renderInactiveClose");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export {
|
||||||
|
blender,
|
||||||
|
blenderCmd,
|
||||||
|
startBlender,
|
||||||
|
renderingPicture,
|
||||||
|
outputArgs
|
||||||
|
}
|
||||||
8
src/components/dateFormat.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
function formatDate(year:number, month:number, day:number) {
|
||||||
|
const date = new Date(year, month, day);
|
||||||
|
return date.toLocaleDateString();
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
formatDate
|
||||||
|
}
|
||||||
216
src/components/logReader.ts
Normal file
@@ -0,0 +1,216 @@
|
|||||||
|
import logger from "./logger";
|
||||||
|
import {parse as csvParse} from "csv-parse";
|
||||||
|
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) {
|
||||||
|
return response.text();
|
||||||
|
}).then(function(data) {
|
||||||
|
return data;
|
||||||
|
}).catch(function(error) {
|
||||||
|
logger.error("Could not load log file: " + error.toString());
|
||||||
|
return "";
|
||||||
|
});
|
||||||
|
|
||||||
|
if(rawData) {
|
||||||
|
return data;
|
||||||
|
} else {
|
||||||
|
let logData:string[]|undefined = undefined;
|
||||||
|
csvParse(data, {}, (err, output:string[]) => {
|
||||||
|
if(err) {
|
||||||
|
logger.errorMSG(`Error parsing file "${filePath}": ${err}`);
|
||||||
|
logData = [];
|
||||||
|
} else {
|
||||||
|
logData = output;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
while(typeof logData === "undefined") {
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 100));
|
||||||
|
}
|
||||||
|
return logData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getLogTime(filePath:string) {
|
||||||
|
return await openLogFile(filePath, false).then(function(data) {
|
||||||
|
if(data.length > 0) {
|
||||||
|
const time = data[1][1];
|
||||||
|
const date = data[1][0];
|
||||||
|
|
||||||
|
const endTime = data[data.length - 1][1];
|
||||||
|
const endDate = data[data.length - 1][0];
|
||||||
|
|
||||||
|
const dateList = date.split("-");
|
||||||
|
const endDateList = endDate.split("-");
|
||||||
|
|
||||||
|
const timeList = time.split(":");
|
||||||
|
timeList.push(timeList[2].split(".")[1]);
|
||||||
|
timeList[2] = timeList[2].split(".")[0];
|
||||||
|
|
||||||
|
const endTimeList = endTime.split(":");
|
||||||
|
endTimeList.push(endTimeList[2].split(".")[1]);
|
||||||
|
endTimeList[2] = endTimeList[2].split(".")[0];
|
||||||
|
|
||||||
|
const year = parseInt(dateList[0]);
|
||||||
|
const month = parseInt(dateList[1]);
|
||||||
|
const day = parseInt(dateList[2]);
|
||||||
|
|
||||||
|
const hour = parseInt(timeList[0]);
|
||||||
|
const minute = parseInt(timeList[1]);
|
||||||
|
const second = parseInt(timeList[2]);
|
||||||
|
const millisecond = parseInt(timeList[3]);
|
||||||
|
|
||||||
|
let pastYears = parseInt(endDateList[0]) - year;
|
||||||
|
let pastMonths = parseInt(endDateList[1]) - month;
|
||||||
|
let pastDays = parseInt(endDateList[2]) - day;
|
||||||
|
|
||||||
|
let pastHours = parseInt(endTimeList[0]) - hour;
|
||||||
|
let pastMinutes = parseInt(endTimeList[1]) - minute;
|
||||||
|
let pastSeconds = parseInt(endTimeList[2]) - second;
|
||||||
|
let pastMilliseconds = parseInt(endTimeList[3]) - millisecond;
|
||||||
|
|
||||||
|
if(pastMilliseconds < 0) {
|
||||||
|
pastMilliseconds += 1000;
|
||||||
|
pastSeconds--;
|
||||||
|
}
|
||||||
|
if(pastSeconds < 0) {
|
||||||
|
pastSeconds += 60;
|
||||||
|
pastMinutes--;
|
||||||
|
}
|
||||||
|
if(pastMinutes < 0) {
|
||||||
|
pastMinutes += 60;
|
||||||
|
pastHours--;
|
||||||
|
}
|
||||||
|
if(pastHours < 0) {
|
||||||
|
pastHours += 24;
|
||||||
|
pastDays--;
|
||||||
|
}
|
||||||
|
if(pastDays < 0) {
|
||||||
|
pastDays += 30;
|
||||||
|
pastMonths--;
|
||||||
|
}
|
||||||
|
if(pastMonths < 0) {
|
||||||
|
pastMonths += 12;
|
||||||
|
pastYears--;
|
||||||
|
}
|
||||||
|
|
||||||
|
const logTimeFormatted = formatDate(year, month, day) + " " + hour + ":" + minute + ":" + second;
|
||||||
|
|
||||||
|
let logLengthFormatted = "00:00:00";
|
||||||
|
if(pastYears > 0) {
|
||||||
|
logLengthFormatted = pastYears + "y " + pastMonths + "m " + pastDays + "d " + pastHours + "h " + pastMinutes + "m " + pastSeconds + "s";
|
||||||
|
} else if(pastMonths > 0) {
|
||||||
|
logLengthFormatted = pastMonths + "m " + pastDays + "d " + pastHours + "h " + pastMinutes + "m " + pastSeconds + "s";
|
||||||
|
} else if(pastDays > 0) {
|
||||||
|
logLengthFormatted = pastDays + "d " + pastHours + "h " + pastMinutes + "m " + pastSeconds + "s";
|
||||||
|
} else {
|
||||||
|
logLengthFormatted = pastHours + "h " + pastMinutes + "m " + pastSeconds + "s";
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
start: {
|
||||||
|
year,
|
||||||
|
month,
|
||||||
|
day,
|
||||||
|
hour,
|
||||||
|
minute,
|
||||||
|
second,
|
||||||
|
millisecond,
|
||||||
|
formatted: logTimeFormatted
|
||||||
|
},
|
||||||
|
length: {
|
||||||
|
years: pastYears,
|
||||||
|
months: pastMonths,
|
||||||
|
days: pastDays,
|
||||||
|
hours: pastHours,
|
||||||
|
minutes: pastMinutes,
|
||||||
|
seconds: pastSeconds,
|
||||||
|
milliseconds: pastMilliseconds,
|
||||||
|
formatted: logLengthFormatted
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
start: {
|
||||||
|
year: 0,
|
||||||
|
month: 0,
|
||||||
|
day: 0,
|
||||||
|
hour: 0,
|
||||||
|
minute: 0,
|
||||||
|
second: 0,
|
||||||
|
millisecond: 0,
|
||||||
|
formatted: "Not Found"
|
||||||
|
},
|
||||||
|
length: {
|
||||||
|
years: 0,
|
||||||
|
months: 0,
|
||||||
|
days: 0,
|
||||||
|
hours: 0,
|
||||||
|
minutes: 0,
|
||||||
|
seconds: 0,
|
||||||
|
milliseconds: 0,
|
||||||
|
formatted: "Not Found"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getAllLogs() {
|
||||||
|
const loadList = [];
|
||||||
|
|
||||||
|
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,
|
||||||
|
time: await getLogTime(log)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return loadList;
|
||||||
|
}
|
||||||
|
|
||||||
|
let logList = await getAllLogs();
|
||||||
|
|
||||||
|
async function reloadAllLogs() {
|
||||||
|
logList = await getAllLogs();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateLogs() {
|
||||||
|
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", ""),
|
||||||
|
path: log,
|
||||||
|
time: await getLogTime(log)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for(const log of logList) {
|
||||||
|
if(!getInOutSettings().logs.some(x => x === log.path)) {
|
||||||
|
logList.splice(logList.indexOf(log), 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logList = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLogSize(index:number) {
|
||||||
|
return fs.statSync(getInOutSettings().logs[index]).size;
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
reloadAllLogs,
|
||||||
|
logList,
|
||||||
|
updateLogs,
|
||||||
|
getLogSize
|
||||||
|
};
|
||||||
51
src/components/logger.ts
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
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',
|
||||||
|
noLink: true,
|
||||||
|
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;
|
||||||
19
src/components/openFolder.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import {spawn} from 'child_process';
|
||||||
|
import logger from './logger';
|
||||||
|
import { Platform, platform } from './platform';
|
||||||
|
|
||||||
|
export default function openFolder(path:string) {
|
||||||
|
if (platform === Platform.Mac) {
|
||||||
|
spawn('open', [path]).on('error', (err) => {
|
||||||
|
logger.errorMSG(err.message);
|
||||||
|
});
|
||||||
|
} else if (platform === Platform.Windows) {
|
||||||
|
spawn('explorer', [path]).on('error', (err) => {
|
||||||
|
logger.errorMSG(err.message);
|
||||||
|
});
|
||||||
|
} else if (platform === Platform.Linux) {
|
||||||
|
spawn('xdg-open', [path]).on('error', (err) => {
|
||||||
|
logger.errorMSG(err.message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
24
src/components/paths.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import path from 'path';
|
||||||
|
import {app} from '@electron/remote';
|
||||||
|
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.json");
|
||||||
|
export const OLDSettingPath = path.join(dataPath, "settings.xml");
|
||||||
|
|
||||||
|
export const defaultOutputPath = path.join(app.getPath('videos'), "StickExporterTX");
|
||||||
|
|
||||||
|
export function platformCharacter() {
|
||||||
|
let platformCharacterTEMP = "/";
|
||||||
|
if (platform === Platform.Windows) {
|
||||||
|
platformCharacterTEMP = "\\";
|
||||||
|
}
|
||||||
|
return platformCharacterTEMP;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const blenderPath = path.join(appPath, "dependencies", platformFolder, "blender", "blender");
|
||||||
|
export const templatePath = path.join(appPath, "dependencies", "template.blend");
|
||||||
|
export const blenderScriptPath = path.join(appPath, "dependencies", "blenderScript.py");
|
||||||
|
|
||||||
|
export const finsishedIconPath = path.join(appPath, "assets", "render_finished_icon.png");
|
||||||
15
src/components/platform.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
const enum Platform {
|
||||||
|
Windows = 'win32',
|
||||||
|
Mac = 'darwin',
|
||||||
|
Linux = 'linux'
|
||||||
|
}
|
||||||
|
|
||||||
|
const platform = process.platform;
|
||||||
|
|
||||||
|
const platformFolder = platform === Platform.Windows ? 'windows' : platform === Platform.Mac ? 'darwin' : 'linux';
|
||||||
|
|
||||||
|
export {
|
||||||
|
platform,
|
||||||
|
Platform,
|
||||||
|
platformFolder
|
||||||
|
}
|
||||||
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
|
||||||
|
}
|
||||||
480
src/components/settings.ts
Normal file
@@ -0,0 +1,480 @@
|
|||||||
|
import {SettingPath, defaultOutputPath, OLDSettingPath} from './paths';
|
||||||
|
import {dialog} from '@electron/remote';
|
||||||
|
import logger from "./logger";
|
||||||
|
import fs from "fs";
|
||||||
|
|
||||||
|
enum VideoFormat {
|
||||||
|
mp4="mp4",
|
||||||
|
mov="mov",
|
||||||
|
webm="webm",
|
||||||
|
avi="avi",
|
||||||
|
mkv="mkv",
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
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));
|
||||||
|
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(async function(data) {
|
||||||
|
if(data === "fileLoadFailed") {
|
||||||
|
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 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 {
|
||||||
|
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(fetchFailed !== "") {
|
||||||
|
writeSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
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 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 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 {
|
||||||
|
getProfiles,
|
||||||
|
createProfile,
|
||||||
|
ProfileLoadDefault,
|
||||||
|
editProfile,
|
||||||
|
removeProfile,
|
||||||
|
setActiveProfile,
|
||||||
|
getActiveProfile,
|
||||||
|
setInOutSettings,
|
||||||
|
getInOutSettings,
|
||||||
|
setShowRenderTerminal,
|
||||||
|
getShowRenderTerminal,
|
||||||
|
importProfile,
|
||||||
|
exportProfile,
|
||||||
|
VideoFormat
|
||||||
|
}
|
||||||
124
src/components/ui/mainPage.tsx
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
import React, {useEffect, useState} from "react";
|
||||||
|
import { dialog } from "@electron/remote";
|
||||||
|
import { setInOutSettings, getInOutSettings } from "../settings";
|
||||||
|
import logger from "../logger";
|
||||||
|
import {blender, blenderCmd} from "../blenderController";
|
||||||
|
import openFolder from "../openFolder";
|
||||||
|
import {platformCharacter} from "../paths";
|
||||||
|
import {logList, reloadAllLogs, updateLogs} from "../logReader";
|
||||||
|
|
||||||
|
function MainPage() {
|
||||||
|
const [output, setOutput] = useState(getInOutSettings().output);
|
||||||
|
const [logTable, setLogTable] = useState([<tr key={0}></tr>]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
updateLogTable(setLogTable);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div id="content">
|
||||||
|
<button id="start-render" onClick={() => blender(blenderCmd.startRendering)}>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 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="M361 215C375.3 223.8 384 239.3 384 256C384 272.7 375.3 288.2 361 296.1L73.03 472.1C58.21 482 39.66 482.4 24.52 473.9C9.377 465.4 0 449.4 0 432V80C0 62.64 9.377 46.63 24.52 38.13C39.66 29.64 58.21 29.99 73.03 39.04L361 215z"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<h4 className="noMarginBottom">Logs:</h4>
|
||||||
|
<table>
|
||||||
|
<tbody>
|
||||||
|
{logTable}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div className="dataDiv">
|
||||||
|
<button id="openLogButton" onClick={() => addLog(setLogTable)}>Add Log(s)</button>
|
||||||
|
<button id="deleteLogsButton" onClick={async () => {
|
||||||
|
setInOutSettings({logs:[]});
|
||||||
|
await reloadAllLogs();
|
||||||
|
updateLogTable(setLogTable);
|
||||||
|
}}>Delete All</button>
|
||||||
|
</div>
|
||||||
|
<div className="dataDiv" id="outputDiv">
|
||||||
|
<h4>Output Folder:</h4>
|
||||||
|
<p id="output" onClick={() => openFolder(getInOutSettings().output)}>{output}</p>
|
||||||
|
<button onClick={() => openVid(setOutput)}>Select Folder</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateLogTable(setLogTable:React.Dispatch<React.SetStateAction<JSX.Element[]>>) {
|
||||||
|
function getData() {
|
||||||
|
setLogTable(logList.map((log, index) => {
|
||||||
|
return <tr key={index}>
|
||||||
|
<td style={{
|
||||||
|
fontWeight: "bold",
|
||||||
|
fontStyle: "italic"
|
||||||
|
}}>{index+1}.</td>
|
||||||
|
<td id="logList-Name" title={log.path+"\n"+log.time.start.formatted+"\n"+log.time.length.formatted} onClick={() => openFolder(log.path.substring(0, log.path.lastIndexOf(platformCharacter())))}>{log.name}</td>
|
||||||
|
<td style={{
|
||||||
|
fontStyle: "italic",
|
||||||
|
fontWeight: "lighter"
|
||||||
|
}}>({log.time.length.formatted})</td>
|
||||||
|
<td><button className="listButton" onClick={async () => {
|
||||||
|
const newLogs = getInOutSettings().logs.filter(value => value !== log.path);
|
||||||
|
setInOutSettings({logs:newLogs});
|
||||||
|
await updateLogs();
|
||||||
|
updateLogTable(setLogTable);
|
||||||
|
}}>Delete</button></td>
|
||||||
|
</tr>
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(getInOutSettings().logs.length === 0) {
|
||||||
|
setLogTable([]);
|
||||||
|
} else {
|
||||||
|
getData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function addLog(setLogTable:React.Dispatch<React.SetStateAction<JSX.Element[]>>) {
|
||||||
|
dialog.showOpenDialog({
|
||||||
|
properties: [
|
||||||
|
"multiSelections"
|
||||||
|
],
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
name: "TX-Logs",
|
||||||
|
extensions: [
|
||||||
|
"csv"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}).then(async result => {
|
||||||
|
const newLogs = getInOutSettings().logs;
|
||||||
|
result.filePaths.forEach(value => {
|
||||||
|
if(getInOutSettings().logs.includes(value)) {
|
||||||
|
logger.warningMSG("Log \"" + value + "\" already added.");
|
||||||
|
} else {
|
||||||
|
newLogs.push(value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
setInOutSettings({logs:newLogs});
|
||||||
|
await updateLogs();
|
||||||
|
updateLogTable(setLogTable);
|
||||||
|
}).catch(err => {
|
||||||
|
logger.errorMSG(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function openVid(updateHook:React.Dispatch<React.SetStateAction<string>>) {
|
||||||
|
dialog.showOpenDialog({
|
||||||
|
properties: [
|
||||||
|
"openDirectory"
|
||||||
|
]
|
||||||
|
}).then(result => {
|
||||||
|
if(result.filePaths.length > 0) {
|
||||||
|
setInOutSettings({output:String(result.filePaths)});
|
||||||
|
updateHook(String(result.filePaths));
|
||||||
|
}
|
||||||
|
}).catch(err => {
|
||||||
|
logger.errorMSG(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MainPage;
|
||||||
133
src/components/ui/menu.tsx
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
import React, {useState} from "react";
|
||||||
|
import { openPage, Page } from "../../renderer";
|
||||||
|
import { platform, Platform } from "../platform";
|
||||||
|
import { ipcRenderer } from "electron";
|
||||||
|
|
||||||
|
const WinButtons = () => (
|
||||||
|
<>
|
||||||
|
<div className="winMenuButtons" id="winMinimize" onClick={() => ipcRenderer.send('minimize')}>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 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="M400 288h-352c-17.69 0-32-14.32-32-32.01s14.31-31.99 32-31.99h352c17.69 0 32 14.3 32 31.99S417.7 288 400 288z"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div className="winMenuButtons" id="winClose" onClick={() => ipcRenderer.send('closeApp')}>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 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="M310.6 361.4c12.5 12.5 12.5 32.75 0 45.25C304.4 412.9 296.2 416 288 416s-16.38-3.125-22.62-9.375L160 301.3L54.63 406.6C48.38 412.9 40.19 416 32 416S15.63 412.9 9.375 406.6c-12.5-12.5-12.5-32.75 0-45.25l105.4-105.4L9.375 150.6c-12.5-12.5-12.5-32.75 0-45.25s32.75-12.5 45.25 0L160 210.8l105.4-105.4c12.5-12.5 32.75-12.5 45.25 0s12.5 32.75 0 45.25l-105.4 105.4L310.6 361.4z"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
|
||||||
|
const MainPageButtonsWin = () => (
|
||||||
|
<div id="settings-button-win" onClick={() => openPage(Page.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 MainPageButtonsLinux = () => (
|
||||||
|
<div id="settings-button-linux" onClick={() => openPage(Page.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 SettingsPageButtonsWin = () => (
|
||||||
|
<div id="backButton-win" onClick={() => openPage(Page.Main)}>
|
||||||
|
<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="M256 0C114.6 0 0 114.6 0 256c0 141.4 114.6 256 256 256s256-114.6 256-256C512 114.6 397.4 0 256 0zM310.6 345.4c12.5 12.5 12.5 32.75 0 45.25s-32.75 12.5-45.25 0l-112-112C147.1 272.4 144 264.2 144 256s3.125-16.38 9.375-22.62l112-112c12.5-12.5 32.75-12.5 45.25 0s12.5 32.75 0 45.25L221.3 256L310.6 345.4z"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
const SettingsPageButtonsLinux = () => (
|
||||||
|
<button className="back-button-linux" onClick={() => openPage(Page.Main)}>Back</button>
|
||||||
|
)
|
||||||
|
|
||||||
|
const BlenderLoadingSVG = () => (
|
||||||
|
<svg id="blender-loading-icon" 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="M304 48C304 74.51 282.5 96 256 96C229.5 96 208 74.51 208 48C208 21.49 229.5 0 256 0C282.5 0 304 21.49 304 48zM304 464C304 490.5 282.5 512 256 512C229.5 512 208 490.5 208 464C208 437.5 229.5 416 256 416C282.5 416 304 437.5 304 464zM0 256C0 229.5 21.49 208 48 208C74.51 208 96 229.5 96 256C96 282.5 74.51 304 48 304C21.49 304 0 282.5 0 256zM512 256C512 282.5 490.5 304 464 304C437.5 304 416 282.5 416 256C416 229.5 437.5 208 464 208C490.5 208 512 229.5 512 256zM74.98 437C56.23 418.3 56.23 387.9 74.98 369.1C93.73 350.4 124.1 350.4 142.9 369.1C161.6 387.9 161.6 418.3 142.9 437C124.1 455.8 93.73 455.8 74.98 437V437zM142.9 142.9C124.1 161.6 93.73 161.6 74.98 142.9C56.24 124.1 56.24 93.73 74.98 74.98C93.73 56.23 124.1 56.23 142.9 74.98C161.6 93.73 161.6 124.1 142.9 142.9zM369.1 369.1C387.9 350.4 418.3 350.4 437 369.1C455.8 387.9 455.8 418.3 437 437C418.3 455.8 387.9 455.8 369.1 437C350.4 418.3 350.4 387.9 369.1 369.1V369.1z"/>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
const BlenderReadySVG = () => (
|
||||||
|
<svg id="blender-ready-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 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="M438.6 105.4C451.1 117.9 451.1 138.1 438.6 150.6L182.6 406.6C170.1 419.1 149.9 419.1 137.4 406.6L9.372 278.6C-3.124 266.1-3.124 245.9 9.372 233.4C21.87 220.9 42.13 220.9 54.63 233.4L159.1 338.7L393.4 105.4C405.9 92.88 426.1 92.88 438.6 105.4H438.6z"/>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
|
||||||
|
function WindowsMenu({page, blenderLoading, blenderStatus}:{page:Page, blenderLoading:boolean, blenderStatus:string}) {
|
||||||
|
return (
|
||||||
|
<header id="winHeader">
|
||||||
|
<h4>
|
||||||
|
{(page == Page.Main)? "StickExporterTX" : null}
|
||||||
|
{(page == Page.Settings)? "Settings" : null}
|
||||||
|
{(page == Page.Rendering)? "Rendering" : null}
|
||||||
|
{(page == Page.RenderFinish)? "Render Finished" : null}
|
||||||
|
</h4>
|
||||||
|
<div id="blender-info-win">
|
||||||
|
<div id="blender-icon-win">
|
||||||
|
{blenderLoading? <BlenderLoadingSVG/> : <BlenderReadySVG/>}
|
||||||
|
</div>
|
||||||
|
<p>{blenderStatus}</p>
|
||||||
|
</div>
|
||||||
|
{(page == Page.Main)? <MainPageButtonsWin/> : null}
|
||||||
|
{(page == Page.Settings)? <SettingsPageButtonsWin/> : null}
|
||||||
|
<WinButtons/>
|
||||||
|
</header>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function LinuxMenu({page, blenderLoading, blenderStatus}:{page:Page, blenderLoading:boolean, blenderStatus:string}) {
|
||||||
|
return (
|
||||||
|
<header id="linuxHeader">
|
||||||
|
<h1>
|
||||||
|
{(page == Page.Main)? "StickExporterTX" : null}
|
||||||
|
{(page == Page.Settings)? "Settings" : null}
|
||||||
|
{(page == Page.Rendering)? "Rendering" : null}
|
||||||
|
{(page == Page.RenderFinish)? "Render Finished" : null}
|
||||||
|
</h1>
|
||||||
|
<div id="blender-info-linux">
|
||||||
|
<div id="blender-icon-linux">
|
||||||
|
{blenderLoading? <BlenderLoadingSVG/> : <BlenderReadySVG/>}
|
||||||
|
</div>
|
||||||
|
<p>{blenderStatus}</p>
|
||||||
|
</div>
|
||||||
|
{(page == Page.Main)? <MainPageButtonsLinux/> : null}
|
||||||
|
{(page == Page.Settings)? <SettingsPageButtonsLinux/> : null}
|
||||||
|
</header>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
let setBlenderLoading:React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
|
let setBlenderStatus:React.Dispatch<React.SetStateAction<string>>;
|
||||||
|
|
||||||
|
function Menu({page}:{page:Page}) {
|
||||||
|
|
||||||
|
const [blenderLoading, setBlenderLoadingInner] = useState(true);
|
||||||
|
setBlenderLoading = setBlenderLoadingInner;
|
||||||
|
const [blenderStatus, setBlenderStatusInner] = useState("Starting");
|
||||||
|
setBlenderStatus = setBlenderStatusInner;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{(platform === Platform.Windows)? <WindowsMenu page={page} blenderLoading={blenderLoading} blenderStatus={blenderStatus}/> : null}
|
||||||
|
{(platform === Platform.Linux)? <LinuxMenu page={page} blenderLoading={blenderLoading} blenderStatus={blenderStatus}/> : null}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Menu;
|
||||||
|
export {
|
||||||
|
setBlenderLoading,
|
||||||
|
setBlenderStatus
|
||||||
|
}
|
||||||
103
src/components/ui/renderFinishPage.tsx
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
import React, { CSSProperties } from "react";
|
||||||
|
import {openPage, Page} from "../../renderer";
|
||||||
|
import {outputArgs} from "../blenderController";
|
||||||
|
import openFolder from "../openFolder";
|
||||||
|
import {getInOutSettings, getActiveProfile} from "../settings";
|
||||||
|
import VideoPlayer from "./videoPlayer";
|
||||||
|
import path from 'path';
|
||||||
|
import {VideoJsPlayerOptions} from "video.js";
|
||||||
|
import {logList} from "../logReader";
|
||||||
|
import {renderInfo} from "../progressController";
|
||||||
|
|
||||||
|
const detailsStyle:CSSProperties = {
|
||||||
|
display: "flex",
|
||||||
|
marginBottom: "10px",
|
||||||
|
}
|
||||||
|
const detailsInnerStyle:CSSProperties = {
|
||||||
|
margin: "0px",
|
||||||
|
marginRight: "5px",
|
||||||
|
}
|
||||||
|
|
||||||
|
const VideoSpanStyle:CSSProperties = {
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center"
|
||||||
|
}
|
||||||
|
const videoSelectStyle:CSSProperties = {
|
||||||
|
padding: "4px 0",
|
||||||
|
border: "0",
|
||||||
|
borderRadius: "14px",
|
||||||
|
cursor: "pointer",
|
||||||
|
textAlign: "center",
|
||||||
|
fontSize: "large"
|
||||||
|
}
|
||||||
|
|
||||||
|
function RenderFinishPage() {
|
||||||
|
const [logPlaying, setLogPlaying] = React.useState(logList[0].name);
|
||||||
|
|
||||||
|
const videoPlayerOptions:VideoJsPlayerOptions = {
|
||||||
|
controls: true,
|
||||||
|
muted: true,
|
||||||
|
fluid: true,
|
||||||
|
controlBar: {
|
||||||
|
volumePanel: false,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
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(() => {
|
||||||
|
setVideoSource({
|
||||||
|
src: path.join(getInOutSettings().output, logPlaying.replace(".csv", "."+getActiveProfile().videoFormat)),
|
||||||
|
type: 'video/'+getActiveProfile().videoFormat.toUpperCase()
|
||||||
|
});
|
||||||
|
}, [logPlaying]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div id="content">
|
||||||
|
<h3 style={{
|
||||||
|
color: "#00c24a",
|
||||||
|
}}>Render finished Successfully!</h3>
|
||||||
|
<div style={detailsStyle}>
|
||||||
|
<h4 style={detailsInnerStyle}>Duration:</h4>
|
||||||
|
<p style={detailsInnerStyle}>{renderInfo.time}</p>
|
||||||
|
</div>
|
||||||
|
<div style={detailsStyle}>
|
||||||
|
<h4 style={detailsInnerStyle}>Start Time:</h4>
|
||||||
|
<p style={detailsInnerStyle}>{new Date(renderInfo.startTime).toLocaleString().replace(",", "")}</p>
|
||||||
|
</div>
|
||||||
|
<div style={detailsStyle}>
|
||||||
|
<h4 style={detailsInnerStyle}>End Time:</h4>
|
||||||
|
<p style={detailsInnerStyle}>{new Date(renderInfo.endTime).toLocaleString().replace(",", "")}</p>
|
||||||
|
</div>
|
||||||
|
<button style={{
|
||||||
|
marginTop: "10px",
|
||||||
|
marginRight: "10px",
|
||||||
|
backgroundColor: "#00c24a",
|
||||||
|
}} onClick={() => {
|
||||||
|
openPage(Page.Main);
|
||||||
|
}}>Finish</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} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RenderFinishPage;
|
||||||
148
src/components/ui/renderingPage.tsx
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
import React, {useEffect, useRef, useState} from "react";
|
||||||
|
import {getInOutSettings, getShowRenderTerminal, setShowRenderTerminal} from "../settings";
|
||||||
|
import openFolder from "../openFolder";
|
||||||
|
import { blenderCmd, blender } from "../blenderController";
|
||||||
|
|
||||||
|
let setLogNumber:React.Dispatch<React.SetStateAction<string>>;
|
||||||
|
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");
|
||||||
|
setStatus = setStatusInner;
|
||||||
|
const [renderDisplayProgress, setRenderDisplayProgressInner] = useState(0);
|
||||||
|
setRenderDisplayProgress = setRenderDisplayProgressInner;
|
||||||
|
const [pastTime, setPastTimeInner] = useState("0m 0s");
|
||||||
|
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(() => {
|
||||||
|
setPastTime(pastTimeNow);
|
||||||
|
setRemainingTime(remainingTimeNow);
|
||||||
|
}, 1000);
|
||||||
|
return () => clearInterval(interval);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div id="content">
|
||||||
|
<p>{"Log " + logNumber + "/" + getInOutSettings().logs.length}</p>
|
||||||
|
<p>{status}</p>
|
||||||
|
<div className="progress">
|
||||||
|
<div className="progress-done" style={{
|
||||||
|
width: renderDisplayProgress + "%"
|
||||||
|
}}></div>
|
||||||
|
<label>{renderDisplayProgress}%</label>
|
||||||
|
</div>
|
||||||
|
<div style={
|
||||||
|
{
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "flex-end",
|
||||||
|
marginTop: "5px",
|
||||||
|
marginBottom: "10px"
|
||||||
|
}
|
||||||
|
}>
|
||||||
|
<h4 style={{margin: "0"}}>Past Time:</h4>
|
||||||
|
<p style={
|
||||||
|
{
|
||||||
|
margin: "0",
|
||||||
|
marginRight: "auto",
|
||||||
|
marginLeft: "10px",
|
||||||
|
display: "flex",
|
||||||
|
width: "auto"
|
||||||
|
}
|
||||||
|
}>{pastTime}</p>
|
||||||
|
<h4 style={{margin: "0"}}>Remaining Time:</h4>
|
||||||
|
<p style={
|
||||||
|
{
|
||||||
|
margin: "0",
|
||||||
|
marginLeft: "10px"
|
||||||
|
}
|
||||||
|
}>{remainingTime}</p>
|
||||||
|
</div>
|
||||||
|
<button id="stopRenderButton" onClick={() => blender(blenderCmd.stopRendering)}>Stop</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>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function setPastTimeNow(time:string) {
|
||||||
|
pastTimeNow = time;
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
setStatus,
|
||||||
|
setRenderDisplayProgress,
|
||||||
|
setPastTime,
|
||||||
|
setRemainingTime,
|
||||||
|
setPastTimeNow,
|
||||||
|
setRemainingTimeNow,
|
||||||
|
addTerminalLine
|
||||||
|
};
|
||||||
456
src/components/ui/settingsPage.tsx
Normal file
@@ -0,0 +1,456 @@
|
|||||||
|
import React, {useState, useEffect, CSSProperties} from "react";
|
||||||
|
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">
|
||||||
|
{/* Font Awesome Pro 6.1.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. */}
|
||||||
|
<path d="M304 48C304 74.51 282.5 96 256 96C229.5 96 208 74.51 208 48C208 21.49 229.5 0 256 0C282.5 0 304 21.49 304 48zM304 464C304 490.5 282.5 512 256 512C229.5 512 208 490.5 208 464C208 437.5 229.5 416 256 416C282.5 416 304 437.5 304 464zM0 256C0 229.5 21.49 208 48 208C74.51 208 96 229.5 96 256C96 282.5 74.51 304 48 304C21.49 304 0 282.5 0 256zM512 256C512 282.5 490.5 304 464 304C437.5 304 416 282.5 416 256C416 229.5 437.5 208 464 208C490.5 208 512 229.5 512 256zM74.98 437C56.23 418.3 56.23 387.9 74.98 369.1C93.73 350.4 124.1 350.4 142.9 369.1C161.6 387.9 161.6 418.3 142.9 437C124.1 455.8 93.73 455.8 74.98 437V437zM142.9 142.9C124.1 161.6 93.73 161.6 74.98 142.9C56.24 124.1 56.24 93.73 74.98 74.98C93.73 56.23 124.1 56.23 142.9 74.98C161.6 93.73 161.6 124.1 142.9 142.9zM369.1 369.1C387.9 350.4 418.3 350.4 437 369.1C455.8 387.9 455.8 418.3 437 437C418.3 455.8 387.9 455.8 369.1 437C350.4 418.3 350.4 387.9 369.1 369.1V369.1z"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
function VideoFormatWarning({videoFormat}:{videoFormat:VideoFormat}) {
|
||||||
|
let message = "";
|
||||||
|
|
||||||
|
switch(videoFormat) {
|
||||||
|
case VideoFormat.mp4:
|
||||||
|
message = "mp4 has no support for alpha channel and the background will be black.";
|
||||||
|
break;
|
||||||
|
case VideoFormat.avi:
|
||||||
|
message = "avi can't be played in the preview of this app.";
|
||||||
|
break;
|
||||||
|
case VideoFormat.mkv:
|
||||||
|
message = "mkv can't be played in the preview of this app.";
|
||||||
|
break;
|
||||||
|
case VideoFormat.mov:
|
||||||
|
message = "mov can't be played in the preview of this app.";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const style:CSSProperties = {
|
||||||
|
height: "35px",
|
||||||
|
width: "35px",
|
||||||
|
fill: "yellow",
|
||||||
|
paddingLeft: "5px",
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div id="videoFormatWarning" title={message} style={style}>
|
||||||
|
<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="M506.3 417l-213.3-364c-16.33-28-57.54-28-73.98 0l-213.2 364C-10.59 444.9 9.849 480 42.74 480h426.6C502.1 480 522.6 445 506.3 417zM232 168c0-13.25 10.75-24 24-24S280 154.8 280 168v128c0 13.25-10.75 24-23.1 24S232 309.3 232 296V168zM256 416c-17.36 0-31.44-14.08-31.44-31.44c0-17.36 14.07-31.44 31.44-31.44s31.44 14.08 31.44 31.44C287.4 401.9 273.4 416 256 416z"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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(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(() => {
|
||||||
|
editProfile({width, stickDistance, stickMode2});
|
||||||
|
blender(blenderCmd.getRender);
|
||||||
|
}, 500);
|
||||||
|
|
||||||
|
return () => clearTimeout(timer);
|
||||||
|
}, [width, stickDistance, stickMode2]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
editProfile({fps, videoFormat});
|
||||||
|
}, 500);
|
||||||
|
|
||||||
|
return () => clearTimeout(timer);
|
||||||
|
}, [fps, videoFormat]);
|
||||||
|
|
||||||
|
pageLoaded = true;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div id="content">
|
||||||
|
<div id="settingRow">
|
||||||
|
<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">
|
||||||
|
{<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}
|
||||||
|
{videoFormat === VideoFormat.mkv? <VideoFormatWarning videoFormat={videoFormat}/> : null}
|
||||||
|
</div>
|
||||||
|
<button id="resetSettingsButton" onClick={() => {
|
||||||
|
ProfileLoadDefault({all: true});
|
||||||
|
|
||||||
|
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>
|
||||||
|
{renderLoading? <RenderLoadingSpinner/> : null}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function imageLoading() {
|
||||||
|
if(pageLoaded) setRenderLoading(true);
|
||||||
|
}
|
||||||
|
function imageLoaded() {
|
||||||
|
if(pageLoaded) {
|
||||||
|
setRenderImg(picturePath());
|
||||||
|
setRenderLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SettingsPage;
|
||||||
|
export {
|
||||||
|
imageLoading,
|
||||||
|
imageLoaded
|
||||||
|
};
|
||||||
34
src/components/ui/videoPlayer.tsx
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import React from "react";
|
||||||
|
import videojs from "video.js";
|
||||||
|
import "video.js/dist/video-js.css";
|
||||||
|
|
||||||
|
const VideoPlayer = ({options, src}:{options:videojs.PlayerOptions, src:{src:string, type:string}}) => {
|
||||||
|
const videoNode = React.useRef<HTMLVideoElement>(null);
|
||||||
|
const player = React.useRef<videojs.Player>();
|
||||||
|
const srcLoaded = React.useRef<boolean>(false);
|
||||||
|
const initialized = React.useRef<boolean>(false);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if(!srcLoaded.current) {
|
||||||
|
if(videoNode.current && !initialized.current) {
|
||||||
|
videojs(videoNode.current, {
|
||||||
|
...options,
|
||||||
|
...{
|
||||||
|
sources: [src]
|
||||||
|
}
|
||||||
|
}).ready(function() {
|
||||||
|
player.current = this;
|
||||||
|
srcLoaded.current = true;
|
||||||
|
});
|
||||||
|
initialized.current = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
player.current?.src(src);
|
||||||
|
player.current?.load();
|
||||||
|
}
|
||||||
|
}, [options, src]);
|
||||||
|
|
||||||
|
return <video ref={videoNode} className="video-js" />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default VideoPlayer;
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
overflow: hidden;
|
|
||||||
background-color: #172336;
|
|
||||||
color: white;
|
|
||||||
font-family: Arial, Helvetica, sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dataDiv {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
.dataDiv p {
|
|
||||||
margin-right: 10px;
|
|
||||||
}
|
|
||||||
485
src/index.css
Normal file
@@ -0,0 +1,485 @@
|
|||||||
|
body {
|
||||||
|
background-color: #172336;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
color: white;
|
||||||
|
font-family: sans-serif;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dataDiv {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
.dataDiv p {
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 10px;
|
||||||
|
}
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background-color: #9DA8B9;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
::-webkit-scrollbar-track {
|
||||||
|
background: #0d131e;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-items: center;
|
||||||
|
background-color: #0d131e;
|
||||||
|
color: #9DA8B9;
|
||||||
|
margin-bottom: 25px;
|
||||||
|
}
|
||||||
|
#winHeader {
|
||||||
|
padding: 0px 8px;
|
||||||
|
padding-right: 0px;
|
||||||
|
height: 31px;
|
||||||
|
-webkit-app-region: drag;
|
||||||
|
}
|
||||||
|
#linuxHeader {
|
||||||
|
padding: 20px 25px;
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
header h4 {
|
||||||
|
margin-right: auto;
|
||||||
|
display: flex;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
header h1 {
|
||||||
|
margin-right: auto;
|
||||||
|
display: flex;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-button-win {
|
||||||
|
cursor: pointer;
|
||||||
|
-webkit-app-region: no-drag;
|
||||||
|
padding: 6px 10px;
|
||||||
|
height: 100%;
|
||||||
|
background-color: #2196F3;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 15px;
|
||||||
|
font-size: large;
|
||||||
|
}
|
||||||
|
.back-button-linux {
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 10px;
|
||||||
|
background-color: #2196F3;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 20px;
|
||||||
|
display: flex;
|
||||||
|
font-size: large;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-button-win:hover {
|
||||||
|
background-color: #0b6ab9;
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
.back-button-linux:hover {
|
||||||
|
transform: scale(1.05);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#backButton-win {
|
||||||
|
cursor: pointer;
|
||||||
|
-webkit-app-region: no-drag;
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
#backButton-win svg {
|
||||||
|
height: 18px;
|
||||||
|
fill: #9DA8B9;
|
||||||
|
}
|
||||||
|
#backButton-win:hover {
|
||||||
|
background-color: #2d323c;
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#stopRenderButton {
|
||||||
|
background-color: #e1334e;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#content {
|
||||||
|
padding-left: 25px;
|
||||||
|
padding-right: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#settings-button-win {
|
||||||
|
cursor: pointer;
|
||||||
|
-webkit-app-region: no-drag;
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
#settings-button-win svg {
|
||||||
|
height: 18px;
|
||||||
|
fill: #9DA8B9;
|
||||||
|
}
|
||||||
|
#settings-button-win:hover {
|
||||||
|
background-color: #2d323c;
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#settings-button-linux {
|
||||||
|
cursor: pointer;
|
||||||
|
background-color: #2196F3;
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 45px;
|
||||||
|
height: 45px;
|
||||||
|
align-items: center;
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
#settings-button-linux svg {
|
||||||
|
height: 35px;
|
||||||
|
fill: white;
|
||||||
|
margin-left: 5px;
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
#settings-button-linux:hover {
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
#blender-info-win {
|
||||||
|
margin-right: auto;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
#blender-info-win p {
|
||||||
|
padding-left: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#blender-info-linux {
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
#blender-info-linux p {
|
||||||
|
margin-top: 5px;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#blender-icon-win {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
align-items: center;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
#blender-icon-linux {
|
||||||
|
width: 25px;
|
||||||
|
height: 25px;
|
||||||
|
align-items: center;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
#blender-icon-win svg {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
fill: white;
|
||||||
|
}
|
||||||
|
#blender-icon-linux svg {
|
||||||
|
width: 25px;
|
||||||
|
height: 25px;
|
||||||
|
fill: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
#blender-loading-icon {
|
||||||
|
animation: rotate-icon 1.2s linear infinite;
|
||||||
|
}
|
||||||
|
#blender-loading-icon path {
|
||||||
|
fill: #2196F3;
|
||||||
|
}
|
||||||
|
#blender-ready-icon path {
|
||||||
|
fill: #00c24a;
|
||||||
|
}
|
||||||
|
|
||||||
|
#renderLoadingDiv {
|
||||||
|
position: absolute;
|
||||||
|
background-color: rgba(0,0,0,0.9);
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
bottom: 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
#renderLoadingDiv svg {
|
||||||
|
height: 50%;
|
||||||
|
fill: #2196F3;
|
||||||
|
animation: rotate-icon 1.2s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes rotate-icon {
|
||||||
|
to {
|
||||||
|
transform: rotate(1turn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#start-render {
|
||||||
|
cursor: pointer;
|
||||||
|
background-color: #00c24a;
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
align-items: center;
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
#start-render svg {
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
fill: white;
|
||||||
|
margin-left: 5px;
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
#start-render:hover {
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
#render-ex {
|
||||||
|
width: auto;
|
||||||
|
height: auto;
|
||||||
|
max-width: 100%;
|
||||||
|
display: block;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
#renderImgDiv {
|
||||||
|
position: relative;
|
||||||
|
margin-top: 15px;
|
||||||
|
image-rendering: pixelated;
|
||||||
|
width: fit-content;
|
||||||
|
height: fit-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
.noMarginBottom {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#outputDiv {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
#output {
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
#output:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
#openLogButton {
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
#deleteLogsButton {
|
||||||
|
background-color: #e1334e;
|
||||||
|
margin-top: 5px;
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#openOutputMain {
|
||||||
|
margin-top: 15px;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.listButton {
|
||||||
|
background-color: #e1334e;
|
||||||
|
padding: 5px;
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 10px;
|
||||||
|
background-color: #2196F3;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 20px;
|
||||||
|
font-weight: bolder;
|
||||||
|
}
|
||||||
|
button:hover {
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.inputSelectSpan {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
height: 35px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
.inputSelectSpan input {
|
||||||
|
width: 75px;
|
||||||
|
height: 100%;
|
||||||
|
border: 0;
|
||||||
|
border-top-right-radius: 18px;
|
||||||
|
border-bottom-right-radius: 18px;
|
||||||
|
text-align: center;
|
||||||
|
font-size: large;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
#resetSettingsButton {
|
||||||
|
height: 35px;
|
||||||
|
background-color: #e1334e;
|
||||||
|
}
|
||||||
|
|
||||||
|
#logList-Name:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.winMenuButtons {
|
||||||
|
fill: #9DA8B9;
|
||||||
|
height: 100%;
|
||||||
|
width: 47px;
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
-webkit-app-region: no-drag;
|
||||||
|
}
|
||||||
|
.winMenuButtons:hover {
|
||||||
|
background-color: #2d323c;
|
||||||
|
}
|
||||||
|
.winMenuButtons svg {
|
||||||
|
height: 17px;
|
||||||
|
width: 17px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#winClose:hover {
|
||||||
|
background-color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
#winMinimize {
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress {
|
||||||
|
height: 25px;
|
||||||
|
width: 100%;
|
||||||
|
background-color: #0d131e;
|
||||||
|
border-radius: 15px;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.progress-done {
|
||||||
|
height: 100%;
|
||||||
|
background: repeating-linear-gradient(
|
||||||
|
-45deg,
|
||||||
|
#2196F3,
|
||||||
|
#2196F3 10px,
|
||||||
|
#0b6ab9 10px,
|
||||||
|
#0b6ab9 20px
|
||||||
|
);
|
||||||
|
border-radius: 15px;
|
||||||
|
transition: width 1s;
|
||||||
|
}
|
||||||
|
.progress label {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
font-size: large;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selectSpan {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
||||||
@@ -2,49 +2,18 @@
|
|||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
|
|
||||||
<title>StickExporterTX</title>
|
<title>StickExporterTX</title>
|
||||||
|
|
||||||
<link rel="stylesheet" href="./css/style.css"/>
|
|
||||||
<link rel="stylesheet" href="./css/toggle-switchy.css"/>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<button onclick="startRender()">Start Render</button>
|
<div id="root"></div>
|
||||||
<p id="logNumber">Log 0/0</p>
|
|
||||||
<div class="dataDiv">
|
<script>
|
||||||
<p id="status">Idle</p>
|
if(process.env.NODE_ENV === 'development') {
|
||||||
<button onclick="openOutputFolder()">Open Output Folder</button>
|
const port = process.env.PORT || 3000;
|
||||||
</div>
|
document.write('<script src="http://localhost:'+port+'/renderer.js"><\/script>');
|
||||||
<hr>
|
} else {
|
||||||
<div class="dataDiv">
|
document.write('<script src="./.webpack/renderer.js"><\/script>');
|
||||||
<p>FPS: </p>
|
}
|
||||||
<input id="fpsInput" type="number" value="25" min="1" step="1" onchange="setFPS();">
|
</script>
|
||||||
</div>
|
|
||||||
<div class="dataDiv">
|
|
||||||
<p>Width: </p>
|
|
||||||
<input id="widthInput" type="number" value="540" min="1" step="1" onchange="setWidth();">
|
|
||||||
</div>
|
|
||||||
<div class="dataDiv">
|
|
||||||
<p>Stick Distance: </p>
|
|
||||||
<input id="stickDistanceInput" type="number" value="540" min="0" step="1" onchange="setStickDistance();">
|
|
||||||
</div>
|
|
||||||
<div class="dataDiv">
|
|
||||||
<p>Stick Mode:</p>
|
|
||||||
<label for="stickMode" class="toggle-switchy" data-style="rounded" data-text="12">
|
|
||||||
<input checked type="checkbox" id="stickMode" onchange="setStickMode();">
|
|
||||||
<span class="toggle">
|
|
||||||
<span class="switch"></span>
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div class="dataDiv">
|
|
||||||
<p id="log">Logs:<br/></p>
|
|
||||||
<button onclick="openLog();">Open Log</button>
|
|
||||||
</div>
|
|
||||||
<div class="dataDiv">
|
|
||||||
<p id="output">Output Folder: </p>
|
|
||||||
<button onclick="openVid();">Open Video</button>
|
|
||||||
</div>
|
|
||||||
<script src="./js/render.js"></script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
57
src/index.js
@@ -1,57 +0,0 @@
|
|||||||
const { app, BrowserWindow } = require('electron');
|
|
||||||
const path = require('path');
|
|
||||||
|
|
||||||
// Handle creating/removing shortcuts on Windows when installing/uninstalling.
|
|
||||||
if (require('electron-squirrel-startup')) {
|
|
||||||
// eslint-disable-line global-require
|
|
||||||
app.quit();
|
|
||||||
}
|
|
||||||
|
|
||||||
const createWindow = () => {
|
|
||||||
// Create the browser window.
|
|
||||||
const mainWindow = new BrowserWindow({
|
|
||||||
width: 800,
|
|
||||||
height: 600,
|
|
||||||
webPreferences: {
|
|
||||||
nodeIntegration: true,
|
|
||||||
contextIsolation: false,
|
|
||||||
enableRemoteModule: true
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
mainWindow.setMenu(null);
|
|
||||||
|
|
||||||
require('@electron/remote/main').initialize();
|
|
||||||
require("@electron/remote/main").enable(mainWindow.webContents);
|
|
||||||
|
|
||||||
// and load the index.html of the app.
|
|
||||||
mainWindow.loadFile(path.join(__dirname, 'index.html'));
|
|
||||||
|
|
||||||
// Open the DevTools.
|
|
||||||
// mainWindow.webContents.openDevTools();
|
|
||||||
};
|
|
||||||
|
|
||||||
// This method will be called when Electron has finished
|
|
||||||
// initialization and is ready to create browser windows.
|
|
||||||
// Some APIs can only be used after this event occurs.
|
|
||||||
app.on('ready', createWindow);
|
|
||||||
|
|
||||||
// Quit when all windows are closed, except on macOS. There, it's common
|
|
||||||
// for applications and their menu bar to stay active until the user quits
|
|
||||||
// explicitly with Cmd + Q.
|
|
||||||
app.on('window-all-closed', () => {
|
|
||||||
if (process.platform !== 'darwin') {
|
|
||||||
app.quit();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
app.on('activate', () => {
|
|
||||||
// On OS X it's common to re-create a window in the app when the
|
|
||||||
// dock icon is clicked and there are no other windows open.
|
|
||||||
if (BrowserWindow.getAllWindows().length === 0) {
|
|
||||||
createWindow();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// In this file you can include the rest of your app's specific main process
|
|
||||||
// code. You can also put them in separate files and import them here.
|
|
||||||
169
src/index.ts
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
import {app, BrowserWindow, dialog, ipcMain, NativeImage} from 'electron';
|
||||||
|
import {initialize as remoteInitialize, enable as remoteEnable} from '@electron/remote/main';
|
||||||
|
import path from 'path';
|
||||||
|
import { autoUpdater } from "electron-updater";
|
||||||
|
import logger from 'electron-log';
|
||||||
|
import { Platform, platform } from './components/platform';
|
||||||
|
|
||||||
|
logger.transports.console.format = "{h}:{i}:{s} {text}";
|
||||||
|
logger.transports.file.getFile();
|
||||||
|
logger.transports.file.resolvePath = () => path.join(app.getPath('userData'), "logs", "start.log");
|
||||||
|
|
||||||
|
const appIconPath = path.join(app.getAppPath().replace("app.asar", ""), "assets", "icon.png");
|
||||||
|
const finsishedIconPath = path.join(app.getAppPath().replace("app.asar", ""), "assets", "render_finished_icon.png");
|
||||||
|
|
||||||
|
autoUpdater.autoDownload = false;
|
||||||
|
|
||||||
|
autoUpdater.checkForUpdatesAndNotify();
|
||||||
|
|
||||||
|
autoUpdater.on('update-available', async () => {
|
||||||
|
const response = await dialog.showMessageBox({
|
||||||
|
type: 'info',
|
||||||
|
title: 'Update Available',
|
||||||
|
message: 'Found updates, do you want update now?',
|
||||||
|
buttons: ['Yes', 'Later'],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.response === 0) {
|
||||||
|
logger.log('Downloading Update');
|
||||||
|
autoUpdater.downloadUpdate();
|
||||||
|
await dialog.showMessageBox({
|
||||||
|
type: 'info',
|
||||||
|
title: 'Update Downloading',
|
||||||
|
message:
|
||||||
|
'Update is being downloaded, you will be notified when it is ready to install',
|
||||||
|
buttons: [],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
autoUpdater.on('update-downloaded', async () => {
|
||||||
|
const response = await dialog.showMessageBox({
|
||||||
|
type: 'info',
|
||||||
|
buttons: ['Restart', 'Later'],
|
||||||
|
title: 'Application Update',
|
||||||
|
message: 'Update',
|
||||||
|
detail:
|
||||||
|
'A new version has been downloaded. Restart the application to apply the updates.',
|
||||||
|
});
|
||||||
|
if (response.response === 0) {
|
||||||
|
setImmediate(() => autoUpdater.quitAndInstall());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle creating/removing shortcuts on Windows when installing/uninstalling.
|
||||||
|
if (require('electron-squirrel-startup')) {
|
||||||
|
// eslint-disable-line global-require
|
||||||
|
app.quit();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(platform === Platform.Windows) {
|
||||||
|
app.setAppUserModelId(app.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
const createWindow = () => {
|
||||||
|
// Create the browser window.
|
||||||
|
const mainWindow = new BrowserWindow({
|
||||||
|
width: 800,
|
||||||
|
height: 600,
|
||||||
|
minWidth: 600,
|
||||||
|
minHeight: 600,
|
||||||
|
webPreferences: {
|
||||||
|
nodeIntegration: true,
|
||||||
|
contextIsolation: false,
|
||||||
|
},
|
||||||
|
titleBarStyle: 'hidden'
|
||||||
|
});
|
||||||
|
|
||||||
|
mainWindow.setMenu(null);
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
mainWindow.webContents.openDevTools();
|
||||||
|
}
|
||||||
|
|
||||||
|
remoteInitialize();
|
||||||
|
remoteEnable(mainWindow.webContents);
|
||||||
|
|
||||||
|
// load the index.html of the app.
|
||||||
|
mainWindow.loadFile(path.join(__dirname, 'index.html'));
|
||||||
|
|
||||||
|
ipcMain.on('closeApp', () => {
|
||||||
|
mainWindow.webContents.send('isRenderActiveClose');
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.on('minimize', () => {
|
||||||
|
mainWindow.minimize();
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.on('renderInactiveClose', () => {
|
||||||
|
app.quit();
|
||||||
|
});
|
||||||
|
ipcMain.on('renderActiveClose', async () => {
|
||||||
|
const response = await dialog.showMessageBox({
|
||||||
|
type: 'warning',
|
||||||
|
noLink: true,
|
||||||
|
buttons: ['Cancel','Minimize', 'Exit'],
|
||||||
|
defaultId: 0,
|
||||||
|
title: 'Close',
|
||||||
|
message: 'A video is still being renderd!',
|
||||||
|
detail:
|
||||||
|
'If you close the application, the progress will be lost!',
|
||||||
|
});
|
||||||
|
if (response.response === 2) {
|
||||||
|
app.quit();
|
||||||
|
} else if (response.response === 1) {
|
||||||
|
mainWindow.minimize();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.on('setProgress', (event, arg) => {
|
||||||
|
const progress = parseFloat(arg);
|
||||||
|
mainWindow.setProgressBar(progress);
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.on('renderFinished', () => {
|
||||||
|
if(!mainWindow.isFocused()) {
|
||||||
|
mainWindow.setOverlayIcon(finsishedIconPath as unknown as NativeImage, 'Rendering Complete');
|
||||||
|
mainWindow.flashFrame(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
mainWindow.on('focus', () => {
|
||||||
|
mainWindow.setOverlayIcon(null, '');
|
||||||
|
mainWindow.flashFrame(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
mainWindow.on('close', () => {
|
||||||
|
mainWindow.webContents.send('closeApp');
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.on('openApp', () => {
|
||||||
|
mainWindow.show();
|
||||||
|
});
|
||||||
|
|
||||||
|
mainWindow.setIcon(appIconPath);
|
||||||
|
};
|
||||||
|
|
||||||
|
// This method will be called when Electron has finished
|
||||||
|
// initialization and is ready to create browser windows.
|
||||||
|
// Some APIs can only be used after this event occurs.
|
||||||
|
app.on('ready', createWindow);
|
||||||
|
|
||||||
|
// Quit when all windows are closed, except on macOS. There, it's common
|
||||||
|
// for applications and their menu bar to stay active until the user quits
|
||||||
|
// explicitly with Cmd + Q.
|
||||||
|
app.on('window-all-closed', () => {
|
||||||
|
if (platform !== Platform.Mac) {
|
||||||
|
app.quit();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.on('activate', () => {
|
||||||
|
// On OS X it's common to re-create a window in the app when the
|
||||||
|
// dock icon is clicked and there are no other windows open.
|
||||||
|
if (BrowserWindow.getAllWindows().length === 0) {
|
||||||
|
createWindow();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// In this file you can include the rest of your app's specific main process
|
||||||
|
// code. You can also put them in separate files and import them here.
|
||||||
220
src/js/render.js
@@ -1,220 +0,0 @@
|
|||||||
var lastData = "Initialise..."
|
|
||||||
var frames = "0";
|
|
||||||
var lastFrame = "0";
|
|
||||||
var logCount = 0;
|
|
||||||
|
|
||||||
var fps = 25
|
|
||||||
var width = 540
|
|
||||||
var stickDistance = 5
|
|
||||||
var stickMode2 = true
|
|
||||||
var log = '"None"'
|
|
||||||
var output = "None"
|
|
||||||
|
|
||||||
const statusDisplay = document.getElementById("status");
|
|
||||||
const fpsDisplay = document.getElementById("fpsInput");
|
|
||||||
const widthDistplay = document.getElementById("widthInput");
|
|
||||||
const stickDistanceDisplay = document.getElementById("stickDistanceInput");
|
|
||||||
const stickModeDisplay = document.getElementById("stickMode");
|
|
||||||
const logDisplay = document.getElementById("log");
|
|
||||||
const outputDisplay = document.getElementById("output");
|
|
||||||
const logNumberDisplay = document.getElementById("logNumber");
|
|
||||||
|
|
||||||
const logger = require('electron-log');
|
|
||||||
const fs = require("fs");
|
|
||||||
const formatXml = require("xml-formatter");
|
|
||||||
const {dialog, app} = require("@electron/remote");
|
|
||||||
const path = require('path');
|
|
||||||
const lineReplace = require('line-replace');
|
|
||||||
|
|
||||||
const dataFolder = app.getPath('userData');
|
|
||||||
const SettingFolder = path.join(dataFolder, "settings.xml");
|
|
||||||
|
|
||||||
const blenderPath = path.join("assets", "blender", "blender");
|
|
||||||
const templatePath = path.join("assets", "template.blend");
|
|
||||||
const blenderScriptPath = path.join("assets", "blenderScript.py");
|
|
||||||
|
|
||||||
lineReplace({
|
|
||||||
file: blenderScriptPath,
|
|
||||||
line: 9,
|
|
||||||
text: 'settings = ET.parse("' + SettingFolder.replaceAll('\\', '/') + '")',
|
|
||||||
addNewLine: true,
|
|
||||||
callback: ({error}) => {
|
|
||||||
if(error != null) {
|
|
||||||
statusDisplay.innerHTML = "Something went wrong! Check Logs.";
|
|
||||||
statusDisplay.style.color = "red";
|
|
||||||
logger.error(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
logger.transports.console.format = "{h}:{i}:{s} {text}";
|
|
||||||
logger.transports.file.getFile();
|
|
||||||
logger.transports.file.resolvePath = () => path.join(dataFolder, "logs", "main.log");
|
|
||||||
|
|
||||||
function startRender() {
|
|
||||||
const {exec} = require("child_process");
|
|
||||||
var blenderCons = exec('"' + blenderPath + '" "' + templatePath + '" --background --python "' + blenderScriptPath + '"', {maxBuffer: Infinity});
|
|
||||||
|
|
||||||
frames = "0";
|
|
||||||
lastFrame = "0";
|
|
||||||
statusDisplay.innerHTML = lastData = "Initialise...";
|
|
||||||
statusDisplay.style.color = "white";
|
|
||||||
logNumberDisplay.innerHTML = "Log 0/" + String(logCount);
|
|
||||||
blenderCons.stdout.on("data", (data) => {
|
|
||||||
var dataStr = String(data);
|
|
||||||
logger.info(dataStr);
|
|
||||||
|
|
||||||
if(dataStr.startsWith("Frames:")) {
|
|
||||||
frames = dataStr.split(":")[1];
|
|
||||||
} else if(dataStr.startsWith("Fra:")) {
|
|
||||||
lastFrame = dataStr.split(":")[1].split(" ")[0]
|
|
||||||
lastData = "Render Frame " + lastFrame + "/" + frames;
|
|
||||||
} else if(dataStr.startsWith("Finished")) {
|
|
||||||
if(lastFrame == frames) {
|
|
||||||
lastData = "Finished Render Successfully!"
|
|
||||||
statusDisplay.style.color = "white";
|
|
||||||
} else {
|
|
||||||
lastData = "Something went wrong! Check Logs."
|
|
||||||
statusDisplay.style.color = "red";
|
|
||||||
}
|
|
||||||
} else if(dataStr.includes("Blender quit")) {
|
|
||||||
if(lastData != "Finished Render Successfully!") {
|
|
||||||
lastData = "Something went wrong! Check Logs.";
|
|
||||||
statusDisplay.style.color = "red";
|
|
||||||
}
|
|
||||||
} else if(dataStr.startsWith("Init:")) {
|
|
||||||
lastData = "Initialize Frame " + dataStr.split(":")[1] + "/" + frames;
|
|
||||||
} else if(dataStr.startsWith("Lognr:")) {
|
|
||||||
logNumberDisplay.innerHTML = "Log " + dataStr.split(":")[1] + "/" + String(logCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(statusDisplay.innerHTML != lastData) {
|
|
||||||
statusDisplay.innerHTML = lastData;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function getXMLChild(doc, child) {
|
|
||||||
return String(doc.getElementsByTagName(child)[0].childNodes[0].nodeValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateSettingDisplay() {
|
|
||||||
fpsDisplay.value = String(fps);
|
|
||||||
widthDistplay.value = String(width);
|
|
||||||
stickDistanceDisplay.value = String(stickDistance);
|
|
||||||
stickModeDisplay.checked = stickMode2;
|
|
||||||
logDisplay.innerHTML = "Logs:<br/>" + log.substring(1).slice(0, -1).replaceAll("\"\"", "<br/>");
|
|
||||||
outputDisplay.innerHTML = "Output Folder: " + output;
|
|
||||||
logCount = log.split("\"\"").length;
|
|
||||||
logNumberDisplay.innerHTML = "Log 0/" + String(logCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateSettings() {
|
|
||||||
var xmlStr = '<?xml version="1.0"?><settings><fps>' + String(fps) +
|
|
||||||
'</fps><width>' + String(width) +
|
|
||||||
'</width><stickDistance>' + String(stickDistance) +
|
|
||||||
'</stickDistance><stickMode2>' + ((stickMode2)?"true":"false") +
|
|
||||||
'</stickMode2><log>' + log +
|
|
||||||
'</log><output>' + output +
|
|
||||||
'</output></settings>';
|
|
||||||
|
|
||||||
fs.writeFile(SettingFolder, formatXml(xmlStr, {collapseContent: true}), function(err) {
|
|
||||||
if(err) {
|
|
||||||
statusDisplay.innerHTML = "Couldn't write Log! Check Logs.";
|
|
||||||
statusDisplay.style.color = "red";
|
|
||||||
logger.error(err);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fetch(SettingFolder).then(function(response){
|
|
||||||
return response.text();
|
|
||||||
}).then(function(data){
|
|
||||||
let parser = new DOMParser();
|
|
||||||
let xmlDoc = parser.parseFromString(data, 'text/xml');
|
|
||||||
|
|
||||||
fps = parseInt(getXMLChild(xmlDoc, "fps"));
|
|
||||||
width = parseInt(getXMLChild(xmlDoc, "width"));
|
|
||||||
stickDistance = parseInt(getXMLChild(xmlDoc, "stickDistance"));
|
|
||||||
if(getXMLChild(xmlDoc, "stickMode2") == "false") {
|
|
||||||
stickMode2 = false;
|
|
||||||
} else {
|
|
||||||
stickMode2 = true;
|
|
||||||
}
|
|
||||||
log = getXMLChild(xmlDoc, "log");
|
|
||||||
output = getXMLChild(xmlDoc, "output");
|
|
||||||
|
|
||||||
updateSettingDisplay();
|
|
||||||
}).catch(function(error) {
|
|
||||||
logger.error(error);
|
|
||||||
updateSettingDisplay();
|
|
||||||
updateSettings();
|
|
||||||
});
|
|
||||||
|
|
||||||
function openLog() {
|
|
||||||
dialog.showOpenDialog({
|
|
||||||
properties: [
|
|
||||||
"multiSelections"
|
|
||||||
],
|
|
||||||
filters: [
|
|
||||||
{
|
|
||||||
name: "TX-Logs",
|
|
||||||
extensions: [
|
|
||||||
"csv"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}).then(result => {
|
|
||||||
logStr = "";
|
|
||||||
result.filePaths.forEach(value => {
|
|
||||||
logStr += "\"" + String(value) + "\"";
|
|
||||||
});
|
|
||||||
log = logStr;
|
|
||||||
updateSettingDisplay();
|
|
||||||
updateSettings();
|
|
||||||
}).catch(err => {
|
|
||||||
statusDisplay.innerHTML = "Something went wrong! Check Logs.";
|
|
||||||
statusDisplay.style.color = "red";
|
|
||||||
logger.error(err);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function openVid() {
|
|
||||||
dialog.showOpenDialog({
|
|
||||||
properties: [
|
|
||||||
"openDirectory"
|
|
||||||
]
|
|
||||||
}).then(result => {
|
|
||||||
output = String(result.filePaths);
|
|
||||||
updateSettingDisplay();
|
|
||||||
updateSettings();
|
|
||||||
}).catch(err => {
|
|
||||||
statusDisplay.innerHTML = "Something went wrong! Check Logs.";
|
|
||||||
statusDisplay.style.color = "red";
|
|
||||||
logger.error(err);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function setFPS() {
|
|
||||||
fps = parseInt(fpsDisplay.value);
|
|
||||||
updateSettings();
|
|
||||||
}
|
|
||||||
|
|
||||||
function setWidth() {
|
|
||||||
width = parseInt(widthDistplay.value);
|
|
||||||
updateSettings();
|
|
||||||
}
|
|
||||||
|
|
||||||
function setStickDistance() {
|
|
||||||
stickDistance = parseInt(stickDistanceDisplay.value);
|
|
||||||
updateSettings();
|
|
||||||
}
|
|
||||||
|
|
||||||
function setStickMode() {
|
|
||||||
stickMode2 = stickModeDisplay.checked;
|
|
||||||
updateSettings();
|
|
||||||
}
|
|
||||||
|
|
||||||
function openOutputFolder() {
|
|
||||||
require("child_process").exec('start "" "' + output + '"');
|
|
||||||
}
|
|
||||||
83
src/renderer.tsx
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
import React from "react";
|
||||||
|
import ReactDOM from "react-dom/client";
|
||||||
|
import Menu from "./components/ui/menu";
|
||||||
|
import MainPage from "./components/ui/mainPage";
|
||||||
|
import SettingsPage from "./components/ui/settingsPage";
|
||||||
|
import RenderingPage from "./components/ui/renderingPage";
|
||||||
|
import RenderFinishPage from "./components/ui/renderFinishPage";
|
||||||
|
import "./index.css";
|
||||||
|
import "./toggle-switchy.css";
|
||||||
|
import { blender, blenderCmd, startBlender } from "./components/blenderController";
|
||||||
|
import {ipcRenderer} from "electron";
|
||||||
|
|
||||||
|
enum Page {
|
||||||
|
Main,
|
||||||
|
Rendering,
|
||||||
|
Settings,
|
||||||
|
RenderFinish
|
||||||
|
}
|
||||||
|
|
||||||
|
let rendering = false;
|
||||||
|
let currentPage = Page.Main;
|
||||||
|
|
||||||
|
const root = ReactDOM.createRoot(document.getElementById("root") as HTMLElement);
|
||||||
|
function openPage(page:Page) {
|
||||||
|
if(page == Page.Main && rendering) {
|
||||||
|
page = Page.Rendering;
|
||||||
|
}
|
||||||
|
currentPage = page;
|
||||||
|
|
||||||
|
root.render(
|
||||||
|
<React.StrictMode>
|
||||||
|
<Menu page={page}/>
|
||||||
|
{(page === Page.Main)? <MainPage/> : null}
|
||||||
|
{(page === Page.Settings)? <SettingsPage/> : null}
|
||||||
|
{(page === Page.Rendering)? <RenderingPage/> : null}
|
||||||
|
{(page === Page.RenderFinish)? <RenderFinishPage/> : null}
|
||||||
|
</React.StrictMode>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
rendering = value;
|
||||||
|
if(value) {
|
||||||
|
if(currentPage === Page.Main) {
|
||||||
|
openPage(Page.Rendering);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if(currentPage === Page.Rendering) {
|
||||||
|
openPage(Page.Main);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setProgress(value?:number) {
|
||||||
|
if(value === undefined) {
|
||||||
|
ipcRenderer.send("setProgress", -1);
|
||||||
|
} else {
|
||||||
|
ipcRenderer.send("setProgress", value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
openPage,
|
||||||
|
Page,
|
||||||
|
pageSetRendering,
|
||||||
|
setProgress
|
||||||
|
}
|
||||||
@@ -17,9 +17,9 @@ Releases: https://github.com/adamculpepper/toggle-switchy/releases
|
|||||||
.toggle-switchy > input + .toggle:after {content:'OFF';}
|
.toggle-switchy > input + .toggle:after {content:'OFF';}
|
||||||
.toggle-switchy > input + .toggle > .switch {background:#fff;}
|
.toggle-switchy > input + .toggle > .switch {background:#fff;}
|
||||||
.toggle-switchy > input + .toggle + .label {color:#000;}
|
.toggle-switchy > input + .toggle + .label {color:#000;}
|
||||||
.toggle-switchy > input:checked + .toggle {background:#3498db;}
|
.toggle-switchy > input:checked + .toggle {background:#00c24a;}
|
||||||
.toggle-switchy > input:not(:checked) + .toggle {background:#ccc;}
|
.toggle-switchy > input:not(:checked) + .toggle {background:#ccc;}
|
||||||
.toggle-switchy > input:checked + .toggle > .switch {border:3px solid #3498db;}
|
.toggle-switchy > input:checked + .toggle > .switch {border:3px solid #00c24a;}
|
||||||
.toggle-switchy > input:not(:checked) + .toggle > .switch {border:3px solid #ccc;}
|
.toggle-switchy > input:not(:checked) + .toggle > .switch {border:3px solid #ccc;}
|
||||||
.toggle-switchy > input:focus + .toggle,
|
.toggle-switchy > input:focus + .toggle,
|
||||||
.toggle-switchy > input:active + .toggle {box-shadow:0 0 5px 3px rgba(0, 119, 200, 0.50);}
|
.toggle-switchy > input:active + .toggle {box-shadow:0 0 5px 3px rgba(0, 119, 200, 0.50);}
|
||||||
@@ -149,7 +149,7 @@ CORE STYLES ABOVE - NO TOUCHY
|
|||||||
/* Text: 1/2 */
|
/* Text: 1/2 */
|
||||||
.toggle-switchy[data-text='12'] > input + .toggle:before {content:'2';}
|
.toggle-switchy[data-text='12'] > input + .toggle:before {content:'2';}
|
||||||
.toggle-switchy[data-text='12'] > input + .toggle:after {content:'1';}
|
.toggle-switchy[data-text='12'] > input + .toggle:after {content:'1';}
|
||||||
.toggle-switchy[data-text='12'] > input:checked + .toggle {background:#3498db;}
|
.toggle-switchy[data-text='12'] > input:checked + .toggle {background:#00c24a;}
|
||||||
.toggle-switchy[data-text='12'] > input:checked + .toggle > .switch {border-color:#3498db;}
|
.toggle-switchy[data-text='12'] > input:checked + .toggle > .switch {border-color:#00c24a;}
|
||||||
.toggle-switchy[data-text='12'] > input:not(:checked) + .toggle > .switch {border-color:#3498db;}
|
.toggle-switchy[data-text='12'] > input:not(:checked) + .toggle > .switch {border-color:#00c24a;}
|
||||||
.toggle-switchy[data-text='12'] > input:not(:checked) + .toggle {background:#3498db;}
|
.toggle-switchy[data-text='12'] > input:not(:checked) + .toggle {background:#00c24a;}
|
||||||
110
tsconfig.json
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
/* Visit https://aka.ms/tsconfig to read more about this file */
|
||||||
|
|
||||||
|
/* Projects */
|
||||||
|
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
|
||||||
|
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
|
||||||
|
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
|
||||||
|
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
|
||||||
|
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
|
||||||
|
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
|
||||||
|
|
||||||
|
/* Language and Environment */
|
||||||
|
"target": "es2018", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
|
||||||
|
"lib": [
|
||||||
|
"DOM",
|
||||||
|
"DOM.Iterable",
|
||||||
|
"ESNext"
|
||||||
|
], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
|
||||||
|
"jsx": "react", /* Specify what JSX code is generated. */
|
||||||
|
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
|
||||||
|
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
|
||||||
|
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
|
||||||
|
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
|
||||||
|
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
|
||||||
|
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
|
||||||
|
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
|
||||||
|
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
|
||||||
|
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
|
||||||
|
|
||||||
|
/* Modules */
|
||||||
|
"module": "esnext", /* Specify what module code is generated. */
|
||||||
|
// "rootDir": "./", /* Specify the root folder within your source files. */
|
||||||
|
"moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
|
||||||
|
"baseUrl": ".", /* Specify the base directory to resolve non-relative module names. */
|
||||||
|
"paths": {
|
||||||
|
"*": ["node_modules/*"]
|
||||||
|
}, /* Specify a set of entries that re-map imports to additional lookup locations. */
|
||||||
|
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
|
||||||
|
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
|
||||||
|
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
|
||||||
|
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||||
|
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
|
||||||
|
"resolveJsonModule": true, /* Enable importing .json files. */
|
||||||
|
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
|
||||||
|
|
||||||
|
/* JavaScript Support */
|
||||||
|
"allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
|
||||||
|
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
|
||||||
|
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
|
||||||
|
|
||||||
|
/* Emit */
|
||||||
|
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
|
||||||
|
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
|
||||||
|
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
|
||||||
|
"sourceMap": true, /* Create source map files for emitted JavaScript files. */
|
||||||
|
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
|
||||||
|
"outDir": "dist", /* Specify an output folder for all emitted files. */
|
||||||
|
// "removeComments": true, /* Disable emitting comments. */
|
||||||
|
"noEmit": true, /* Disable emitting files from a compilation. */
|
||||||
|
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
|
||||||
|
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
|
||||||
|
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
|
||||||
|
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
|
||||||
|
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||||
|
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
|
||||||
|
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
|
||||||
|
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
|
||||||
|
// "newLine": "crlf", /* Set the newline character for emitting files. */
|
||||||
|
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
|
||||||
|
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
|
||||||
|
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
|
||||||
|
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
|
||||||
|
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
|
||||||
|
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
|
||||||
|
|
||||||
|
/* Interop Constraints */
|
||||||
|
"isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
|
||||||
|
"allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
|
||||||
|
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
|
||||||
|
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
|
||||||
|
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
|
||||||
|
|
||||||
|
/* Type Checking */
|
||||||
|
"strict": true, /* Enable all strict type-checking options. */
|
||||||
|
"noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
|
||||||
|
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
|
||||||
|
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
|
||||||
|
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
|
||||||
|
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
|
||||||
|
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
|
||||||
|
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
|
||||||
|
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
|
||||||
|
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
|
||||||
|
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
|
||||||
|
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
|
||||||
|
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
|
||||||
|
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
|
||||||
|
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
|
||||||
|
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
|
||||||
|
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
|
||||||
|
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
|
||||||
|
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
|
||||||
|
|
||||||
|
/* Completeness */
|
||||||
|
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
||||||
|
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
||||||
|
},
|
||||||
|
"include": ["src/**/*", "js"]
|
||||||
|
}
|
||||||