first
This commit is contained in:
454
.gitignore
vendored
Normal file
454
.gitignore
vendored
Normal file
@@ -0,0 +1,454 @@
|
|||||||
|
## Ignore Visual Studio temporary files, build results, and
|
||||||
|
## files generated by popular Visual Studio add-ons.
|
||||||
|
##
|
||||||
|
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
|
||||||
|
|
||||||
|
# User-specific files
|
||||||
|
*.rsuser
|
||||||
|
*.suo
|
||||||
|
*.user
|
||||||
|
*.userosscache
|
||||||
|
*.sln.docstates
|
||||||
|
|
||||||
|
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||||
|
*.userprefs
|
||||||
|
|
||||||
|
# Mono auto generated files
|
||||||
|
mono_crash.*
|
||||||
|
|
||||||
|
# Build results
|
||||||
|
[Dd]ebug/
|
||||||
|
[Dd]ebugPublic/
|
||||||
|
[Rr]elease/
|
||||||
|
[Rr]eleases/
|
||||||
|
x64/
|
||||||
|
x86/
|
||||||
|
[Ww][Ii][Nn]32/
|
||||||
|
[Aa][Rr][Mm]/
|
||||||
|
[Aa][Rr][Mm]64/
|
||||||
|
bld/
|
||||||
|
[Bb]in/
|
||||||
|
[Oo]bj/
|
||||||
|
[Ll]og/
|
||||||
|
[Ll]ogs/
|
||||||
|
|
||||||
|
# Visual Studio 2015/2017 cache/options directory
|
||||||
|
.vs/
|
||||||
|
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||||
|
#wwwroot/
|
||||||
|
|
||||||
|
# Visual Studio 2017 auto generated files
|
||||||
|
Generated\ Files/
|
||||||
|
|
||||||
|
# MSTest test Results
|
||||||
|
[Tt]est[Rr]esult*/
|
||||||
|
[Bb]uild[Ll]og.*
|
||||||
|
|
||||||
|
# NUnit
|
||||||
|
*.VisualState.xml
|
||||||
|
TestResult.xml
|
||||||
|
nunit-*.xml
|
||||||
|
|
||||||
|
# Build Results of an ATL Project
|
||||||
|
[Dd]ebugPS/
|
||||||
|
[Rr]eleasePS/
|
||||||
|
dlldata.c
|
||||||
|
|
||||||
|
# Benchmark Results
|
||||||
|
BenchmarkDotNet.Artifacts/
|
||||||
|
|
||||||
|
# .NET Core
|
||||||
|
project.lock.json
|
||||||
|
project.fragment.lock.json
|
||||||
|
artifacts/
|
||||||
|
|
||||||
|
# Tye
|
||||||
|
.tye/
|
||||||
|
|
||||||
|
# ASP.NET Scaffolding
|
||||||
|
ScaffoldingReadMe.txt
|
||||||
|
|
||||||
|
# StyleCop
|
||||||
|
StyleCopReport.xml
|
||||||
|
|
||||||
|
# Files built by Visual Studio
|
||||||
|
*_i.c
|
||||||
|
*_p.c
|
||||||
|
*_h.h
|
||||||
|
*.ilk
|
||||||
|
*.meta
|
||||||
|
*.obj
|
||||||
|
*.iobj
|
||||||
|
*.pch
|
||||||
|
*.pdb
|
||||||
|
*.ipdb
|
||||||
|
*.pgc
|
||||||
|
*.pgd
|
||||||
|
*.rsp
|
||||||
|
*.sbr
|
||||||
|
*.tlb
|
||||||
|
*.tli
|
||||||
|
*.tlh
|
||||||
|
*.tmp
|
||||||
|
*.tmp_proj
|
||||||
|
*_wpftmp.csproj
|
||||||
|
*.log
|
||||||
|
*.vspscc
|
||||||
|
*.vssscc
|
||||||
|
.builds
|
||||||
|
*.pidb
|
||||||
|
*.svclog
|
||||||
|
*.scc
|
||||||
|
|
||||||
|
# Chutzpah Test files
|
||||||
|
_Chutzpah*
|
||||||
|
|
||||||
|
# Visual C++ cache files
|
||||||
|
ipch/
|
||||||
|
*.aps
|
||||||
|
*.ncb
|
||||||
|
*.opendb
|
||||||
|
*.opensdf
|
||||||
|
*.sdf
|
||||||
|
*.cachefile
|
||||||
|
*.VC.db
|
||||||
|
*.VC.VC.opendb
|
||||||
|
|
||||||
|
# Visual Studio profiler
|
||||||
|
*.psess
|
||||||
|
*.vsp
|
||||||
|
*.vspx
|
||||||
|
*.sap
|
||||||
|
|
||||||
|
# Visual Studio Trace Files
|
||||||
|
*.e2e
|
||||||
|
|
||||||
|
# TFS 2012 Local Workspace
|
||||||
|
$tf/
|
||||||
|
|
||||||
|
# Guidance Automation Toolkit
|
||||||
|
*.gpState
|
||||||
|
|
||||||
|
# ReSharper is a .NET coding add-in
|
||||||
|
_ReSharper*/
|
||||||
|
*.[Rr]e[Ss]harper
|
||||||
|
*.DotSettings.user
|
||||||
|
|
||||||
|
# TeamCity is a build add-in
|
||||||
|
_TeamCity*
|
||||||
|
|
||||||
|
# DotCover is a Code Coverage Tool
|
||||||
|
*.dotCover
|
||||||
|
|
||||||
|
# AxoCover is a Code Coverage Tool
|
||||||
|
.axoCover/*
|
||||||
|
!.axoCover/settings.json
|
||||||
|
|
||||||
|
# Coverlet is a free, cross platform Code Coverage Tool
|
||||||
|
coverage*.json
|
||||||
|
coverage*.xml
|
||||||
|
coverage*.info
|
||||||
|
|
||||||
|
# Visual Studio code coverage results
|
||||||
|
*.coverage
|
||||||
|
*.coveragexml
|
||||||
|
|
||||||
|
# NCrunch
|
||||||
|
_NCrunch_*
|
||||||
|
.*crunch*.local.xml
|
||||||
|
nCrunchTemp_*
|
||||||
|
|
||||||
|
# MightyMoose
|
||||||
|
*.mm.*
|
||||||
|
AutoTest.Net/
|
||||||
|
|
||||||
|
# Web workbench (sass)
|
||||||
|
.sass-cache/
|
||||||
|
|
||||||
|
# Installshield output folder
|
||||||
|
[Ee]xpress/
|
||||||
|
|
||||||
|
# DocProject is a documentation generator add-in
|
||||||
|
DocProject/buildhelp/
|
||||||
|
DocProject/Help/*.HxT
|
||||||
|
DocProject/Help/*.HxC
|
||||||
|
DocProject/Help/*.hhc
|
||||||
|
DocProject/Help/*.hhk
|
||||||
|
DocProject/Help/*.hhp
|
||||||
|
DocProject/Help/Html2
|
||||||
|
DocProject/Help/html
|
||||||
|
|
||||||
|
# Click-Once directory
|
||||||
|
publish/
|
||||||
|
|
||||||
|
# Publish Web Output
|
||||||
|
*.[Pp]ublish.xml
|
||||||
|
*.azurePubxml
|
||||||
|
# Note: Comment the next line if you want to checkin your web deploy settings,
|
||||||
|
# but database connection strings (with potential passwords) will be unencrypted
|
||||||
|
*.pubxml
|
||||||
|
*.publishproj
|
||||||
|
|
||||||
|
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||||
|
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||||
|
# in these scripts will be unencrypted
|
||||||
|
PublishScripts/
|
||||||
|
|
||||||
|
# NuGet Packages
|
||||||
|
*.nupkg
|
||||||
|
# NuGet Symbol Packages
|
||||||
|
*.snupkg
|
||||||
|
# The packages folder can be ignored because of Package Restore
|
||||||
|
**/[Pp]ackages/*
|
||||||
|
# except build/, which is used as an MSBuild target.
|
||||||
|
!**/[Pp]ackages/build/
|
||||||
|
# Uncomment if necessary however generally it will be regenerated when needed
|
||||||
|
#!**/[Pp]ackages/repositories.config
|
||||||
|
# NuGet v3's project.json files produces more ignorable files
|
||||||
|
*.nuget.props
|
||||||
|
*.nuget.targets
|
||||||
|
|
||||||
|
# Microsoft Azure Build Output
|
||||||
|
csx/
|
||||||
|
*.build.csdef
|
||||||
|
|
||||||
|
# Microsoft Azure Emulator
|
||||||
|
ecf/
|
||||||
|
rcf/
|
||||||
|
|
||||||
|
# Windows Store app package directories and files
|
||||||
|
AppPackages/
|
||||||
|
BundleArtifacts/
|
||||||
|
Package.StoreAssociation.xml
|
||||||
|
_pkginfo.txt
|
||||||
|
*.appx
|
||||||
|
*.appxbundle
|
||||||
|
*.appxupload
|
||||||
|
|
||||||
|
# Visual Studio cache files
|
||||||
|
# files ending in .cache can be ignored
|
||||||
|
*.[Cc]ache
|
||||||
|
# but keep track of directories ending in .cache
|
||||||
|
!?*.[Cc]ache/
|
||||||
|
|
||||||
|
# Others
|
||||||
|
ClientBin/
|
||||||
|
~$*
|
||||||
|
*~
|
||||||
|
*.dbmdl
|
||||||
|
*.dbproj.schemaview
|
||||||
|
*.jfm
|
||||||
|
*.pfx
|
||||||
|
*.publishsettings
|
||||||
|
orleans.codegen.cs
|
||||||
|
|
||||||
|
# Including strong name files can present a security risk
|
||||||
|
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
|
||||||
|
#*.snk
|
||||||
|
|
||||||
|
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
||||||
|
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||||
|
#bower_components/
|
||||||
|
|
||||||
|
# RIA/Silverlight projects
|
||||||
|
Generated_Code/
|
||||||
|
|
||||||
|
# Backup & report files from converting an old project file
|
||||||
|
# to a newer Visual Studio version. Backup files are not needed,
|
||||||
|
# because we have git ;-)
|
||||||
|
_UpgradeReport_Files/
|
||||||
|
Backup*/
|
||||||
|
UpgradeLog*.XML
|
||||||
|
UpgradeLog*.htm
|
||||||
|
ServiceFabricBackup/
|
||||||
|
*.rptproj.bak
|
||||||
|
|
||||||
|
# SQL Server files
|
||||||
|
*.mdf
|
||||||
|
*.ldf
|
||||||
|
*.ndf
|
||||||
|
|
||||||
|
# Business Intelligence projects
|
||||||
|
*.rdl.data
|
||||||
|
*.bim.layout
|
||||||
|
*.bim_*.settings
|
||||||
|
*.rptproj.rsuser
|
||||||
|
*- [Bb]ackup.rdl
|
||||||
|
*- [Bb]ackup ([0-9]).rdl
|
||||||
|
*- [Bb]ackup ([0-9][0-9]).rdl
|
||||||
|
|
||||||
|
# Microsoft Fakes
|
||||||
|
FakesAssemblies/
|
||||||
|
|
||||||
|
# GhostDoc plugin setting file
|
||||||
|
*.GhostDoc.xml
|
||||||
|
|
||||||
|
# Node.js Tools for Visual Studio
|
||||||
|
.ntvs_analysis.dat
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
# Visual Studio 6 build log
|
||||||
|
*.plg
|
||||||
|
|
||||||
|
# Visual Studio 6 workspace options file
|
||||||
|
*.opt
|
||||||
|
|
||||||
|
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
||||||
|
*.vbw
|
||||||
|
|
||||||
|
# Visual Studio LightSwitch build output
|
||||||
|
**/*.HTMLClient/GeneratedArtifacts
|
||||||
|
**/*.DesktopClient/GeneratedArtifacts
|
||||||
|
**/*.DesktopClient/ModelManifest.xml
|
||||||
|
**/*.Server/GeneratedArtifacts
|
||||||
|
**/*.Server/ModelManifest.xml
|
||||||
|
_Pvt_Extensions
|
||||||
|
|
||||||
|
# Paket dependency manager
|
||||||
|
.paket/paket.exe
|
||||||
|
paket-files/
|
||||||
|
|
||||||
|
# FAKE - F# Make
|
||||||
|
.fake/
|
||||||
|
|
||||||
|
# CodeRush personal settings
|
||||||
|
.cr/personal
|
||||||
|
|
||||||
|
# Python Tools for Visual Studio (PTVS)
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
|
||||||
|
# Cake - Uncomment if you are using it
|
||||||
|
# tools/**
|
||||||
|
# !tools/packages.config
|
||||||
|
|
||||||
|
# Tabs Studio
|
||||||
|
*.tss
|
||||||
|
|
||||||
|
# Telerik's JustMock configuration file
|
||||||
|
*.jmconfig
|
||||||
|
|
||||||
|
# BizTalk build output
|
||||||
|
*.btp.cs
|
||||||
|
*.btm.cs
|
||||||
|
*.odx.cs
|
||||||
|
*.xsd.cs
|
||||||
|
|
||||||
|
# OpenCover UI analysis results
|
||||||
|
OpenCover/
|
||||||
|
|
||||||
|
# Azure Stream Analytics local run output
|
||||||
|
ASALocalRun/
|
||||||
|
|
||||||
|
# MSBuild Binary and Structured Log
|
||||||
|
*.binlog
|
||||||
|
|
||||||
|
# NVidia Nsight GPU debugger configuration file
|
||||||
|
*.nvuser
|
||||||
|
|
||||||
|
# MFractors (Xamarin productivity tool) working folder
|
||||||
|
.mfractor/
|
||||||
|
|
||||||
|
# Local History for Visual Studio
|
||||||
|
.localhistory/
|
||||||
|
|
||||||
|
# BeatPulse healthcheck temp database
|
||||||
|
healthchecksdb
|
||||||
|
|
||||||
|
# Backup folder for Package Reference Convert tool in Visual Studio 2017
|
||||||
|
MigrationBackup/
|
||||||
|
|
||||||
|
# Ionide (cross platform F# VS Code tools) working folder
|
||||||
|
.ionide/
|
||||||
|
|
||||||
|
# Fody - auto-generated XML schema
|
||||||
|
FodyWeavers.xsd
|
||||||
|
|
||||||
|
##
|
||||||
|
## Visual studio for Mac
|
||||||
|
##
|
||||||
|
|
||||||
|
|
||||||
|
# globs
|
||||||
|
Makefile.in
|
||||||
|
*.userprefs
|
||||||
|
*.usertasks
|
||||||
|
config.make
|
||||||
|
config.status
|
||||||
|
aclocal.m4
|
||||||
|
install-sh
|
||||||
|
autom4te.cache/
|
||||||
|
*.tar.gz
|
||||||
|
tarballs/
|
||||||
|
test-results/
|
||||||
|
|
||||||
|
# Mac bundle stuff
|
||||||
|
*.dmg
|
||||||
|
*.app
|
||||||
|
|
||||||
|
# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore
|
||||||
|
# General
|
||||||
|
.DS_Store
|
||||||
|
.AppleDouble
|
||||||
|
.LSOverride
|
||||||
|
|
||||||
|
# Icon must end with two \r
|
||||||
|
Icon
|
||||||
|
|
||||||
|
|
||||||
|
# Thumbnails
|
||||||
|
._*
|
||||||
|
|
||||||
|
# Files that might appear in the root of a volume
|
||||||
|
.DocumentRevisions-V100
|
||||||
|
.fseventsd
|
||||||
|
.Spotlight-V100
|
||||||
|
.TemporaryItems
|
||||||
|
.Trashes
|
||||||
|
.VolumeIcon.icns
|
||||||
|
.com.apple.timemachine.donotpresent
|
||||||
|
|
||||||
|
# Directories potentially created on remote AFP share
|
||||||
|
.AppleDB
|
||||||
|
.AppleDesktop
|
||||||
|
Network Trash Folder
|
||||||
|
Temporary Items
|
||||||
|
.apdisk
|
||||||
|
|
||||||
|
# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore
|
||||||
|
# Windows thumbnail cache files
|
||||||
|
Thumbs.db
|
||||||
|
ehthumbs.db
|
||||||
|
ehthumbs_vista.db
|
||||||
|
|
||||||
|
# Dump file
|
||||||
|
*.stackdump
|
||||||
|
|
||||||
|
# Folder config file
|
||||||
|
[Dd]esktop.ini
|
||||||
|
|
||||||
|
# Recycle Bin used on file shares
|
||||||
|
$RECYCLE.BIN/
|
||||||
|
|
||||||
|
# Windows Installer files
|
||||||
|
*.cab
|
||||||
|
*.msi
|
||||||
|
*.msix
|
||||||
|
*.msm
|
||||||
|
*.msp
|
||||||
|
|
||||||
|
# Windows shortcuts
|
||||||
|
*.lnk
|
||||||
|
|
||||||
|
# JetBrains Rider
|
||||||
|
.idea/
|
||||||
|
*.sln.iml
|
||||||
|
|
||||||
|
##
|
||||||
|
## Visual Studio Code
|
||||||
|
##
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/settings.json
|
||||||
|
!.vscode/tasks.json
|
||||||
|
!.vscode/launch.json
|
||||||
|
!.vscode/extensions.json
|
13
.idea/.idea.TorchRemote/.idea/.gitignore
generated
vendored
Normal file
13
.idea/.idea.TorchRemote/.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# Rider ignored files
|
||||||
|
/modules.xml
|
||||||
|
/projectSettingsUpdater.xml
|
||||||
|
/.idea.TorchRemote.iml
|
||||||
|
/contentModel.xml
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
/httpRequests/
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
17
.idea/.idea.TorchRemote/.idea/avalonia.xml
generated
Normal file
17
.idea/.idea.TorchRemote/.idea/avalonia.xml
generated
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="AvaloniaProject">
|
||||||
|
<option name="projectPerEditor">
|
||||||
|
<map>
|
||||||
|
<entry key="TorchRemote/App.axaml" value="TorchRemote/TorchRemote.csproj" />
|
||||||
|
<entry key="TorchRemote/Views/MainWindow.axaml" value="TorchRemote/TorchRemote.csproj" />
|
||||||
|
<entry key="TorchRemote/Views/RemoteServerView.axaml" value="TorchRemote/TorchRemote.csproj" />
|
||||||
|
<entry key="TorchRemote/Views/Server/ChatView.axaml" value="TorchRemote/TorchRemote.csproj" />
|
||||||
|
<entry key="TorchRemote/Views/Server/DashboardView.axaml" value="TorchRemote/TorchRemote.csproj" />
|
||||||
|
<entry key="TorchRemote/Views/Server/PlayersView.axaml" value="TorchRemote/TorchRemote.csproj" />
|
||||||
|
<entry key="TorchRemote/Views/Server/ServerConfigView.axaml" value="TorchRemote/TorchRemote.csproj" />
|
||||||
|
<entry key="TorchRemote/Views/Server/SettingsView.axaml" value="TorchRemote/TorchRemote.csproj" />
|
||||||
|
</map>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</project>
|
4
.idea/.idea.TorchRemote/.idea/encodings.xml
generated
Normal file
4
.idea/.idea.TorchRemote/.idea/encodings.xml
generated
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="Encoding" addBOMForNewFiles="with BOM under Windows, with no BOM otherwise" />
|
||||||
|
</project>
|
8
.idea/.idea.TorchRemote/.idea/indexLayout.xml
generated
Normal file
8
.idea/.idea.TorchRemote/.idea/indexLayout.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="UserContentModel">
|
||||||
|
<attachedFolders />
|
||||||
|
<explicitIncludes />
|
||||||
|
<explicitExcludes />
|
||||||
|
</component>
|
||||||
|
</project>
|
6
.idea/.idea.TorchRemote/.idea/misc.xml
generated
Normal file
6
.idea/.idea.TorchRemote/.idea/misc.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="com.jetbrains.rider.android.RiderAndroidMiscFileCreationComponent">
|
||||||
|
<option name="ENSURE_MISC_FILE_EXISTS" value="true" />
|
||||||
|
</component>
|
||||||
|
</project>
|
6
.idea/.idea.TorchRemote/.idea/vcs.xml
generated
Normal file
6
.idea/.idea.TorchRemote/.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
3
TorchRemote.Models/Requests/ChatCommandRequest.cs
Normal file
3
TorchRemote.Models/Requests/ChatCommandRequest.cs
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
namespace TorchRemote.Models.Requests;
|
||||||
|
|
||||||
|
public record ChatCommandRequest(string Command);
|
4
TorchRemote.Models/Requests/ChatMessageRequest.cs
Normal file
4
TorchRemote.Models/Requests/ChatMessageRequest.cs
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
using TorchRemote.Models.Shared;
|
||||||
|
namespace TorchRemote.Models.Requests;
|
||||||
|
|
||||||
|
public record ChatMessageRequest(string Author, string Message, ChatChannel Channel, long? TargetId = null);
|
3
TorchRemote.Models/Requests/StopServerRequest.cs
Normal file
3
TorchRemote.Models/Requests/StopServerRequest.cs
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
namespace TorchRemote.Models.Requests;
|
||||||
|
|
||||||
|
public record StopServerRequest(bool Save = true);
|
3
TorchRemote.Models/Responses/ChatCommandResponse.cs
Normal file
3
TorchRemote.Models/Responses/ChatCommandResponse.cs
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
namespace TorchRemote.Models.Responses;
|
||||||
|
|
||||||
|
public record ChatCommandResponse(Guid Id, string Author, string Message) : ChatResponseBase;
|
4
TorchRemote.Models/Responses/ChatMessageResponse.cs
Normal file
4
TorchRemote.Models/Responses/ChatMessageResponse.cs
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
using TorchRemote.Models.Shared;
|
||||||
|
namespace TorchRemote.Models.Responses;
|
||||||
|
|
||||||
|
public record ChatMessageResponse(string AuthorName, ulong? Author, ChatChannel Channel, string Message) : ChatResponseBase;
|
6
TorchRemote.Models/Responses/ChatResponseBase.cs
Normal file
6
TorchRemote.Models/Responses/ChatResponseBase.cs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
namespace TorchRemote.Models.Responses;
|
||||||
|
|
||||||
|
[JsonDerivedType(typeof(ChatCommandResponse), "command")]
|
||||||
|
[JsonDerivedType(typeof(ChatMessageResponse), "message")]
|
||||||
|
public record ChatResponseBase();
|
13
TorchRemote.Models/Responses/LogLineResponse.cs
Normal file
13
TorchRemote.Models/Responses/LogLineResponse.cs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
namespace TorchRemote.Models.Responses;
|
||||||
|
|
||||||
|
public record struct LogLineResponse(DateTime Time, LogLineLevel Level, string Logger, string Message);
|
||||||
|
|
||||||
|
public enum LogLineLevel
|
||||||
|
{
|
||||||
|
Trace,
|
||||||
|
Debug,
|
||||||
|
Info,
|
||||||
|
Warning,
|
||||||
|
Error,
|
||||||
|
Fatal
|
||||||
|
}
|
15
TorchRemote.Models/Responses/ServerStatusResponse.cs
Normal file
15
TorchRemote.Models/Responses/ServerStatusResponse.cs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
namespace TorchRemote.Models.Responses;
|
||||||
|
|
||||||
|
public record ServerStatusResponse(double SimSpeed, int MemberCount, TimeSpan Uptime, ServerStatus Status);
|
||||||
|
|
||||||
|
public enum ServerStatus
|
||||||
|
{
|
||||||
|
/// <summary>The server is not running.</summary>
|
||||||
|
Stopped,
|
||||||
|
/// <summary>The server is starting/loading the session.</summary>
|
||||||
|
Starting,
|
||||||
|
/// <summary>The server is running.</summary>
|
||||||
|
Running,
|
||||||
|
/// <summary>The server encountered an error.</summary>
|
||||||
|
Error,
|
||||||
|
}
|
15
TorchRemote.Models/Responses/SettingInfoResponse.cs
Normal file
15
TorchRemote.Models/Responses/SettingInfoResponse.cs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
namespace TorchRemote.Models.Responses;
|
||||||
|
|
||||||
|
public record SettingInfoResponse(string Name, ICollection<SettingPropertyInfo> Properties);
|
||||||
|
public record SettingPropertyInfo(string Name, string? Description, int? Order, Guid Type);
|
||||||
|
|
||||||
|
public struct SettingPropertyTypeEnum
|
||||||
|
{
|
||||||
|
public static readonly Guid Integer = new("95c0d25b-e44d-4505-9549-48ee9c14bce8");
|
||||||
|
public static readonly Guid Boolean = new("028ef347-1fc3-486a-b70b-3d3b1dcdb538");
|
||||||
|
public static readonly Guid Number = new("009ced71-4a69-4af0-abb9-ec3339fffce0");
|
||||||
|
public static readonly Guid String = new("22dbed1b-b976-44b4-98c9-d1b742a93f0c");
|
||||||
|
public static readonly Guid DateTime = new("f0978b29-9da9-4289-85c9-41d5b92056e8");
|
||||||
|
public static readonly Guid TimeSpan = new("7a2bebf1-78f5-4e4e-8d83-18914dbee55c");
|
||||||
|
public static readonly Guid Color = new("99c74632-0fa9-469b-ba05-825ba21a017b");
|
||||||
|
}
|
3
TorchRemote.Models/Responses/WorldResponse.cs
Normal file
3
TorchRemote.Models/Responses/WorldResponse.cs
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
namespace TorchRemote.Models.Responses;
|
||||||
|
|
||||||
|
public record WorldResponse(string Name, long SizeKb);
|
9
TorchRemote.Models/Shared/ChatChannel.cs
Normal file
9
TorchRemote.Models/Shared/ChatChannel.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
namespace TorchRemote.Models.Shared;
|
||||||
|
|
||||||
|
public enum ChatChannel
|
||||||
|
{
|
||||||
|
Global,
|
||||||
|
GlobalScripted,
|
||||||
|
Faction,
|
||||||
|
Private
|
||||||
|
}
|
3
TorchRemote.Models/Shared/IpAddress.cs
Normal file
3
TorchRemote.Models/Shared/IpAddress.cs
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
namespace TorchRemote.Models.Shared;
|
||||||
|
|
||||||
|
public record IpAddress(string Ip, int Port);
|
9
TorchRemote.Models/Shared/ServerSettings.cs
Normal file
9
TorchRemote.Models/Shared/ServerSettings.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
namespace TorchRemote.Models.Shared;
|
||||||
|
|
||||||
|
public record ServerSettings(
|
||||||
|
string ServerName,
|
||||||
|
string MapName,
|
||||||
|
string ServerDescription,
|
||||||
|
short MemberLimit,
|
||||||
|
IpAddress ListenEndPoint
|
||||||
|
);
|
13
TorchRemote.Models/TorchRemote.Models.csproj
Normal file
13
TorchRemote.Models/TorchRemote.Models.csproj
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>netstandard2.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<LangVersion>10</LangVersion>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="System.Text.Json" Version="7.0.0-preview.6.22324.4" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
5
TorchRemote.Models/Trash/IsExternalInit.cs
Normal file
5
TorchRemote.Models/Trash/IsExternalInit.cs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
[assembly: InternalsVisibleTo("TorchRemote.Plugin")]
|
||||||
|
// ReSharper disable once CheckNamespace
|
||||||
|
namespace System.Runtime.CompilerServices;
|
||||||
|
internal class IsExternalInit { }
|
17
TorchRemote.Plugin/Config.cs
Normal file
17
TorchRemote.Plugin/Config.cs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
using Torch;
|
||||||
|
using Torch.Views;
|
||||||
|
|
||||||
|
namespace TorchRemote.Plugin;
|
||||||
|
|
||||||
|
public class Config : ViewModel
|
||||||
|
{
|
||||||
|
[Display(Name = "Web Server Config", Description = "Basic configuration for serving web api.")]
|
||||||
|
public ListenerConfig Listener { get; set; } = new();
|
||||||
|
public string SecurityKey { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ListenerConfig : ViewModel
|
||||||
|
{
|
||||||
|
[Display(Name = "Url Prefix", Description = "Root url for all requests. If you want access server from remote replace + with your public ip or domain.")]
|
||||||
|
public string UrlPrefix { get; set; } = "http://+:80/";
|
||||||
|
}
|
118
TorchRemote.Plugin/Controllers/ChatController.cs
Normal file
118
TorchRemote.Plugin/Controllers/ChatController.cs
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
using System.Net;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using EmbedIO;
|
||||||
|
using EmbedIO.Routing;
|
||||||
|
using EmbedIO.WebApi;
|
||||||
|
using EmbedIO.WebSockets;
|
||||||
|
using Sandbox.Engine.Multiplayer;
|
||||||
|
using Sandbox.Game.Multiplayer;
|
||||||
|
using Sandbox.Game.World;
|
||||||
|
using Torch.API;
|
||||||
|
using Torch.API.Managers;
|
||||||
|
using Torch.API.Plugins;
|
||||||
|
using Torch.Commands;
|
||||||
|
using Torch.Managers;
|
||||||
|
using Torch.Utils;
|
||||||
|
using TorchRemote.Models.Requests;
|
||||||
|
using TorchRemote.Models.Responses;
|
||||||
|
using TorchRemote.Models.Shared;
|
||||||
|
using TorchRemote.Plugin.Modules;
|
||||||
|
using TorchRemote.Plugin.Utils;
|
||||||
|
using VRage.Network;
|
||||||
|
namespace TorchRemote.Plugin.Controllers;
|
||||||
|
|
||||||
|
public class ChatController : WebApiController
|
||||||
|
{
|
||||||
|
private const string RootPath = "/chat";
|
||||||
|
|
||||||
|
[ReflectedMethodInfo(typeof(MyMultiplayerBase), "OnChatMessageReceived_BroadcastExcept")]
|
||||||
|
private static readonly MethodInfo BroadcastExceptMethod = null!;
|
||||||
|
[ReflectedMethodInfo(typeof(MyMultiplayerBase), "OnChatMessageReceived_SingleTarget")]
|
||||||
|
private static readonly MethodInfo SingleTargetMethod = null!;
|
||||||
|
|
||||||
|
[Route(HttpVerbs.Post, $"{RootPath}/message")]
|
||||||
|
public void SendMessage([JsonData] ChatMessageRequest request)
|
||||||
|
{
|
||||||
|
if (MyMultiplayer.Static is null)
|
||||||
|
throw new HttpException(HttpStatusCode.ServiceUnavailable);
|
||||||
|
|
||||||
|
var msg = new ChatMsg
|
||||||
|
{
|
||||||
|
CustomAuthorName = request.Author,
|
||||||
|
Text = request.Message,
|
||||||
|
Channel = (byte)request.Channel,
|
||||||
|
TargetId = request.TargetId.GetValueOrDefault()
|
||||||
|
};
|
||||||
|
|
||||||
|
switch (request.Channel)
|
||||||
|
{
|
||||||
|
case ChatChannel.Global:
|
||||||
|
case ChatChannel.GlobalScripted when request.TargetId is null:
|
||||||
|
NetworkManager.RaiseStaticEvent(BroadcastExceptMethod, msg);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ChatChannel.Private when request.TargetId is not null:
|
||||||
|
case ChatChannel.GlobalScripted:
|
||||||
|
var steamId = Sync.Players.TryGetSteamId(request.TargetId.Value);
|
||||||
|
if (steamId == 0)
|
||||||
|
throw HttpException.NotFound($"Unable to find player with identity id {request.TargetId.Value}", request.TargetId.Value);
|
||||||
|
|
||||||
|
NetworkManager.RaiseStaticEvent(SingleTargetMethod, msg, new(steamId));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ChatChannel.Faction when request.TargetId is not null:
|
||||||
|
var faction = MySession.Static.Factions.TryGetFactionById(request.TargetId.Value);
|
||||||
|
if (faction is null)
|
||||||
|
throw HttpException.NotFound($"Unable to find faction with id {request.TargetId.Value}", request.TargetId.Value);
|
||||||
|
|
||||||
|
foreach (var playerId in faction.Members.Keys.Where(Sync.Players.IsPlayerOnline))
|
||||||
|
{
|
||||||
|
NetworkManager.RaiseStaticEvent(SingleTargetMethod, msg, new(Sync.Players.TryGetSteamId(playerId)));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw HttpException.BadRequest("Invalid channel and targetId combination");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Statics.Torch.CurrentSession?.Managers.GetManager<IChatManagerServer>() is { } manager)
|
||||||
|
manager.DisplayMessageOnSelf(request.Author, request.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Route(HttpVerbs.Post, $"{RootPath}/command")]
|
||||||
|
public async Task<Guid> InvokeCommand([JsonData] ChatCommandRequest request)
|
||||||
|
{
|
||||||
|
if (Statics.CommandManager is null)
|
||||||
|
throw new HttpException(HttpStatusCode.ServiceUnavailable);
|
||||||
|
|
||||||
|
if (Statics.CommandManager.Commands.GetCommand(request.Command, out var argText) is not { } command)
|
||||||
|
throw HttpException.NotFound($"Unable to find command {request.Command}", request.Command);
|
||||||
|
|
||||||
|
var argsList = Regex.Matches(argText, "(\"[^\"]+\"|\\S+)").Cast<Match>().Select(x => x.ToString().Replace("\"", "")).ToList();
|
||||||
|
|
||||||
|
var id = new Guid();
|
||||||
|
var context = new WebSocketCommandContext(Statics.Torch, command.Plugin, argText, argsList, Statics.ChatModule, id);
|
||||||
|
|
||||||
|
if (await Statics.Torch.InvokeAsync(() => command.TryInvoke(context)))
|
||||||
|
return id;
|
||||||
|
|
||||||
|
throw HttpException.BadRequest("Invalid syntax", request.Command);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class WebSocketCommandContext : CommandContext
|
||||||
|
{
|
||||||
|
private readonly ChatModule _module;
|
||||||
|
private readonly Guid _id;
|
||||||
|
public WebSocketCommandContext(ITorchBase torch, ITorchPlugin plugin, string rawArgs, List<string> args, ChatModule module, Guid id) : base(torch, plugin, Sync.MyId, rawArgs, args)
|
||||||
|
{
|
||||||
|
_module = module;
|
||||||
|
_id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Respond(string message, string? sender = null, string? font = null)
|
||||||
|
{
|
||||||
|
_module.SendChatResponse(new ChatCommandResponse(_id, sender ?? Torch.Config.ChatName, message));
|
||||||
|
}
|
||||||
|
}
|
76
TorchRemote.Plugin/Controllers/ServerController.cs
Normal file
76
TorchRemote.Plugin/Controllers/ServerController.cs
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
using EmbedIO;
|
||||||
|
using EmbedIO.Routing;
|
||||||
|
using EmbedIO.WebApi;
|
||||||
|
using Sandbox.Game.Multiplayer;
|
||||||
|
using Sandbox.Game.World;
|
||||||
|
using Torch.API.Session;
|
||||||
|
using TorchRemote.Models.Requests;
|
||||||
|
using TorchRemote.Models.Responses;
|
||||||
|
using TorchRemote.Models.Shared;
|
||||||
|
using TorchRemote.Plugin.Utils;
|
||||||
|
|
||||||
|
namespace TorchRemote.Plugin.Controllers;
|
||||||
|
|
||||||
|
public class ServerController : WebApiController
|
||||||
|
{
|
||||||
|
private const string RootPath = "/server";
|
||||||
|
|
||||||
|
[Route(HttpVerbs.Get, $"{RootPath}/status")]
|
||||||
|
public ServerStatusResponse GetStatus()
|
||||||
|
{
|
||||||
|
return new(Math.Round(Sync.ServerSimulationRatio, 2),
|
||||||
|
MySession.Static?.Players?.GetOnlinePlayerCount() ?? 0,
|
||||||
|
Statics.Torch.ElapsedPlayTime,
|
||||||
|
(ServerStatus)Statics.Torch.State);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Route(HttpVerbs.Post, $"{RootPath}/start")]
|
||||||
|
public void Start()
|
||||||
|
{
|
||||||
|
if (!Statics.Torch.CanRun)
|
||||||
|
throw HttpException.BadRequest($"Server can't start in state {Statics.Torch.State}", Statics.Torch.State);
|
||||||
|
|
||||||
|
Statics.Torch.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Route(HttpVerbs.Post, $"{RootPath}/stop")]
|
||||||
|
public async Task Stop(StopServerRequest request)
|
||||||
|
{
|
||||||
|
if (!Statics.Torch.IsRunning)
|
||||||
|
throw HttpException.BadRequest($"Server can't stop in state {Statics.Torch.State}", Statics.Torch.State);
|
||||||
|
|
||||||
|
var saveResult = await Statics.Torch.Save(exclusive: true);
|
||||||
|
if (saveResult is not GameSaveResult.Success)
|
||||||
|
throw HttpException.InternalServerError($"Save resulted in {saveResult}", saveResult);
|
||||||
|
|
||||||
|
Statics.Torch.Stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Route(HttpVerbs.Get, $"{RootPath}/settings")]
|
||||||
|
public ServerSettings GetSettings()
|
||||||
|
{
|
||||||
|
var settings = Statics.Torch.DedicatedInstance.DedicatedConfig;
|
||||||
|
|
||||||
|
return new(settings.ServerName ?? "unamed",
|
||||||
|
settings.WorldName ?? "unamed",
|
||||||
|
settings.ServerDescription ?? string.Empty,
|
||||||
|
settings.SessionSettings.MaxPlayers,
|
||||||
|
new(settings.IP, settings.Port));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Route(HttpVerbs.Post, $"{RootPath}/settings")]
|
||||||
|
public async Task SetSettings([JsonData] ServerSettings request)
|
||||||
|
{
|
||||||
|
var settings = Statics.Torch.DedicatedInstance.DedicatedConfig;
|
||||||
|
|
||||||
|
settings.ServerName = request.ServerName;
|
||||||
|
settings.WorldName = request.MapName;
|
||||||
|
settings.ServerDescription = request.ServerDescription;
|
||||||
|
settings.SessionSettings.MaxPlayers = request.MemberLimit;
|
||||||
|
settings.IP = request.ListenEndPoint.Ip;
|
||||||
|
settings.Port = request.ListenEndPoint.Port;
|
||||||
|
|
||||||
|
if (Statics.Torch.IsRunning)
|
||||||
|
await Statics.Torch.InvokeAsync(request.ApplyDynamically);
|
||||||
|
}
|
||||||
|
}
|
24
TorchRemote.Plugin/Controllers/SettingsController.cs
Normal file
24
TorchRemote.Plugin/Controllers/SettingsController.cs
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
using EmbedIO;
|
||||||
|
using EmbedIO.Routing;
|
||||||
|
using EmbedIO.WebApi;
|
||||||
|
using Swan;
|
||||||
|
using TorchRemote.Models.Responses;
|
||||||
|
using TorchRemote.Plugin.Utils;
|
||||||
|
namespace TorchRemote.Plugin.Controllers;
|
||||||
|
|
||||||
|
public class SettingsController : WebApiController
|
||||||
|
{
|
||||||
|
private const string RootPath = "/settings";
|
||||||
|
|
||||||
|
[Route(HttpVerbs.Get, $"{RootPath}/{{id}}")]
|
||||||
|
public SettingInfoResponse Get(Guid id)
|
||||||
|
{
|
||||||
|
if (!Statics.SettingManager.Settings.TryGetValue(id, out var setting))
|
||||||
|
throw HttpException.NotFound($"Setting with id {id} not found", id);
|
||||||
|
|
||||||
|
return new(setting.Name.Humanize(), setting.Properties.Select(b =>
|
||||||
|
new SettingPropertyInfo(b.DisplayInfo?.Name ?? b.Name.Humanize(),
|
||||||
|
b.DisplayInfo?.Description, b.DisplayInfo?.Order, b.TypeId))
|
||||||
|
.ToArray());
|
||||||
|
}
|
||||||
|
}
|
62
TorchRemote.Plugin/Controllers/WorldsController.cs
Normal file
62
TorchRemote.Plugin/Controllers/WorldsController.cs
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
using System.Net;
|
||||||
|
using EmbedIO;
|
||||||
|
using EmbedIO.Routing;
|
||||||
|
using EmbedIO.WebApi;
|
||||||
|
using TorchRemote.Models.Responses;
|
||||||
|
using TorchRemote.Plugin.Utils;
|
||||||
|
namespace TorchRemote.Plugin.Controllers;
|
||||||
|
|
||||||
|
public class WorldsController : WebApiController
|
||||||
|
{
|
||||||
|
private const string RootPath = "/worlds";
|
||||||
|
|
||||||
|
[Route(HttpVerbs.Get, RootPath)]
|
||||||
|
public IEnumerable<Guid> Get()
|
||||||
|
{
|
||||||
|
var config = Statics.InstanceManager.DedicatedConfig;
|
||||||
|
|
||||||
|
if (config is null)
|
||||||
|
throw new HttpException(HttpStatusCode.ServiceUnavailable);
|
||||||
|
|
||||||
|
return config.Worlds.Select(b => b.FolderName.ToGuid());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Route(HttpVerbs.Get, $"{RootPath}/selected")]
|
||||||
|
public Guid GetSelected()
|
||||||
|
{
|
||||||
|
if (Statics.InstanceManager.DedicatedConfig?.SelectedWorld is not { } world)
|
||||||
|
throw new HttpException(HttpStatusCode.ServiceUnavailable);
|
||||||
|
|
||||||
|
return world.FolderName.ToGuid();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Route(HttpVerbs.Get, $"{RootPath}/{{id}}")]
|
||||||
|
public WorldResponse GetWorld(Guid id)
|
||||||
|
{
|
||||||
|
var config = Statics.InstanceManager.DedicatedConfig;
|
||||||
|
|
||||||
|
if (config is null)
|
||||||
|
throw new HttpException(HttpStatusCode.ServiceUnavailable);
|
||||||
|
|
||||||
|
if (config.Worlds.FirstOrDefault(b => b.FolderName.ToGuid() == id) is not { } world)
|
||||||
|
throw HttpException.NotFound($"World not found by given id {id}", id);
|
||||||
|
|
||||||
|
return new(world.FolderName, world.WorldSizeKB);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Route(HttpVerbs.Post, $"{RootPath}/{{id}}/select")]
|
||||||
|
public void Select(Guid id)
|
||||||
|
{
|
||||||
|
var config = Statics.InstanceManager.DedicatedConfig;
|
||||||
|
|
||||||
|
if (config is null)
|
||||||
|
throw new HttpException(HttpStatusCode.ServiceUnavailable);
|
||||||
|
|
||||||
|
if (config.Worlds.FirstOrDefault(b => b.FolderName.ToGuid() == id) is not { } world)
|
||||||
|
throw HttpException.NotFound($"World not found by given id {id}", id);
|
||||||
|
|
||||||
|
config.Model.IgnoreLastSession = true;
|
||||||
|
config.SelectedWorld = world;
|
||||||
|
config.Save();
|
||||||
|
}
|
||||||
|
}
|
3
TorchRemote.Plugin/FodyWeavers.xml
Normal file
3
TorchRemote.Plugin/FodyWeavers.xml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
|
||||||
|
<PropertyChanged />
|
||||||
|
</Weavers>
|
74
TorchRemote.Plugin/FodyWeavers.xsd
Normal file
74
TorchRemote.Plugin/FodyWeavers.xsd
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
||||||
|
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. -->
|
||||||
|
<xs:element name="Weavers">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:all>
|
||||||
|
<xs:element name="PropertyChanged" minOccurs="0" maxOccurs="1">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:attribute name="InjectOnPropertyNameChanged" type="xs:boolean">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Used to control if the On_PropertyName_Changed feature is enabled.</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:attribute>
|
||||||
|
<xs:attribute name="TriggerDependentProperties" type="xs:boolean">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Used to control if the Dependent properties feature is enabled.</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:attribute>
|
||||||
|
<xs:attribute name="EnableIsChangedProperty" type="xs:boolean">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Used to control if the IsChanged property feature is enabled.</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:attribute>
|
||||||
|
<xs:attribute name="EventInvokerNames" type="xs:string">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Used to change the name of the method that fires the notify event. This is a string that accepts multiple values in a comma separated form.</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:attribute>
|
||||||
|
<xs:attribute name="CheckForEquality" type="xs:boolean">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Used to control if equality checks should be inserted. If false, equality checking will be disabled for the project.</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:attribute>
|
||||||
|
<xs:attribute name="CheckForEqualityUsingBaseEquals" type="xs:boolean">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Used to control if equality checks should use the Equals method resolved from the base class.</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:attribute>
|
||||||
|
<xs:attribute name="UseStaticEqualsFromBase" type="xs:boolean">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Used to control if equality checks should use the static Equals method resolved from the base class.</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:attribute>
|
||||||
|
<xs:attribute name="SuppressWarnings" type="xs:boolean">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Used to turn off build warnings from this weaver.</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:attribute>
|
||||||
|
<xs:attribute name="SuppressOnPropertyNameChangedWarning" type="xs:boolean">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Used to turn off build warnings about mismatched On_PropertyName_Changed methods.</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:attribute>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:all>
|
||||||
|
<xs:attribute name="VerifyAssembly" type="xs:boolean">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:attribute>
|
||||||
|
<xs:attribute name="VerifyIgnoreCodes" type="xs:string">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:attribute>
|
||||||
|
<xs:attribute name="GenerateXsd" type="xs:boolean">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:attribute>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:schema>
|
95
TorchRemote.Plugin/Managers/ApiServerManager.cs
Normal file
95
TorchRemote.Plugin/Managers/ApiServerManager.cs
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Text.Json;
|
||||||
|
using EmbedIO;
|
||||||
|
using EmbedIO.BearerToken;
|
||||||
|
using EmbedIO.WebApi;
|
||||||
|
using Microsoft.IdentityModel.Tokens;
|
||||||
|
using NLog;
|
||||||
|
using Torch.API;
|
||||||
|
using Torch.API.Managers;
|
||||||
|
using Torch.Managers;
|
||||||
|
using Torch.Server.Managers;
|
||||||
|
using TorchRemote.Plugin.Controllers;
|
||||||
|
using TorchRemote.Plugin.Modules;
|
||||||
|
using TorchRemote.Plugin.Utils;
|
||||||
|
using VRage.Game.ModAPI;
|
||||||
|
namespace TorchRemote.Plugin.Managers;
|
||||||
|
|
||||||
|
public class ApiServerManager : Manager
|
||||||
|
{
|
||||||
|
private static readonly ILogger Log = LogManager.GetCurrentClassLogger();
|
||||||
|
|
||||||
|
private readonly Config _config;
|
||||||
|
private readonly IWebServer _server;
|
||||||
|
[Dependency]
|
||||||
|
private readonly SettingManager _settingManager = null!;
|
||||||
|
[Dependency]
|
||||||
|
private readonly InstanceManager _instanceManager = null!;
|
||||||
|
public ApiServerManager(ITorchBase torchInstance, Config config) : base(torchInstance)
|
||||||
|
{
|
||||||
|
_config = config;
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(_config.SecurityKey))
|
||||||
|
_config.SecurityKey = CreateSecurityKey();
|
||||||
|
|
||||||
|
var apiModule = new WebApiModule("/api/v1", async (context, data) =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
context.Response.ContentType = "application/json";
|
||||||
|
using var stream = context.OpenResponseStream();
|
||||||
|
await JsonSerializer.SerializeAsync(stream, data, Statics.SerializerOptions);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e);
|
||||||
|
throw HttpException.InternalServerError(e.Message, e.Message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var chatModule = new ChatModule("/api/live/chat", true);
|
||||||
|
Statics.ChatModule = chatModule;
|
||||||
|
|
||||||
|
_server = new WebServer(o => o
|
||||||
|
.WithUrlPrefix(_config.Listener.UrlPrefix)
|
||||||
|
.WithMicrosoftHttpListener())
|
||||||
|
.WithLocalSessionManager()
|
||||||
|
.WithModule(apiModule
|
||||||
|
.WithController<ServerController>()
|
||||||
|
.WithController<SettingsController>()
|
||||||
|
.WithController<WorldsController>()
|
||||||
|
.WithController<ChatController>())
|
||||||
|
.WithModule(new LogsModule("/api/live/logs", true))
|
||||||
|
.WithModule(chatModule)
|
||||||
|
.WithBearerToken("/api", new SymmetricSecurityKey(Convert.FromBase64String(_config.SecurityKey)), new BasicAuthorizationServerProvider());
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Attach()
|
||||||
|
{
|
||||||
|
base.Attach();
|
||||||
|
|
||||||
|
Starter();
|
||||||
|
Log.Info("Listening on {0}", _config.Listener.UrlPrefix);
|
||||||
|
|
||||||
|
//_instanceManager.InstanceLoaded += model => _settingManager.RegisterSetting(model.Model, typeof(IMyConfigDedicated), false);
|
||||||
|
}
|
||||||
|
public override void Detach()
|
||||||
|
{
|
||||||
|
base.Detach();
|
||||||
|
_server.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void Starter()
|
||||||
|
{
|
||||||
|
await _server.RunAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string CreateSecurityKey()
|
||||||
|
{
|
||||||
|
var aes = Aes.Create();
|
||||||
|
aes.GenerateIV();
|
||||||
|
aes.GenerateKey();
|
||||||
|
|
||||||
|
return Convert.ToBase64String(aes.Key);
|
||||||
|
}
|
||||||
|
}
|
28
TorchRemote.Plugin/Managers/ChatMonitorManager.cs
Normal file
28
TorchRemote.Plugin/Managers/ChatMonitorManager.cs
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
using Sandbox.Engine.Multiplayer;
|
||||||
|
using Torch.API;
|
||||||
|
using Torch.API.Managers;
|
||||||
|
using Torch.Managers;
|
||||||
|
using TorchRemote.Models.Responses;
|
||||||
|
using TorchRemote.Models.Shared;
|
||||||
|
using TorchRemote.Plugin.Utils;
|
||||||
|
namespace TorchRemote.Plugin.Managers;
|
||||||
|
|
||||||
|
public class ChatMonitorManager : Manager
|
||||||
|
{
|
||||||
|
[Dependency]
|
||||||
|
private readonly IChatManagerServer _chatManager = null!;
|
||||||
|
public ChatMonitorManager(ITorchBase torchInstance) : base(torchInstance)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Attach()
|
||||||
|
{
|
||||||
|
base.Attach();
|
||||||
|
_chatManager.MessageRecieved += ChatManagerOnMessageReceived;
|
||||||
|
}
|
||||||
|
private void ChatManagerOnMessageReceived(TorchChatMessage msg, ref bool consumed)
|
||||||
|
{
|
||||||
|
Statics.ChatModule.SendChatResponse(new ChatMessageResponse(msg.Author ?? (msg.AuthorSteamId is null ? Torch.Config.ChatName : MyMultiplayer.Static.GetMemberName(msg.AuthorSteamId.Value)),
|
||||||
|
msg.AuthorSteamId, (ChatChannel)msg.Channel, msg.Message));
|
||||||
|
}
|
||||||
|
}
|
77
TorchRemote.Plugin/Managers/SettingManager.cs
Normal file
77
TorchRemote.Plugin/Managers/SettingManager.cs
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using System.Xml.Serialization;
|
||||||
|
using NLog;
|
||||||
|
using Torch.API;
|
||||||
|
using Torch.Managers;
|
||||||
|
using Torch.Views;
|
||||||
|
using TorchRemote.Models.Responses;
|
||||||
|
using TorchRemote.Plugin.Utils;
|
||||||
|
using VRage;
|
||||||
|
namespace TorchRemote.Plugin.Managers;
|
||||||
|
|
||||||
|
public class SettingManager : Manager
|
||||||
|
{
|
||||||
|
private static readonly ILogger Log = LogManager.GetCurrentClassLogger();
|
||||||
|
|
||||||
|
public SettingManager(ITorchBase torchInstance) : base(torchInstance)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public Guid RegisterSetting(object value, Type type, bool includeOnlyDisplay = true)
|
||||||
|
{
|
||||||
|
var properties = type.IsInterface ? type.GetProperties() : type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
|
||||||
|
var settingProperties = properties
|
||||||
|
.Where(b => !b.HasAttribute<XmlIgnoreAttribute>() &&
|
||||||
|
!b.HasAttribute<JsonIgnoreAttribute>() &&
|
||||||
|
(!includeOnlyDisplay ||
|
||||||
|
b.HasAttribute<DisplayAttribute>()))
|
||||||
|
.Select(property => new SettingProperty(property.Name,
|
||||||
|
GetTypeId(property.PropertyType, property.GetValue(value), includeOnlyDisplay),
|
||||||
|
property.PropertyType, property.GetMethod, property.SetMethod,
|
||||||
|
property.GetCustomAttribute<DisplayAttribute>() is { } attr ?
|
||||||
|
new(attr.Name, attr.Description, attr.GroupName, attr.Order, attr.ReadOnly, attr.Enabled) :
|
||||||
|
null))
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
var setting = new Setting(type.Name, type, settingProperties, value);
|
||||||
|
|
||||||
|
var id = (type.FullName! + value.GetHashCode()).ToGuid();
|
||||||
|
Settings.Add(id, setting);
|
||||||
|
Log.Debug("Registered type {0} with id {1}", type, id);
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Guid GetTypeId(Type type, object value, bool includeOnlyDisplay)
|
||||||
|
{
|
||||||
|
if (type == typeof(int) || type == typeof(uint))
|
||||||
|
return SettingPropertyTypeEnum.Integer;
|
||||||
|
if (type == typeof(bool))
|
||||||
|
return SettingPropertyTypeEnum.Boolean;
|
||||||
|
if (type == typeof(short) ||
|
||||||
|
type == typeof(ushort) ||
|
||||||
|
type == typeof(byte) ||
|
||||||
|
type == typeof(ulong) ||
|
||||||
|
type == typeof(long) ||
|
||||||
|
type == typeof(float) ||
|
||||||
|
type == typeof(double) ||
|
||||||
|
type == typeof(MyFixedPoint))
|
||||||
|
return SettingPropertyTypeEnum.Number;
|
||||||
|
if (type == typeof(string))
|
||||||
|
return SettingPropertyTypeEnum.String;
|
||||||
|
if (type == typeof(DateTime))
|
||||||
|
return SettingPropertyTypeEnum.DateTime;
|
||||||
|
if (type == typeof(TimeSpan))
|
||||||
|
return SettingPropertyTypeEnum.TimeSpan;
|
||||||
|
if (type == typeof(System.Drawing.Color) || type == typeof(VRageMath.Color))
|
||||||
|
return SettingPropertyTypeEnum.Color;
|
||||||
|
return RegisterSetting(value, type, includeOnlyDisplay);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IDictionary<Guid, Setting> Settings { get; } = new ConcurrentDictionary<Guid, Setting>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public record Setting(string Name, Type Type, IEnumerable<SettingProperty> Properties, object? Value = null);
|
||||||
|
public record SettingProperty(string Name, Guid TypeId, Type Type, MethodInfo Getter, MethodInfo? Setter, SettingPropertyDisplayInfo? DisplayInfo);
|
||||||
|
public record SettingPropertyDisplayInfo(string? Name, string? Description, string? GroupName, int? Order, bool? IsReadOnly, bool? IsEnabled);
|
28
TorchRemote.Plugin/Modules/ChatModule.cs
Normal file
28
TorchRemote.Plugin/Modules/ChatModule.cs
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
using System.Net.WebSockets;
|
||||||
|
using System.Text.Json;
|
||||||
|
using EmbedIO.WebSockets;
|
||||||
|
using TorchRemote.Models.Responses;
|
||||||
|
using TorchRemote.Plugin.Utils;
|
||||||
|
namespace TorchRemote.Plugin.Modules;
|
||||||
|
|
||||||
|
public class ChatModule : WebSocketModule
|
||||||
|
{
|
||||||
|
public ChatModule(string urlPath, bool enableConnectionWatchdog) : base(urlPath, enableConnectionWatchdog)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public async void SendChatResponse(ChatResponseBase response)
|
||||||
|
{
|
||||||
|
if (ActiveContexts.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var buffer = JsonSerializer.SerializeToUtf8Bytes(response, Statics.SerializerOptions);
|
||||||
|
await Task.WhenAll(ActiveContexts
|
||||||
|
.Where(b => b.WebSocket.State is WebSocketState.Open)
|
||||||
|
.Select(context => context.WebSocket.SendAsync(buffer, true)));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task OnMessageReceivedAsync(IWebSocketContext context, byte[] buffer, IWebSocketReceiveResult result)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
85
TorchRemote.Plugin/Modules/LogsModule.cs
Normal file
85
TorchRemote.Plugin/Modules/LogsModule.cs
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
using System.Net.WebSockets;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
|
using EmbedIO.WebSockets;
|
||||||
|
using NLog;
|
||||||
|
using NLog.Targets;
|
||||||
|
using NLog.Targets.Wrappers;
|
||||||
|
using Torch.Server;
|
||||||
|
using TorchRemote.Models.Responses;
|
||||||
|
using TorchRemote.Plugin.Utils;
|
||||||
|
namespace TorchRemote.Plugin.Modules;
|
||||||
|
|
||||||
|
public class LogsModule : WebSocketModule
|
||||||
|
{
|
||||||
|
|
||||||
|
public LogsModule(string urlPath, bool enableConnectionWatchdog) : base(urlPath, enableConnectionWatchdog)
|
||||||
|
{
|
||||||
|
ConfigureLogging();
|
||||||
|
}
|
||||||
|
protected override async Task OnMessageReceivedAsync(IWebSocketContext context, byte[] buffer, IWebSocketReceiveResult result)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public async void OnLogMessageReceived(DateTime time, LogLevel level, string logger, string message)
|
||||||
|
{
|
||||||
|
if (ActiveContexts.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var response = new LogLineResponse(time, (LogLineLevel)level.Ordinal, logger, message);
|
||||||
|
var buffer = JsonSerializer.SerializeToUtf8Bytes(response, Statics.SerializerOptions);
|
||||||
|
|
||||||
|
await Task.WhenAll(ActiveContexts
|
||||||
|
.Where(b => b.WebSocket.State is WebSocketState.Open)
|
||||||
|
.Select(context => context.WebSocket.SendAsync(buffer, true)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ConfigureLogging()
|
||||||
|
{
|
||||||
|
var cfg = LogManager.Configuration;
|
||||||
|
var flowDocumentTarget = cfg.FindTargetByName("wpf");
|
||||||
|
|
||||||
|
if (flowDocumentTarget is null or SplitGroupTarget)
|
||||||
|
return;
|
||||||
|
|
||||||
|
flowDocumentTarget.Name = "wpf-old";
|
||||||
|
|
||||||
|
var target = new SplitGroupTarget
|
||||||
|
{
|
||||||
|
Name = "wpf",
|
||||||
|
Targets =
|
||||||
|
{
|
||||||
|
flowDocumentTarget,
|
||||||
|
new StupidTarget(this)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
cfg.RemoveTarget("wpf");
|
||||||
|
cfg.AddTarget(target);
|
||||||
|
foreach (var rule in cfg.LoggingRules)
|
||||||
|
{
|
||||||
|
if (rule.Targets.Remove(flowDocumentTarget))
|
||||||
|
rule.Targets.Add(target);
|
||||||
|
}
|
||||||
|
LogManager.Configuration = cfg;
|
||||||
|
LogManager.GetCurrentClassLogger().Info("Reconfigured logging");
|
||||||
|
}
|
||||||
|
|
||||||
|
private class StupidTarget : Target
|
||||||
|
{
|
||||||
|
private readonly LogsModule _module;
|
||||||
|
public StupidTarget(LogsModule module)
|
||||||
|
{
|
||||||
|
_module = module;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Write(LogEventInfo logEvent)
|
||||||
|
{
|
||||||
|
var message = logEvent.FormattedMessage;
|
||||||
|
if (logEvent.Exception is not null)
|
||||||
|
message += $"\n{logEvent.Exception}";
|
||||||
|
|
||||||
|
_module.OnLogMessageReceived(logEvent.TimeStamp, logEvent.Level, logEvent.LoggerName, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
33
TorchRemote.Plugin/Plugin.cs
Normal file
33
TorchRemote.Plugin/Plugin.cs
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
using System.IO;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using Torch;
|
||||||
|
using Torch.API;
|
||||||
|
using Torch.API.Managers;
|
||||||
|
using Torch.API.Plugins;
|
||||||
|
using Torch.API.Session;
|
||||||
|
using Torch.Views;
|
||||||
|
using TorchRemote.Plugin.Managers;
|
||||||
|
|
||||||
|
namespace TorchRemote.Plugin;
|
||||||
|
|
||||||
|
public class Plugin : TorchPluginBase, IWpfPlugin
|
||||||
|
{
|
||||||
|
private Persistent<Config> _config = null!;
|
||||||
|
|
||||||
|
public override void Init(ITorchBase torch)
|
||||||
|
{
|
||||||
|
base.Init(torch);
|
||||||
|
_config = Persistent<Config>.Load(Path.Combine(StoragePath, "TorchRemote.cfg"));
|
||||||
|
|
||||||
|
Torch.Managers.AddManager(new ApiServerManager(Torch, _config.Data));
|
||||||
|
Torch.Managers.AddManager(new SettingManager(Torch));
|
||||||
|
Torch.Managers.GetManager<ITorchSessionManager>()
|
||||||
|
.AddFactory(s => new ChatMonitorManager(s.Torch));
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserControl GetControl() => new PropertyGrid
|
||||||
|
{
|
||||||
|
Margin = new(3),
|
||||||
|
DataContext = _config.Data
|
||||||
|
};
|
||||||
|
}
|
99
TorchRemote.Plugin/TorchRemote.Plugin.csproj
Normal file
99
TorchRemote.Plugin/TorchRemote.Plugin.csproj
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net48</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<PlatformTarget>x64</PlatformTarget>
|
||||||
|
<LangVersion>10</LangVersion>
|
||||||
|
<UseWpf>true</UseWpf>
|
||||||
|
<TorchDir>$(SolutionDir)TorchBinaries\</TorchDir>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||||
|
<DebugType>none</DebugType>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Reference Include="NLog">
|
||||||
|
<HintPath>$(TorchDir)NLog.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Sandbox.Common, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
|
||||||
|
<HintPath>$(TorchDir)DedicatedServer64\Sandbox.Common.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Sandbox.Game, Version=0.1.1.0, Culture=neutral, PublicKeyToken=null">
|
||||||
|
<HintPath>$(TorchDir)DedicatedServer64\Sandbox.Game.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Sandbox.Graphics, Version=0.1.1.0, Culture=neutral, PublicKeyToken=null">
|
||||||
|
<HintPath>$(TorchDir)DedicatedServer64\Sandbox.Graphics.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="System.Memory">
|
||||||
|
<HintPath>$(TorchDir)DedicatedServer64\System.Memory.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="System.Net.Http" />
|
||||||
|
<Reference Include="Torch">
|
||||||
|
<HintPath>$(TorchDir)Torch.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Torch.API">
|
||||||
|
<HintPath>$(TorchDir)Torch.API.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Torch.Server">
|
||||||
|
<HintPath>$(TorchDir)Torch.Server.exe</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="VRage, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
|
||||||
|
<HintPath>$(TorchDir)DedicatedServer64\VRage.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="VRage.Game, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
|
||||||
|
<HintPath>$(TorchDir)DedicatedServer64\VRage.Game.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="VRage.Input, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
|
||||||
|
<HintPath>$(TorchDir)DedicatedServer64\VRage.Input.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="VRage.Library, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
|
||||||
|
<HintPath>$(TorchDir)DedicatedServer64\VRage.Library.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="VRage.Math, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
|
||||||
|
<HintPath>$(TorchDir)DedicatedServer64\VRage.Math.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="VRage.Network, Version=1.0.53.0, Culture=neutral, PublicKeyToken=null">
|
||||||
|
<HintPath>$(TorchDir)DedicatedServer64\VRage.Network.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="EmbedIO" Version="3.4.3" />
|
||||||
|
<PackageReference Include="EmbedIO.BearerToken" Version="3.4.2" />
|
||||||
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" ExcludeAssets="all" PrivateAssets="all" />
|
||||||
|
<PackageReference Include="PropertyChanged.Fody" Version="4.0.0" PrivateAssets="all" />
|
||||||
|
<PackageReference Include="System.Text.Json" Version="7.0.0-preview.6.22324.4" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Update="manifest.xml">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\TorchRemote.Models\TorchRemote.Models.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<Target Name="CopyToTorch" AfterTargets="Build">
|
||||||
|
<ZipDirectory DestinationFile="$(TorchDir)Plugins\$(AssemblyName).zip" SourceDirectory="$(TargetDir)" Overwrite="true" />
|
||||||
|
</Target>
|
||||||
|
|
||||||
|
</Project>
|
29
TorchRemote.Plugin/Utils/Extensions.cs
Normal file
29
TorchRemote.Plugin/Utils/Extensions.cs
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Text;
|
||||||
|
using Sandbox.Engine.Multiplayer;
|
||||||
|
using Sandbox.Engine.Networking;
|
||||||
|
using Sandbox.Game;
|
||||||
|
using Sandbox.Game.World;
|
||||||
|
using TorchRemote.Models.Shared;
|
||||||
|
namespace TorchRemote.Plugin.Utils;
|
||||||
|
|
||||||
|
public static class Extensions
|
||||||
|
{
|
||||||
|
public static void ApplyDynamically(this ServerSettings settings)
|
||||||
|
{
|
||||||
|
MyGameService.GameServer.SetServerName(settings.ServerName);
|
||||||
|
|
||||||
|
MyMultiplayer.Static.HostName = settings.ServerName;
|
||||||
|
MyMultiplayer.Static.WorldName = settings.MapName;
|
||||||
|
MySession.Static.Name = settings.MapName;
|
||||||
|
MyMultiplayer.Static.MemberLimit = settings.MemberLimit;
|
||||||
|
|
||||||
|
MyCachedServerItem.SendSettingsToSteam();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Guid ToGuid(this string s)
|
||||||
|
{
|
||||||
|
using var md5 = MD5.Create();
|
||||||
|
return new(md5.ComputeHash(Encoding.UTF8.GetBytes(s)));
|
||||||
|
}
|
||||||
|
}
|
34
TorchRemote.Plugin/Utils/JsonDataAttribute.cs
Normal file
34
TorchRemote.Plugin/Utils/JsonDataAttribute.cs
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
using System.Text.Json;
|
||||||
|
using EmbedIO;
|
||||||
|
using EmbedIO.WebApi;
|
||||||
|
namespace TorchRemote.Plugin.Utils;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <para>Specifies that a parameter of a controller method will receive
|
||||||
|
/// an object obtained by deserializing the request body as JSON.</para>
|
||||||
|
/// <para>The received object will be <see langword="null"/>
|
||||||
|
/// only if the deserialized object is <c>null</c>.</para>
|
||||||
|
/// <para>If the request body is not valid JSON,
|
||||||
|
/// or if it cannot be deserialized to the type of the parameter,
|
||||||
|
/// a <c>400 Bad Request</c> response will be sent to the client.</para>
|
||||||
|
/// <para>This class cannot be inherited.</para>
|
||||||
|
/// </summary>
|
||||||
|
/// <seealso cref="Attribute" />
|
||||||
|
/// <seealso cref="IRequestDataAttribute{TController}" />
|
||||||
|
[AttributeUsage(AttributeTargets.Parameter)]
|
||||||
|
public class JsonDataAttribute : Attribute, IRequestDataAttribute<WebApiController>
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public async Task<object?> GetRequestDataAsync(WebApiController controller, Type type, string parameterName)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var stream = controller.HttpContext.OpenRequestStream();
|
||||||
|
return await JsonSerializer.DeserializeAsync(stream, type, Statics.SerializerOptions);
|
||||||
|
}
|
||||||
|
catch (JsonException)
|
||||||
|
{
|
||||||
|
throw HttpException.BadRequest($"Expected request body to be deserializable to {type.FullName}.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
24
TorchRemote.Plugin/Utils/Statics.cs
Normal file
24
TorchRemote.Plugin/Utils/Statics.cs
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
using System.Text.Json;
|
||||||
|
using Torch;
|
||||||
|
using Torch.API;
|
||||||
|
using Torch.API.Managers;
|
||||||
|
using Torch.Commands;
|
||||||
|
using Torch.Server;
|
||||||
|
using Torch.Server.Managers;
|
||||||
|
using TorchRemote.Plugin.Managers;
|
||||||
|
using TorchRemote.Plugin.Modules;
|
||||||
|
namespace TorchRemote.Plugin.Utils;
|
||||||
|
|
||||||
|
internal static class Statics
|
||||||
|
{
|
||||||
|
#pragma warning disable CS0618
|
||||||
|
public static TorchServer Torch => (TorchServer)TorchBase.Instance;
|
||||||
|
#pragma warning restore CS0618
|
||||||
|
|
||||||
|
public static readonly JsonSerializerOptions SerializerOptions = new(JsonSerializerDefaults.Web);
|
||||||
|
public static SettingManager SettingManager => Torch.Managers.GetManager<SettingManager>();
|
||||||
|
public static InstanceManager InstanceManager => Torch.Managers.GetManager<InstanceManager>();
|
||||||
|
public static CommandManager? CommandManager => Torch.CurrentSession?.Managers.GetManager<CommandManager>();
|
||||||
|
|
||||||
|
public static ChatModule ChatModule = null!;
|
||||||
|
}
|
6
TorchRemote.Plugin/manifest.xml
Normal file
6
TorchRemote.Plugin/manifest.xml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<PluginManifest xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||||
|
<Name>TorchRemote.Plugin</Name>
|
||||||
|
<Guid>284017F3-9682-4841-A544-EB04DB8CB9BA</Guid>
|
||||||
|
<Version>v1.0.0</Version>
|
||||||
|
</PluginManifest>
|
20
TorchRemote.Plugin/setup.bat
Normal file
20
TorchRemote.Plugin/setup.bat
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
:: This script creates a symlink to the game binaries to account for different installation directories on different systems.
|
||||||
|
|
||||||
|
@echo off
|
||||||
|
|
||||||
|
set /p path="Please enter the folder location of your Torch.Server.exe: "
|
||||||
|
cd %~dp0
|
||||||
|
rmdir TorchBinaries > nul 2>&1
|
||||||
|
mklink /J ..\TorchBinaries "%path%"
|
||||||
|
if errorlevel 1 goto Error
|
||||||
|
echo Done!
|
||||||
|
|
||||||
|
echo You can now open the plugin without issue.
|
||||||
|
goto EndFinal
|
||||||
|
|
||||||
|
:Error
|
||||||
|
echo An error occured creating the symlink.
|
||||||
|
goto EndFinal
|
||||||
|
|
||||||
|
:EndFinal
|
||||||
|
pause
|
28
TorchRemote.sln
Normal file
28
TorchRemote.sln
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TorchRemote", "TorchRemote\TorchRemote.csproj", "{AFCBEFA1-A827-43C8-B777-A1548FE4BE89}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TorchRemote.Models", "TorchRemote.Models\TorchRemote.Models.csproj", "{FC44F0B6-A2A3-4DA7-A900-4BAC69AEA529}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TorchRemote.Plugin", "TorchRemote.Plugin\TorchRemote.Plugin.csproj", "{054A416F-F106-4E74-98A0-29566F9161C1}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{AFCBEFA1-A827-43C8-B777-A1548FE4BE89}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{AFCBEFA1-A827-43C8-B777-A1548FE4BE89}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{AFCBEFA1-A827-43C8-B777-A1548FE4BE89}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{AFCBEFA1-A827-43C8-B777-A1548FE4BE89}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{FC44F0B6-A2A3-4DA7-A900-4BAC69AEA529}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{FC44F0B6-A2A3-4DA7-A900-4BAC69AEA529}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{FC44F0B6-A2A3-4DA7-A900-4BAC69AEA529}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{FC44F0B6-A2A3-4DA7-A900-4BAC69AEA529}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{054A416F-F106-4E74-98A0-29566F9161C1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{054A416F-F106-4E74-98A0-29566F9161C1}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{054A416F-F106-4E74-98A0-29566F9161C1}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{054A416F-F106-4E74-98A0-29566F9161C1}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
9
TorchRemote/App.axaml
Normal file
9
TorchRemote/App.axaml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<Application xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:local="using:TorchRemote"
|
||||||
|
xmlns:styling="clr-namespace:FluentAvalonia.Styling;assembly=FluentAvalonia"
|
||||||
|
x:Class="TorchRemote.App">
|
||||||
|
<Application.Styles>
|
||||||
|
<styling:FluentAvaloniaTheme PreferSystemTheme="True" />
|
||||||
|
</Application.Styles>
|
||||||
|
</Application>
|
42
TorchRemote/App.axaml.cs
Normal file
42
TorchRemote/App.axaml.cs
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls.ApplicationLifetimes;
|
||||||
|
using Avalonia.Markup.Xaml;
|
||||||
|
using ReactiveUI;
|
||||||
|
using Splat;
|
||||||
|
using TorchRemote.ViewModels;
|
||||||
|
using TorchRemote.ViewModels.Server;
|
||||||
|
using TorchRemote.Views;
|
||||||
|
using TorchRemote.Views.Server;
|
||||||
|
|
||||||
|
namespace TorchRemote
|
||||||
|
{
|
||||||
|
public partial class App : Application
|
||||||
|
{
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
AvaloniaXamlLoader.Load(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnFrameworkInitializationCompleted()
|
||||||
|
{
|
||||||
|
Locator.CurrentMutable.RegisterConstant<IScreen>(new MainWindowViewModel());
|
||||||
|
|
||||||
|
Locator.CurrentMutable.Register<IViewFor<RemoteServerViewModel>>(() => new RemoteServerView());
|
||||||
|
Locator.CurrentMutable.Register<IViewFor<DashboardViewModel>>(() => new DashboardView());
|
||||||
|
Locator.CurrentMutable.Register<IViewFor<ServerConfigViewModel>>(() => new ServerConfigView());
|
||||||
|
Locator.CurrentMutable.Register<IViewFor<ChatViewModel>>(() => new ChatView());
|
||||||
|
Locator.CurrentMutable.Register<IViewFor<PlayersViewModel>>(() => new PlayersView());
|
||||||
|
Locator.CurrentMutable.Register<IViewFor<SettingsViewModel>>(() => new SettingsView());
|
||||||
|
|
||||||
|
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||||
|
{
|
||||||
|
desktop.MainWindow = new MainWindow
|
||||||
|
{
|
||||||
|
DataContext = Locator.Current.GetService<IScreen>(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
base.OnFrameworkInitializationCompleted();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
BIN
TorchRemote/Assets/avalonia-logo.ico
Normal file
BIN
TorchRemote/Assets/avalonia-logo.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 172 KiB |
BIN
TorchRemote/Assets/torchicon.ico
Normal file
BIN
TorchRemote/Assets/torchicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 17 KiB |
65
TorchRemote/Controls/Flyouts/MessageConfirmDismissFlyout.cs
Normal file
65
TorchRemote/Controls/Flyouts/MessageConfirmDismissFlyout.cs
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
using System.Windows.Input;
|
||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Layout;
|
||||||
|
using FluentAvalonia.UI.Controls;
|
||||||
|
using FluentAvalonia.UI.Controls.Primitives;
|
||||||
|
namespace TorchRemote.Controls.Flyouts;
|
||||||
|
#nullable disable
|
||||||
|
public class MessageConfirmDismissFlyout : PickerFlyoutBase
|
||||||
|
{
|
||||||
|
public static readonly DirectProperty<MessageConfirmDismissFlyout, ICommand> ResultCommandProperty =
|
||||||
|
AvaloniaProperty.RegisterDirect<MessageConfirmDismissFlyout, ICommand>(nameof(ResultCommand), flyout => flyout._command!,
|
||||||
|
(flyout, command) => flyout._command = command);
|
||||||
|
|
||||||
|
public static readonly DirectProperty<MessageConfirmDismissFlyout, string> MessageProperty =
|
||||||
|
AvaloniaProperty.RegisterDirect<MessageConfirmDismissFlyout, string>(nameof(Message), flyout => flyout._message,
|
||||||
|
(flyout, s) => flyout._message = s);
|
||||||
|
|
||||||
|
public string Message
|
||||||
|
{
|
||||||
|
get => _message;
|
||||||
|
set => SetAndRaise(MessageProperty, ref _message, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ICommand ResultCommand
|
||||||
|
{
|
||||||
|
get => _command;
|
||||||
|
set => SetAndRaise(ResultCommandProperty, ref _command, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ICommand _command;
|
||||||
|
private string _message;
|
||||||
|
protected override Control CreatePresenter()
|
||||||
|
{
|
||||||
|
var pfp = new PickerFlyoutPresenter
|
||||||
|
{
|
||||||
|
Content = new TextBlock
|
||||||
|
{
|
||||||
|
Text = _message,
|
||||||
|
HorizontalAlignment = HorizontalAlignment.Center,
|
||||||
|
VerticalAlignment = VerticalAlignment.Center,
|
||||||
|
Margin = new(3)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pfp.Confirmed += PfpOnConfirmed;
|
||||||
|
pfp.Dismissed += PfpOnDismissed;
|
||||||
|
|
||||||
|
return pfp;
|
||||||
|
}
|
||||||
|
private void PfpOnDismissed(PickerFlyoutPresenter sender, object args)
|
||||||
|
{
|
||||||
|
ResultCommand?.Execute(false);
|
||||||
|
Hide();
|
||||||
|
}
|
||||||
|
private void PfpOnConfirmed(PickerFlyoutPresenter sender, object args)
|
||||||
|
{
|
||||||
|
OnConfirmed();
|
||||||
|
Hide();
|
||||||
|
}
|
||||||
|
protected override void OnConfirmed()
|
||||||
|
{
|
||||||
|
ResultCommand?.Execute(true);
|
||||||
|
}
|
||||||
|
}
|
3
TorchRemote/FodyWeavers.xml
Normal file
3
TorchRemote/FodyWeavers.xml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
|
||||||
|
<ReactiveUI />
|
||||||
|
</Weavers>
|
23
TorchRemote/Program.cs
Normal file
23
TorchRemote/Program.cs
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
using Avalonia;
|
||||||
|
using Avalonia.ReactiveUI;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace TorchRemote
|
||||||
|
{
|
||||||
|
class Program
|
||||||
|
{
|
||||||
|
// Initialization code. Don't use any Avalonia, third-party APIs or any
|
||||||
|
// SynchronizationContext-reliant code before AppMain is called: things aren't initialized
|
||||||
|
// yet and stuff might break.
|
||||||
|
[STAThread]
|
||||||
|
public static void Main(string[] args) => BuildAvaloniaApp()
|
||||||
|
.StartWithClassicDesktopLifetime(args);
|
||||||
|
|
||||||
|
// Avalonia configuration, don't remove; also used by visual designer.
|
||||||
|
public static AppBuilder BuildAvaloniaApp()
|
||||||
|
=> AppBuilder.Configure<App>()
|
||||||
|
.UsePlatformDetect()
|
||||||
|
.LogToTrace()
|
||||||
|
.UseReactiveUI();
|
||||||
|
}
|
||||||
|
}
|
115
TorchRemote/Services/ApiClientService.cs
Normal file
115
TorchRemote/Services/ApiClientService.cs
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Net.Http.Headers;
|
||||||
|
using System.Net.Http.Json;
|
||||||
|
using System.Net.WebSockets;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using TorchRemote.Models.Requests;
|
||||||
|
using TorchRemote.Models.Responses;
|
||||||
|
using TorchRemote.Models.Shared;
|
||||||
|
using Websocket.Client;
|
||||||
|
namespace TorchRemote.Services;
|
||||||
|
|
||||||
|
public class ApiClientService
|
||||||
|
{
|
||||||
|
public const string Version = "v1";
|
||||||
|
public string BearerToken
|
||||||
|
{
|
||||||
|
get => _client.DefaultRequestHeaders.Authorization?.Parameter ?? "*****";
|
||||||
|
set => _client.DefaultRequestHeaders.Authorization = AuthenticationHeaderValue.Parse($"Bearer {value}");
|
||||||
|
}
|
||||||
|
private readonly HttpClient _client = new();
|
||||||
|
public string BaseUrl
|
||||||
|
{
|
||||||
|
get => _client.BaseAddress?.ToString() ?? "http://localhost";
|
||||||
|
set => _client.BaseAddress = new($"{value}/api/{Version}/");
|
||||||
|
}
|
||||||
|
|
||||||
|
public event EventHandler? Connected;
|
||||||
|
|
||||||
|
public ApiClientService()
|
||||||
|
{
|
||||||
|
Task.Run(ConnectionTimer);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ConnectionTimer()
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
await Task.Delay(1000);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await GetServerStatusAsync(CancellationToken.None);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Connected?.Invoke(this, EventArgs.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<ServerStatusResponse> GetServerStatusAsync(CancellationToken token) =>
|
||||||
|
_client.GetFromJsonAsync<ServerStatusResponse>("server/status", token)!;
|
||||||
|
|
||||||
|
public Task<ServerSettings> GetServerSettingsAsync(CancellationToken token) =>
|
||||||
|
_client.GetFromJsonAsync<ServerSettings>("server/settings", token)!;
|
||||||
|
|
||||||
|
public Task SetServerSettingsAsync(ServerSettings settings, CancellationToken token) =>
|
||||||
|
_client.PostAsJsonAsync("server/settings", settings, token);
|
||||||
|
|
||||||
|
public Task StartServerAsync(CancellationToken token) =>
|
||||||
|
_client.PostAsync("server/start", null, token);
|
||||||
|
|
||||||
|
public Task StopServerAsync(StopServerRequest request, CancellationToken token) =>
|
||||||
|
_client.PostAsJsonAsync("server/stop", request, token);
|
||||||
|
|
||||||
|
public Task<IEnumerable<Guid>> GetWorldsAsync(CancellationToken token) =>
|
||||||
|
_client.GetFromJsonAsync<IEnumerable<Guid>>("worlds", token)!;
|
||||||
|
|
||||||
|
public Task<WorldResponse> GetWorldAsync(Guid id, CancellationToken token) =>
|
||||||
|
_client.GetFromJsonAsync<WorldResponse>($"worlds/{id}", token)!;
|
||||||
|
|
||||||
|
public Task<Guid> GetSelectedWorld(CancellationToken token) =>
|
||||||
|
_client.GetFromJsonAsync<Guid>("worlds/selected", token);
|
||||||
|
|
||||||
|
public Task SelectWorldAsync(Guid id, CancellationToken token) =>
|
||||||
|
_client.PostAsync($"worlds/{id}/select", null, token);
|
||||||
|
|
||||||
|
public Task SendChatMessageAsync(ChatMessageRequest request, CancellationToken token) =>
|
||||||
|
_client.PostAsJsonAsync("chat/message", request, token);
|
||||||
|
|
||||||
|
public async Task<Guid> InvokeCommandAsync(ChatCommandRequest request, CancellationToken token)
|
||||||
|
{
|
||||||
|
var r = await _client.PostAsJsonAsync("chat/command", request, token);
|
||||||
|
r.EnsureSuccessStatusCode();
|
||||||
|
return await r.Content.ReadFromJsonAsync<Guid>(cancellationToken: token);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<WebsocketClient> WatchChatAsync() => StartWebsocketConnectionAsync("live/chat");
|
||||||
|
|
||||||
|
public Task<WebsocketClient> WatchLogLinesAsync() => StartWebsocketConnectionAsync("live/logs");
|
||||||
|
|
||||||
|
private async Task<WebsocketClient> StartWebsocketConnectionAsync(string url)
|
||||||
|
{
|
||||||
|
var client = new WebsocketClient(new($"{BaseUrl}{url}"
|
||||||
|
.Replace($"/{Version}", string.Empty)
|
||||||
|
.Replace("http", "ws")),
|
||||||
|
() =>
|
||||||
|
{
|
||||||
|
var socket = new ClientWebSocket();
|
||||||
|
socket.Options.SetRequestHeader("Authorization", $"Bearer {BearerToken}");
|
||||||
|
return socket;
|
||||||
|
})
|
||||||
|
{
|
||||||
|
ReconnectTimeout = null,
|
||||||
|
ErrorReconnectTimeout = TimeSpan.FromSeconds(10)
|
||||||
|
};
|
||||||
|
|
||||||
|
await client.Start();
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
}
|
35
TorchRemote/TorchRemote.csproj
Normal file
35
TorchRemote/TorchRemote.csproj
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>WinExe</OutputType>
|
||||||
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<!--Avalonia doesen't support TrimMode=link currently,but we are working on that https://github.com/AvaloniaUI/Avalonia/issues/6892 -->
|
||||||
|
<TrimMode>copyused</TrimMode>
|
||||||
|
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<AvaloniaResource Include="Assets\**" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<!--This helps with theme dll-s trimming.
|
||||||
|
If you will publish your application in self-contained mode with p:PublishTrimmed=true and it will use Fluent theme Default theme will be trimmed from the output and vice versa.
|
||||||
|
https://github.com/AvaloniaUI/Avalonia/issues/5593 -->
|
||||||
|
<TrimmableAssembly Include="Avalonia.Themes.Fluent" />
|
||||||
|
<TrimmableAssembly Include="Avalonia.Themes.Default" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Avalonia" Version="0.10.16" />
|
||||||
|
<PackageReference Include="Avalonia.Desktop" Version="0.10.16" />
|
||||||
|
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
|
||||||
|
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="0.10.16" />
|
||||||
|
<PackageReference Include="Avalonia.ReactiveUI" Version="0.10.16" />
|
||||||
|
<PackageReference Include="FluentAvaloniaUI" Version="1.4.1" />
|
||||||
|
<PackageReference Include="ReactiveUI.Fody" Version="18.3.1" />
|
||||||
|
<PackageReference Include="System.Text.Json" Version="7.0.0-preview.6.22324.4" />
|
||||||
|
<PackageReference Include="Websocket.Client" Version="4.4.43" />
|
||||||
|
<PackageReference Include="XamlNameReferenceGenerator" Version="1.3.4" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\TorchRemote.Models\TorchRemote.Models.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
13
TorchRemote/ViewModels/MainWindowViewModel.cs
Normal file
13
TorchRemote/ViewModels/MainWindowViewModel.cs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using ReactiveUI;
|
||||||
|
namespace TorchRemote.ViewModels;
|
||||||
|
|
||||||
|
public class MainWindowViewModel : ViewModelBase, IScreen
|
||||||
|
{
|
||||||
|
public ObservableCollection<TabViewModelBase> Tabs { get; set; } = new()
|
||||||
|
{
|
||||||
|
new RemoteServerViewModel()
|
||||||
|
};
|
||||||
|
|
||||||
|
public RoutingState Router { get; set; } = new();
|
||||||
|
}
|
62
TorchRemote/ViewModels/RemoteServerViewModel.cs
Normal file
62
TorchRemote/ViewModels/RemoteServerViewModel.cs
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reactive.Linq;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using DynamicData.Binding;
|
||||||
|
using FluentAvalonia.UI.Controls;
|
||||||
|
using ReactiveUI;
|
||||||
|
using ReactiveUI.Fody.Helpers;
|
||||||
|
using TorchRemote.Services;
|
||||||
|
using TorchRemote.ViewModels.Server;
|
||||||
|
namespace TorchRemote.ViewModels;
|
||||||
|
|
||||||
|
public class RemoteServerViewModel : TabViewModelBase, IScreen
|
||||||
|
{
|
||||||
|
private readonly ApiClientService _clientService = new();
|
||||||
|
|
||||||
|
[Reactive]
|
||||||
|
public override string Header { get; set; } = "Torch Server";
|
||||||
|
|
||||||
|
public ObservableCollection<ServerNavItem> NavItems { get; set; }
|
||||||
|
[Reactive]
|
||||||
|
public ServerNavItem CurrentNavItem { get; set; }
|
||||||
|
|
||||||
|
public RemoteServerViewModel()
|
||||||
|
{
|
||||||
|
var settingsViewModel = new SettingsViewModel(_clientService);
|
||||||
|
NavItems = new()
|
||||||
|
{
|
||||||
|
new("Dashboard", Symbol.Home, new DashboardViewModel(_clientService)),
|
||||||
|
new("Server Config", Symbol.Settings, new ServerConfigViewModel(_clientService)),
|
||||||
|
new("Chat", Symbol.Message, new ChatViewModel(_clientService)),
|
||||||
|
new("Players", Symbol.People, new PlayersViewModel(_clientService)),
|
||||||
|
new("Settings", Symbol.More, settingsViewModel) {IsVisible = true}
|
||||||
|
};
|
||||||
|
CurrentNavItem = NavItems.Last();
|
||||||
|
|
||||||
|
this.WhenAnyValue(x => x.CurrentNavItem)
|
||||||
|
.Select<ServerNavItem, IRoutableViewModel>(b => b.ViewModel)
|
||||||
|
.InvokeCommand(Router, x => x.Navigate);
|
||||||
|
|
||||||
|
Observable.FromEventPattern(_clientService, nameof(_clientService.Connected))
|
||||||
|
.ObserveOn(RxApp.MainThreadScheduler)
|
||||||
|
.Subscribe(_ => Connected = true);
|
||||||
|
|
||||||
|
this.WhenAnyValue(x => x.Connected)
|
||||||
|
.Where(b => b)
|
||||||
|
.Subscribe(_ =>
|
||||||
|
{
|
||||||
|
foreach (var item in NavItems)
|
||||||
|
{
|
||||||
|
item.IsVisible = true;
|
||||||
|
}
|
||||||
|
CurrentNavItem = NavItems[0];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
public RoutingState Router { get; set; } = new();
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
[Reactive]
|
||||||
|
public bool Connected { get; set; }
|
||||||
|
}
|
51
TorchRemote/ViewModels/Server/ChatViewModel.cs
Normal file
51
TorchRemote/ViewModels/Server/ChatViewModel.cs
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
using System;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Reactive;
|
||||||
|
using System.Reactive.Linq;
|
||||||
|
using System.Text.Json;
|
||||||
|
using ReactiveUI;
|
||||||
|
using ReactiveUI.Fody.Helpers;
|
||||||
|
using TorchRemote.Models.Responses;
|
||||||
|
using TorchRemote.Models.Shared;
|
||||||
|
using TorchRemote.Services;
|
||||||
|
namespace TorchRemote.ViewModels.Server;
|
||||||
|
|
||||||
|
public class ChatViewModel : ViewModelBase
|
||||||
|
{
|
||||||
|
public ChatViewModel(ApiClientService clientService)
|
||||||
|
{
|
||||||
|
Observable.FromEventPattern(clientService, nameof(clientService.Connected))
|
||||||
|
.ObserveOn(RxApp.MainThreadScheduler)
|
||||||
|
.Subscribe(_ =>
|
||||||
|
{
|
||||||
|
var options = new JsonSerializerOptions(JsonSerializerDefaults.Web);
|
||||||
|
Observable.FromAsync(clientService.WatchChatAsync)
|
||||||
|
.Select(b => b.MessageReceived)
|
||||||
|
.Concat()
|
||||||
|
.Select(b => JsonSerializer.Deserialize<ChatResponseBase>(b.Text, options))
|
||||||
|
.Select(b => b switch
|
||||||
|
{
|
||||||
|
ChatMessageResponse msg => $"[{msg.Channel}] {msg.AuthorName}: {msg.Message}",
|
||||||
|
ChatCommandResponse cmd => $"[Command] {cmd.Author}: {cmd.Message}",
|
||||||
|
_ => throw new ArgumentOutOfRangeException(nameof(b), b, null)
|
||||||
|
})
|
||||||
|
.ObserveOn(RxApp.MainThreadScheduler)
|
||||||
|
.Subscribe(s => ChatLines += $"{s}{Environment.NewLine}");
|
||||||
|
});
|
||||||
|
|
||||||
|
SendMessageCommand = ReactiveCommand.CreateFromTask<string>((s, t) => s.StartsWith("!") ?
|
||||||
|
clientService.InvokeCommandAsync(new(s[(s.IndexOf('!') + 1)..]), t) :
|
||||||
|
clientService.SendChatMessageAsync(new("Server", s, ChatChannel.GlobalScripted), t));
|
||||||
|
|
||||||
|
InvalidCommandPopup = SendMessageCommand.ThrownExceptions
|
||||||
|
.Where(b => b is HttpRequestException {StatusCode: HttpStatusCode.NotFound or HttpStatusCode.BadRequest})
|
||||||
|
.Select(_ => true);
|
||||||
|
}
|
||||||
|
[Reactive]
|
||||||
|
public string ChatLines { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public ReactiveCommand<string, Unit> SendMessageCommand { get; set; }
|
||||||
|
|
||||||
|
public IObservable<bool> InvalidCommandPopup { get; set; }
|
||||||
|
}
|
73
TorchRemote/ViewModels/Server/DashboardViewModel.cs
Normal file
73
TorchRemote/ViewModels/Server/DashboardViewModel.cs
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reactive;
|
||||||
|
using System.Reactive.Linq;
|
||||||
|
using System.Text.Json;
|
||||||
|
using ReactiveUI;
|
||||||
|
using ReactiveUI.Fody.Helpers;
|
||||||
|
using TorchRemote.Models.Responses;
|
||||||
|
using TorchRemote.Services;
|
||||||
|
namespace TorchRemote.ViewModels.Server;
|
||||||
|
|
||||||
|
public class DashboardViewModel : ViewModelBase
|
||||||
|
{
|
||||||
|
private readonly ApiClientService _clientService;
|
||||||
|
public DashboardViewModel(ApiClientService clientService)
|
||||||
|
{
|
||||||
|
_clientService = clientService;
|
||||||
|
|
||||||
|
Observable.FromEventPattern(_clientService, nameof(_clientService.Connected))
|
||||||
|
.ObserveOn(RxApp.MainThreadScheduler)
|
||||||
|
.Subscribe(_ =>
|
||||||
|
{
|
||||||
|
Observable.Timer(TimeSpan.Zero, TimeSpan.FromSeconds(10))
|
||||||
|
.Select(_ => Observable.FromAsync(t => _clientService.GetServerStatusAsync(t)))
|
||||||
|
.Concat()
|
||||||
|
.ObserveOn(RxApp.MainThreadScheduler)
|
||||||
|
.Subscribe(r =>
|
||||||
|
{
|
||||||
|
var (simSpeed, online, uptime, status) = r;
|
||||||
|
SimSpeed = simSpeed;
|
||||||
|
Status = status;
|
||||||
|
Uptime = uptime;
|
||||||
|
MemberCount = online;
|
||||||
|
});
|
||||||
|
|
||||||
|
var options = new JsonSerializerOptions(JsonSerializerDefaults.Web);
|
||||||
|
Observable.FromAsync(() => _clientService.WatchLogLinesAsync())
|
||||||
|
.Select(b => b.MessageReceived)
|
||||||
|
.Concat()
|
||||||
|
.Select(b => JsonSerializer.Deserialize<LogLineResponse>(b.Text, options))
|
||||||
|
.Select(b => $"{b.Time:hh:mm:ss} [{b.Level}] {(b.Logger.Contains('.') ? b.Logger[(b.Logger.LastIndexOf('.') + 1)..] : b.Logger)}: {b.Message}")
|
||||||
|
.ObserveOn(RxApp.MainThreadScheduler)
|
||||||
|
.Subscribe(s =>
|
||||||
|
{
|
||||||
|
if (LogLines.Count(b => b == '\n') > 1000)
|
||||||
|
LogLines = LogLines['\n'..];
|
||||||
|
LogLines += $"{s}\n";
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
StartCommand = ReactiveCommand.CreateFromTask(t => _clientService.StartServerAsync(t),
|
||||||
|
this.WhenAnyValue(x => x.Status)
|
||||||
|
.Select(b => b is ServerStatus.Stopped));
|
||||||
|
|
||||||
|
StopCommand = ReactiveCommand.CreateFromTask<bool>((b, t) => _clientService.StopServerAsync(new(b), t),
|
||||||
|
this.WhenAnyValue(x => x.Status)
|
||||||
|
.Select(b => b is ServerStatus.Running));
|
||||||
|
}
|
||||||
|
public ReactiveCommand<bool,Unit> StopCommand { get; set; }
|
||||||
|
public ReactiveCommand<Unit,Unit> StartCommand { get; set; }
|
||||||
|
|
||||||
|
[Reactive]
|
||||||
|
public double SimSpeed { get; set; }
|
||||||
|
[Reactive]
|
||||||
|
public ServerStatus Status { get; set; }
|
||||||
|
[Reactive]
|
||||||
|
public TimeSpan Uptime { get; set; }
|
||||||
|
[Reactive]
|
||||||
|
public string LogLines { get; set; } = string.Empty;
|
||||||
|
[Reactive]
|
||||||
|
public int MemberCount { get; set; }
|
||||||
|
}
|
10
TorchRemote/ViewModels/Server/PlayersViewModel.cs
Normal file
10
TorchRemote/ViewModels/Server/PlayersViewModel.cs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
using TorchRemote.Services;
|
||||||
|
namespace TorchRemote.ViewModels.Server;
|
||||||
|
|
||||||
|
public class PlayersViewModel : ViewModelBase
|
||||||
|
{
|
||||||
|
public PlayersViewModel(ApiClientService clientService)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
90
TorchRemote/ViewModels/Server/ServerConfigViewModel.cs
Normal file
90
TorchRemote/ViewModels/Server/ServerConfigViewModel.cs
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
using System;
|
||||||
|
using System.Reactive;
|
||||||
|
using System.Reactive.Linq;
|
||||||
|
using ReactiveUI;
|
||||||
|
using ReactiveUI.Fody.Helpers;
|
||||||
|
using TorchRemote.Services;
|
||||||
|
namespace TorchRemote.ViewModels.Server;
|
||||||
|
|
||||||
|
public class ServerConfigViewModel : ViewModelBase
|
||||||
|
{
|
||||||
|
public ServerConfigViewModel(ApiClientService clientService)
|
||||||
|
{
|
||||||
|
Observable.FromEventPattern(clientService, nameof(clientService.Connected))
|
||||||
|
.ObserveOn(RxApp.MainThreadScheduler)
|
||||||
|
.Select(_ => Observable.FromAsync(clientService.GetServerSettingsAsync))
|
||||||
|
.Concat()
|
||||||
|
.Subscribe(b =>
|
||||||
|
{
|
||||||
|
Name = b.ServerName;
|
||||||
|
MapName = b.MapName;
|
||||||
|
MemberLimit = b.MemberLimit;
|
||||||
|
Description = b.ServerDescription;
|
||||||
|
Ip = b.ListenEndPoint.Ip;
|
||||||
|
Port = b.ListenEndPoint.Port;
|
||||||
|
});
|
||||||
|
|
||||||
|
SaveCommand = ReactiveCommand.CreateFromTask(t =>
|
||||||
|
clientService.SetServerSettingsAsync(new(
|
||||||
|
Name,
|
||||||
|
MapName,
|
||||||
|
Description,
|
||||||
|
MemberLimit,
|
||||||
|
new(Ip, Port)
|
||||||
|
), t));
|
||||||
|
|
||||||
|
Worlds = Observable.FromEventPattern(clientService, nameof(clientService.Connected))
|
||||||
|
.ObserveOn(RxApp.MainThreadScheduler)
|
||||||
|
.Select(_ => Observable.FromAsync(clientService.GetWorldsAsync))
|
||||||
|
.Concat()
|
||||||
|
.SelectMany(ids => ids)
|
||||||
|
.Select(id => Observable.FromAsync(t => clientService.GetWorldAsync(id, t)).Select(b => new World(id, b.Name, b.SizeKb)))
|
||||||
|
.Concat();
|
||||||
|
|
||||||
|
Observable.FromEventPattern(clientService, nameof(clientService.Connected))
|
||||||
|
.ObserveOn(RxApp.MainThreadScheduler)
|
||||||
|
.Select(_ => Observable.FromAsync(clientService.GetSelectedWorld))
|
||||||
|
.Concat()
|
||||||
|
.Select(id => Observable.FromAsync(t => clientService.GetWorldAsync(id, t)).Select(b => new World(id, b.Name, b.SizeKb)))
|
||||||
|
.Concat()
|
||||||
|
.BindTo(this, x => x.SelectedWorld);
|
||||||
|
|
||||||
|
this.ObservableForProperty(x => x.SelectedWorld)
|
||||||
|
.Select(world => Observable.FromAsync(t => clientService.SelectWorldAsync(world.Value!.Id, t)))
|
||||||
|
.Concat()
|
||||||
|
.Subscribe(_ => { });
|
||||||
|
}
|
||||||
|
public ReactiveCommand<Unit,Unit> SaveCommand { get; set; }
|
||||||
|
|
||||||
|
[Reactive]
|
||||||
|
public string Name { get; set; } = null!;
|
||||||
|
[Reactive]
|
||||||
|
public string MapName { get; set; } = null!;
|
||||||
|
[Reactive]
|
||||||
|
public string Description { get; set; } = null!;
|
||||||
|
[Reactive]
|
||||||
|
public short MemberLimit { get; set; }
|
||||||
|
[Reactive]
|
||||||
|
public string Ip { get; set; } = null!;
|
||||||
|
[Reactive]
|
||||||
|
public int Port { get; set; }
|
||||||
|
|
||||||
|
public IObservable<World> Worlds { get; set; }
|
||||||
|
[Reactive]
|
||||||
|
public World? SelectedWorld { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class World : ReactiveObject
|
||||||
|
{
|
||||||
|
public World(Guid id, string name, long sizeKb)
|
||||||
|
{
|
||||||
|
Id = id;
|
||||||
|
Name = name;
|
||||||
|
SizeKb = sizeKb;
|
||||||
|
}
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
public string Name { get; set; }
|
||||||
|
public long SizeKb { get; set; }
|
||||||
|
|
||||||
|
public string SizeString => SizeKb > 1024 ? $"{SizeKb / 1024:N1} MB" : $"{SizeKb:N1} KB";
|
||||||
|
}
|
28
TorchRemote/ViewModels/Server/ServerNavItem.cs
Normal file
28
TorchRemote/ViewModels/Server/ServerNavItem.cs
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
using System.Reactive.Linq;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using FluentAvalonia.UI.Controls;
|
||||||
|
using ReactiveUI;
|
||||||
|
using ReactiveUI.Fody.Helpers;
|
||||||
|
namespace TorchRemote.ViewModels.Server;
|
||||||
|
|
||||||
|
public class ServerNavItem : ReactiveObject
|
||||||
|
{
|
||||||
|
public ServerNavItem(string title, Symbol icon, ViewModelBase viewModel)
|
||||||
|
{
|
||||||
|
Title = title;
|
||||||
|
Icon = icon;
|
||||||
|
ViewModel = viewModel;
|
||||||
|
|
||||||
|
this.WhenAnyValue(x => x.Icon)
|
||||||
|
.Select(b => new SymbolIcon {Symbol = b})
|
||||||
|
.BindTo(this, x => x.IconElement);
|
||||||
|
}
|
||||||
|
public string Title { get; set; }
|
||||||
|
public Symbol Icon { get; set; }
|
||||||
|
public ViewModelBase ViewModel { get; set; }
|
||||||
|
[JsonIgnore]
|
||||||
|
[Reactive]
|
||||||
|
public bool IsVisible { get; set; }
|
||||||
|
[JsonIgnore]
|
||||||
|
public IconElement IconElement { get; set; } = null!;
|
||||||
|
}
|
25
TorchRemote/ViewModels/Server/SettingsViewModel.cs
Normal file
25
TorchRemote/ViewModels/Server/SettingsViewModel.cs
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
using DynamicData.Binding;
|
||||||
|
using ReactiveUI;
|
||||||
|
using ReactiveUI.Fody.Helpers;
|
||||||
|
using TorchRemote.Services;
|
||||||
|
namespace TorchRemote.ViewModels.Server;
|
||||||
|
|
||||||
|
public class SettingsViewModel : ViewModelBase
|
||||||
|
{
|
||||||
|
private readonly ApiClientService _clientService;
|
||||||
|
[Reactive]
|
||||||
|
public string BearerToken { get; set; } = "WcdYT5qHjSt5Uzjs54xu8vE9Oq4a5MD2edLxywtJHtc=";
|
||||||
|
[Reactive]
|
||||||
|
public string RemoteUrl { get; set; } = "http://localhost";
|
||||||
|
|
||||||
|
public SettingsViewModel(ApiClientService clientService)
|
||||||
|
{
|
||||||
|
_clientService = clientService;
|
||||||
|
|
||||||
|
this.WhenValueChanged(x => x.BearerToken)
|
||||||
|
.BindTo(_clientService, x => x.BearerToken);
|
||||||
|
|
||||||
|
this.WhenValueChanged(x => x.RemoteUrl)
|
||||||
|
.BindTo(_clientService, x => x.BaseUrl);
|
||||||
|
}
|
||||||
|
}
|
8
TorchRemote/ViewModels/TabViewModelBase.cs
Normal file
8
TorchRemote/ViewModels/TabViewModelBase.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
namespace TorchRemote.ViewModels;
|
||||||
|
|
||||||
|
[JsonDerivedType(typeof(RemoteServerViewModel))]
|
||||||
|
public abstract class TabViewModelBase : ViewModelBase
|
||||||
|
{
|
||||||
|
public abstract string Header { get; set; }
|
||||||
|
}
|
10
TorchRemote/ViewModels/ViewModelBase.cs
Normal file
10
TorchRemote/ViewModels/ViewModelBase.cs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
using ReactiveUI;
|
||||||
|
|
||||||
|
namespace TorchRemote.ViewModels
|
||||||
|
{
|
||||||
|
public class ViewModelBase : ReactiveObject, IRoutableViewModel
|
||||||
|
{
|
||||||
|
public string? UrlPathSegment { get; set; }
|
||||||
|
public IScreen HostScreen { get; set; }
|
||||||
|
}
|
||||||
|
}
|
82
TorchRemote/Views/MainWindow.axaml
Normal file
82
TorchRemote/Views/MainWindow.axaml
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
<Window xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:vm="using:TorchRemote.ViewModels"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:reactiveUi="http://reactiveui.net"
|
||||||
|
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||||
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
|
x:Class="TorchRemote.Views.MainWindow"
|
||||||
|
Icon="/Assets/avalonia-logo.ico"
|
||||||
|
Title="Torch Remote"
|
||||||
|
TransparencyLevelHint="AcrylicBlur"
|
||||||
|
Background="Transparent"
|
||||||
|
ExtendClientAreaToDecorationsHint="True"
|
||||||
|
ExtendClientAreaChromeHints="NoChrome"
|
||||||
|
ExtendClientAreaTitleBarHeightHint="-1">
|
||||||
|
|
||||||
|
<Design.DataContext>
|
||||||
|
<vm:MainWindowViewModel />
|
||||||
|
</Design.DataContext>
|
||||||
|
|
||||||
|
<Panel>
|
||||||
|
<ExperimentalAcrylicBorder IsHitTestVisible="False">
|
||||||
|
<ExperimentalAcrylicBorder.Material>
|
||||||
|
<ExperimentalAcrylicMaterial TintColor="Black"
|
||||||
|
TintOpacity="0.4"
|
||||||
|
MaterialOpacity="0.1"
|
||||||
|
BackgroundSource="Digger"/>
|
||||||
|
</ExperimentalAcrylicBorder.Material>
|
||||||
|
</ExperimentalAcrylicBorder>
|
||||||
|
|
||||||
|
<Grid RowDefinitions="Auto,*">
|
||||||
|
<StackPanel Grid.Row="0" Orientation="Horizontal" IsHitTestVisible="False">
|
||||||
|
<Image Margin="12,4"
|
||||||
|
Source="/Assets/avalonia-logo.ico"
|
||||||
|
Width="18" Height="18"
|
||||||
|
DockPanel.Dock="Left"
|
||||||
|
Name="WindowIcon" />
|
||||||
|
<TextBlock
|
||||||
|
Text="{Binding $parent[Window].Title}"
|
||||||
|
FontSize="12"
|
||||||
|
VerticalAlignment="Center" />
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel Grid.Row="0" HorizontalAlignment="Right" Margin="0,5,0,0" Orientation="Horizontal">
|
||||||
|
<ui:Button Margin="0,0,5,0"
|
||||||
|
Classes="AppBarButton"
|
||||||
|
Command="{Binding $parent[Window].set_WindowState}"
|
||||||
|
CommandParameter="Minimized">
|
||||||
|
<ui:SymbolIcon Symbol="Remove" FontSize="18" />
|
||||||
|
</ui:Button>
|
||||||
|
<ui:Button Margin="0,0,5,0"
|
||||||
|
Classes="AppBarButton"
|
||||||
|
Command="{Binding $parent[Window].Close}">
|
||||||
|
<ui:SymbolIcon Symbol="Dismiss" FontSize="18" />
|
||||||
|
</ui:Button>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<TabControl Grid.Row="1" Items="{Binding Tabs}">
|
||||||
|
<TabControl.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<StackPanel Orientation="Horizontal">
|
||||||
|
<Image Source="../Assets/torchicon.ico"
|
||||||
|
Margin="12,4"
|
||||||
|
Width="18"
|
||||||
|
Height="18" />
|
||||||
|
<TextBlock Text="{Binding Header}"
|
||||||
|
FontSize="12"
|
||||||
|
Margin="0,0,12,0"
|
||||||
|
VerticalAlignment="Center"/>
|
||||||
|
</StackPanel>
|
||||||
|
</DataTemplate>
|
||||||
|
</TabControl.ItemTemplate>
|
||||||
|
<TabControl.ContentTemplate>
|
||||||
|
<DataTemplate DataType="{x:Type vm:TabViewModelBase}">
|
||||||
|
<reactiveUi:ViewModelViewHost Margin="3" ViewModel="{Binding}" />
|
||||||
|
</DataTemplate>
|
||||||
|
</TabControl.ContentTemplate>
|
||||||
|
</TabControl>
|
||||||
|
</Grid>
|
||||||
|
</Panel>
|
||||||
|
</Window>
|
12
TorchRemote/Views/MainWindow.axaml.cs
Normal file
12
TorchRemote/Views/MainWindow.axaml.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
using Avalonia.Controls;
|
||||||
|
|
||||||
|
namespace TorchRemote.Views
|
||||||
|
{
|
||||||
|
public partial class MainWindow : Window
|
||||||
|
{
|
||||||
|
public MainWindow()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
43
TorchRemote/Views/RemoteServerView.axaml
Normal file
43
TorchRemote/Views/RemoteServerView.axaml
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
<UserControl xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:fuc="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||||
|
xmlns:viewModels="clr-namespace:TorchRemote.ViewModels"
|
||||||
|
xmlns:reactiveUi="http://reactiveui.net"
|
||||||
|
xmlns:server="clr-namespace:TorchRemote.ViewModels.Server"
|
||||||
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
|
x:Class="TorchRemote.Views.RemoteServerView">
|
||||||
|
<Design.DataContext>
|
||||||
|
<viewModels:RemoteServerViewModel />
|
||||||
|
</Design.DataContext>
|
||||||
|
<UserControl.Styles>
|
||||||
|
<Style Selector="fuc|NavigationView /template/ Border#ContentGridBorder">
|
||||||
|
<Setter Property="Background" Value="Transparent" />
|
||||||
|
<Setter Property="BorderBrush" Value="Transparent" />
|
||||||
|
</Style>
|
||||||
|
</UserControl.Styles>
|
||||||
|
<fuc:NavigationView MenuItems="{Binding NavItems}"
|
||||||
|
SelectedItem="{Binding CurrentNavItem, Mode=TwoWay}"
|
||||||
|
PaneDisplayMode="LeftCompact"
|
||||||
|
IsSettingsVisible="False">
|
||||||
|
<fuc:NavigationView.MenuItemTemplate>
|
||||||
|
<DataTemplate DataType="{x:Type server:ServerNavItem}">
|
||||||
|
<fuc:NavigationViewItem Content="{Binding Title}"
|
||||||
|
Icon="{Binding IconElement}"
|
||||||
|
IsVisible="{Binding IsVisible}"/>
|
||||||
|
</DataTemplate>
|
||||||
|
</fuc:NavigationView.MenuItemTemplate>
|
||||||
|
<Panel>
|
||||||
|
<Grid Margin="3" RowDefinitions="Auto,*">
|
||||||
|
<TextBlock Text="{Binding CurrentNavItem.Title}" FontSize="24" FontWeight="Bold" />
|
||||||
|
<reactiveUi:RoutedViewHost Grid.Row="1" Margin="0,10" Router="{Binding Router}" />
|
||||||
|
</Grid>
|
||||||
|
<TextBlock Text="Loading..."
|
||||||
|
IsVisible="{Binding !Connected}"
|
||||||
|
IsHitTestVisible="False"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Center"/>
|
||||||
|
</Panel>
|
||||||
|
</fuc:NavigationView>
|
||||||
|
</UserControl>
|
21
TorchRemote/Views/RemoteServerView.axaml.cs
Normal file
21
TorchRemote/Views/RemoteServerView.axaml.cs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Markup.Xaml;
|
||||||
|
using Avalonia.ReactiveUI;
|
||||||
|
using TorchRemote.ViewModels;
|
||||||
|
|
||||||
|
namespace TorchRemote.Views;
|
||||||
|
|
||||||
|
public partial class RemoteServerView : ReactiveUserControl<RemoteServerViewModel>
|
||||||
|
{
|
||||||
|
public RemoteServerView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeComponent()
|
||||||
|
{
|
||||||
|
AvaloniaXamlLoader.Load(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
45
TorchRemote/Views/Server/ChatView.axaml
Normal file
45
TorchRemote/Views/Server/ChatView.axaml
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
<UserControl xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:server="clr-namespace:TorchRemote.ViewModels.Server"
|
||||||
|
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||||
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
|
x:Class="TorchRemote.Views.Server.ChatView">
|
||||||
|
<Design.DataContext>
|
||||||
|
<server:ChatViewModel/>
|
||||||
|
</Design.DataContext>
|
||||||
|
<Grid RowDefinitions="*,Auto">
|
||||||
|
<ExperimentalAcrylicBorder CornerRadius="6" Margin="5,8">
|
||||||
|
<ExperimentalAcrylicBorder.Material>
|
||||||
|
<ExperimentalAcrylicMaterial TintColor="Black"
|
||||||
|
TintOpacity="0.8"
|
||||||
|
MaterialOpacity="0.6"
|
||||||
|
BackgroundSource="Digger"/>
|
||||||
|
</ExperimentalAcrylicBorder.Material>
|
||||||
|
<Grid>
|
||||||
|
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto">
|
||||||
|
<TextBlock Margin="3,5" Text="{Binding ChatLines}" />
|
||||||
|
</ScrollViewer>
|
||||||
|
</Grid>
|
||||||
|
</ExperimentalAcrylicBorder>
|
||||||
|
<Grid Grid.Row="1" ColumnDefinitions="*,Auto" Margin="3">
|
||||||
|
<TextBox Name="MessageBox" Watermark="Enter chat message or command..." />
|
||||||
|
<Button Grid.Column="1"
|
||||||
|
Margin="10,0,0,0"
|
||||||
|
IsDefault="True"
|
||||||
|
IsEnabled="{Binding #MessageBox.Text, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
|
||||||
|
CommandParameter="{Binding #MessageBox.Text}"
|
||||||
|
Command="{Binding SendMessageCommand}">
|
||||||
|
<controls:SymbolIcon Symbol="Send" />
|
||||||
|
</Button>
|
||||||
|
<Popup Grid.Column="0"
|
||||||
|
IsLightDismissEnabled="True"
|
||||||
|
IsOpen="{Binding InvalidCommandPopup^}"
|
||||||
|
PlacementMode="Top"
|
||||||
|
PlacementTarget="MessageBox">
|
||||||
|
<TextPresenter Text="Invalid Command" FontSize="24" FontWeight="Bold" />
|
||||||
|
</Popup>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</UserControl>
|
21
TorchRemote/Views/Server/ChatView.axaml.cs
Normal file
21
TorchRemote/Views/Server/ChatView.axaml.cs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Markup.Xaml;
|
||||||
|
using Avalonia.ReactiveUI;
|
||||||
|
using TorchRemote.ViewModels.Server;
|
||||||
|
|
||||||
|
namespace TorchRemote.Views.Server;
|
||||||
|
|
||||||
|
public partial class ChatView : ReactiveUserControl<ChatViewModel>
|
||||||
|
{
|
||||||
|
public ChatView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeComponent()
|
||||||
|
{
|
||||||
|
AvaloniaXamlLoader.Load(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
65
TorchRemote/Views/Server/DashboardView.axaml
Normal file
65
TorchRemote/Views/Server/DashboardView.axaml
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
<UserControl xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:server="clr-namespace:TorchRemote.ViewModels.Server"
|
||||||
|
xmlns:fuc="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||||
|
xmlns:flyouts="clr-namespace:TorchRemote.Controls.Flyouts"
|
||||||
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
|
x:Class="TorchRemote.Views.Server.DashboardView">
|
||||||
|
<Design.DataContext>
|
||||||
|
<server:DashboardViewModel/>
|
||||||
|
</Design.DataContext>
|
||||||
|
<Grid RowDefinitions="Auto,*">
|
||||||
|
<StackPanel Orientation="Horizontal">
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="5,0">
|
||||||
|
<TextBlock Text="Status:" FontWeight="Heavy"/>
|
||||||
|
<TextBlock Text="{Binding Status}" Margin="5,0" />
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="5,0">
|
||||||
|
<TextBlock Text="Sim:" FontWeight="Heavy"/>
|
||||||
|
<TextBlock Text="{Binding SimSpeed, StringFormat={}{0:0.00}}" Margin="5,0" />
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="5,0">
|
||||||
|
<TextBlock Text="Uptime:" FontWeight="Heavy"/>
|
||||||
|
<TextBlock Text="{Binding Uptime, StringFormat={}{0:hh\\:mm\\.ss}}" Margin="5,0" />
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="5,0">
|
||||||
|
<TextBlock Text="Online:" FontWeight="Heavy"/>
|
||||||
|
<TextBlock Text="{Binding MemberCount}" Margin="5,0" />
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="5,0">
|
||||||
|
<fuc:Button Command="{Binding StartCommand}">
|
||||||
|
<StackPanel Orientation="Horizontal">
|
||||||
|
<fuc:SymbolIcon Symbol="Up" Margin="5,0"/>
|
||||||
|
<TextBlock Text="Start" Margin="0,0,5,0" />
|
||||||
|
</StackPanel>
|
||||||
|
</fuc:Button>
|
||||||
|
<fuc:Button Margin="5,0" IsEnabled="{Binding StopCommand.CanExecute^}">
|
||||||
|
<fuc:Button.Flyout>
|
||||||
|
<flyouts:MessageConfirmDismissFlyout Message="Save before Stop?"
|
||||||
|
ResultCommand="{Binding StopCommand}"/>
|
||||||
|
</fuc:Button.Flyout>
|
||||||
|
<StackPanel Orientation="Horizontal">
|
||||||
|
<fuc:SymbolIcon Symbol="Dismiss" Margin="5,0"/>
|
||||||
|
<TextBlock Text="Stop" Margin="0,0,5,0" />
|
||||||
|
</StackPanel>
|
||||||
|
</fuc:Button>
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<ExperimentalAcrylicBorder Grid.Row="1" CornerRadius="6" Margin="5,8">
|
||||||
|
<ExperimentalAcrylicBorder.Material>
|
||||||
|
<ExperimentalAcrylicMaterial TintColor="Black"
|
||||||
|
TintOpacity="0.8"
|
||||||
|
MaterialOpacity="0.6"
|
||||||
|
BackgroundSource="Digger"/>
|
||||||
|
</ExperimentalAcrylicBorder.Material>
|
||||||
|
<Grid>
|
||||||
|
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto">
|
||||||
|
<TextBlock Margin="3,5" Text="{Binding LogLines}" />
|
||||||
|
</ScrollViewer>
|
||||||
|
</Grid>
|
||||||
|
</ExperimentalAcrylicBorder>
|
||||||
|
</Grid>
|
||||||
|
</UserControl>
|
21
TorchRemote/Views/Server/DashboardView.axaml.cs
Normal file
21
TorchRemote/Views/Server/DashboardView.axaml.cs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Markup.Xaml;
|
||||||
|
using Avalonia.ReactiveUI;
|
||||||
|
using TorchRemote.ViewModels.Server;
|
||||||
|
|
||||||
|
namespace TorchRemote.Views.Server;
|
||||||
|
|
||||||
|
public partial class DashboardView : ReactiveUserControl<DashboardViewModel>
|
||||||
|
{
|
||||||
|
public DashboardView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeComponent()
|
||||||
|
{
|
||||||
|
AvaloniaXamlLoader.Load(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
8
TorchRemote/Views/Server/PlayersView.axaml
Normal file
8
TorchRemote/Views/Server/PlayersView.axaml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<UserControl xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
|
x:Class="TorchRemote.Views.Server.PlayersView">
|
||||||
|
|
||||||
|
</UserControl>
|
21
TorchRemote/Views/Server/PlayersView.axaml.cs
Normal file
21
TorchRemote/Views/Server/PlayersView.axaml.cs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Markup.Xaml;
|
||||||
|
using Avalonia.ReactiveUI;
|
||||||
|
using TorchRemote.ViewModels.Server;
|
||||||
|
|
||||||
|
namespace TorchRemote.Views.Server;
|
||||||
|
|
||||||
|
public partial class PlayersView : ReactiveUserControl<PlayersViewModel>
|
||||||
|
{
|
||||||
|
public PlayersView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeComponent()
|
||||||
|
{
|
||||||
|
AvaloniaXamlLoader.Load(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
61
TorchRemote/Views/Server/ServerConfigView.axaml
Normal file
61
TorchRemote/Views/Server/ServerConfigView.axaml
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
<UserControl xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:server="clr-namespace:TorchRemote.ViewModels.Server"
|
||||||
|
xmlns:fuc="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||||
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
|
x:Class="TorchRemote.Views.Server.ServerConfigView">
|
||||||
|
<Design.DataContext>
|
||||||
|
<server:ServerConfigViewModel/>
|
||||||
|
</Design.DataContext>
|
||||||
|
<StackPanel>
|
||||||
|
<Grid ColumnDefinitions="1*,1*">
|
||||||
|
<TextBlock Text="Name"/>
|
||||||
|
<TextBox Grid.Column="1" Text="{Binding Name}" />
|
||||||
|
</Grid>
|
||||||
|
<Grid ColumnDefinitions="1*,1*" Margin="0,10,0,0">
|
||||||
|
<TextBlock Text="Map Name"/>
|
||||||
|
<TextBox Grid.Column="1" Text="{Binding MapName}" />
|
||||||
|
</Grid>
|
||||||
|
<Grid ColumnDefinitions="1*,1*" Margin="0,10,0,0">
|
||||||
|
<TextBlock Text="Description"/>
|
||||||
|
<TextBox Grid.Column="1" AcceptsReturn="True" Text="{Binding Description}" />
|
||||||
|
</Grid>
|
||||||
|
<Grid ColumnDefinitions="1*,1*" Margin="0,10,0,0">
|
||||||
|
<TextBlock Text="Member Limit"/>
|
||||||
|
<NumericUpDown Grid.Column="1" Minimum="0" Maximum="255" Value="{Binding MemberLimit}" />
|
||||||
|
</Grid>
|
||||||
|
<Grid ColumnDefinitions="1*,1*" Margin="0,10,0,0">
|
||||||
|
<TextBlock Text="IP Address"/>
|
||||||
|
<Grid Grid.Column="1" ColumnDefinitions="3*,Auto,1*">
|
||||||
|
<TextBox Text="{Binding Ip}" />
|
||||||
|
<TextBlock Grid.Column="1" Text=":" FontSize="18" FontWeight="Bold" Margin="3,0" />
|
||||||
|
<NumericUpDown Grid.Column="2" Value="{Binding Port}" />
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
<Grid ColumnDefinitions="1*,1*" Margin="0,10,0,0">
|
||||||
|
<TextBlock Text="Load World"/>
|
||||||
|
<ComboBox Grid.Column="1" Items="{Binding Worlds^}"
|
||||||
|
SelectedItem="{Binding SelectedWorld}"
|
||||||
|
AutoScrollToSelectedItem="True">
|
||||||
|
<ComboBox.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<StackPanel Orientation="Horizontal">
|
||||||
|
<TextBlock Text="{Binding Name}"/>
|
||||||
|
<TextBlock Text="{Binding SizeString}" Margin="8,0,0,0"/>
|
||||||
|
</StackPanel>
|
||||||
|
</DataTemplate>
|
||||||
|
</ComboBox.ItemTemplate>
|
||||||
|
</ComboBox>
|
||||||
|
</Grid>
|
||||||
|
<fuc:Button Command="{Binding SaveCommand}"
|
||||||
|
Classes="accent"
|
||||||
|
Width="175">
|
||||||
|
<StackPanel Orientation="Horizontal">
|
||||||
|
<fuc:SymbolIcon Symbol="Save" Margin="5,0"/>
|
||||||
|
<TextBlock Text="Save" Margin="0,0,5,0" />
|
||||||
|
</StackPanel>
|
||||||
|
</fuc:Button>
|
||||||
|
</StackPanel>
|
||||||
|
</UserControl>
|
21
TorchRemote/Views/Server/ServerConfigView.axaml.cs
Normal file
21
TorchRemote/Views/Server/ServerConfigView.axaml.cs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Markup.Xaml;
|
||||||
|
using Avalonia.ReactiveUI;
|
||||||
|
using TorchRemote.ViewModels.Server;
|
||||||
|
|
||||||
|
namespace TorchRemote.Views.Server;
|
||||||
|
|
||||||
|
public partial class ServerConfigView : ReactiveUserControl<ServerConfigViewModel>
|
||||||
|
{
|
||||||
|
public ServerConfigView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeComponent()
|
||||||
|
{
|
||||||
|
AvaloniaXamlLoader.Load(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
29
TorchRemote/Views/Server/SettingsView.axaml
Normal file
29
TorchRemote/Views/Server/SettingsView.axaml
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<UserControl xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:viewModels="clr-namespace:TorchRemote.ViewModels"
|
||||||
|
xmlns:fuc="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||||
|
xmlns:server="clr-namespace:TorchRemote.ViewModels.Server"
|
||||||
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
|
x:Class="TorchRemote.Views.Server.SettingsView">
|
||||||
|
<Design.DataContext>
|
||||||
|
<!-- ReSharper disable once Xaml.ConstructorWarning -->
|
||||||
|
<server:SettingsViewModel />
|
||||||
|
</Design.DataContext>
|
||||||
|
<UserControl.Styles>
|
||||||
|
<Style Selector="fuc|NavigationView /template/ Border#ContentGridBorder">
|
||||||
|
<Setter Property="Background" Value="Transparent" />
|
||||||
|
<Setter Property="BorderBrush" Value="Transparent" />
|
||||||
|
</Style>
|
||||||
|
</UserControl.Styles>
|
||||||
|
<StackPanel>
|
||||||
|
<Grid ColumnDefinitions="1*,2*"
|
||||||
|
RowDefinitions="1*,1*">
|
||||||
|
<TextBlock Text="Url" />
|
||||||
|
<TextBox Grid.Row="0" Grid.Column="1" Margin="3" Text="{Binding RemoteUrl}" />
|
||||||
|
<TextBlock Grid.Row="1" Grid.Column="0" Text="Token" />
|
||||||
|
<TextBox Grid.Row="1" Grid.Column="1" Margin="3" Text="{Binding BearerToken}" />
|
||||||
|
</Grid>
|
||||||
|
</StackPanel>
|
||||||
|
</UserControl>
|
21
TorchRemote/Views/Server/SettingsView.axaml.cs
Normal file
21
TorchRemote/Views/Server/SettingsView.axaml.cs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Markup.Xaml;
|
||||||
|
using Avalonia.ReactiveUI;
|
||||||
|
using TorchRemote.ViewModels.Server;
|
||||||
|
|
||||||
|
namespace TorchRemote.Views.Server;
|
||||||
|
|
||||||
|
public partial class SettingsView : ReactiveUserControl<SettingsViewModel>
|
||||||
|
{
|
||||||
|
public SettingsView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeComponent()
|
||||||
|
{
|
||||||
|
AvaloniaXamlLoader.Load(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Reference in New Issue
Block a user