vddCore 2 gadi atpakaļ
revīzija
b597f495d6
73 mainītis faili ar 1862 papildinājumiem un 0 dzēšanām
  1. 5 0
      .gitignore
  2. 3 0
      .gitmodules
  3. 8 0
      .idea/.idea.Neptune/.idea/.gitignore
  4. 7 0
      .idea/.idea.Neptune/.idea/discord.xml
  5. 4 0
      .idea/.idea.Neptune/.idea/encodings.xml
  6. 8 0
      .idea/.idea.Neptune/.idea/indexLayout.xml
  7. 6 0
      .idea/.idea.Neptune/.idea/misc.xml
  8. 6 0
      .idea/.idea.Neptune/.idea/vcs.xml
  9. 1 0
      Glitonea
  10. 22 0
      Neptune.sln
  11. 454 0
      Neptune/.gitignore
  12. 13 0
      Neptune/App.axaml
  13. 29 0
      Neptune/App.axaml.cs
  14. 3 0
      Neptune/FodyWeavers.xml
  15. 8 0
      Neptune/Infrastructure/Messaging/ProjectClosedMessage.cs
  16. 15 0
      Neptune/Infrastructure/Messaging/ProjectLoadedMessage.cs
  17. 15 0
      Neptune/Infrastructure/Messaging/SelectedConfigMemberChangedMessage.cs
  18. 87 0
      Neptune/Infrastructure/Services/AppStorageService.cs
  19. 14 0
      Neptune/Infrastructure/Services/IAppStorageService.cs
  20. 12 0
      Neptune/Infrastructure/Services/IProjectService.cs
  21. 13 0
      Neptune/Infrastructure/Services/IRecentProjectService.cs
  22. 71 0
      Neptune/Infrastructure/Services/ProjectService.cs
  23. 49 0
      Neptune/Infrastructure/Services/RecentProjectService.cs
  24. 105 0
      Neptune/Model/ProjectManagement/ConfigMember.cs
  25. 55 0
      Neptune/Model/ProjectManagement/ConfigMemberType.cs
  26. 33 0
      Neptune/Model/ProjectManagement/Project.cs
  27. 30 0
      Neptune/Model/Storage/AppStorage.cs
  28. 80 0
      Neptune/Neptune.csproj
  29. 20 0
      Neptune/Program.cs
  30. 34 0
      Neptune/Resources/Definitions/ImageDefinitions.axaml
  31. BIN
      Neptune/Resources/Fonts/OpenSans.ttf
  32. BIN
      Neptune/Resources/Images/Raster/16/Exit.png
  33. BIN
      Neptune/Resources/Images/Raster/16/History.png
  34. BIN
      Neptune/Resources/Images/Raster/16/Interface.png
  35. BIN
      Neptune/Resources/Images/Raster/16/Key.png
  36. BIN
      Neptune/Resources/Images/Raster/16/Map.png
  37. BIN
      Neptune/Resources/Images/Raster/16/OpenDoor.png
  38. BIN
      Neptune/Resources/Images/Raster/16/OpenFolder.png
  39. BIN
      Neptune/Resources/Images/Raster/16/QuestionMark.png
  40. BIN
      Neptune/Resources/Images/Raster/16/Save.png
  41. BIN
      Neptune/Resources/Images/Raster/24/Adventure.png
  42. BIN
      Neptune/Resources/Images/Raster/24/ArcherArrow.png
  43. BIN
      Neptune/Resources/Images/Raster/24/ArcherBow.png
  44. BIN
      Neptune/Resources/Images/Raster/24/Binary.png
  45. BIN
      Neptune/Resources/Images/Raster/24/Boxes.png
  46. BIN
      Neptune/Resources/Images/Raster/24/BoxesPin.png
  47. BIN
      Neptune/Resources/Images/Raster/24/Cube.png
  48. BIN
      Neptune/Resources/Images/Raster/24/Interface.png
  49. BIN
      Neptune/Resources/Images/Raster/24/Key.png
  50. BIN
      Neptune/Resources/Images/Raster/24/Loot.png
  51. BIN
      Neptune/Resources/Images/Raster/24/MusicNote.png
  52. BIN
      Neptune/Resources/Images/Raster/24/OpenDoor.png
  53. BIN
      Neptune/Resources/Images/Raster/24/Person.png
  54. BIN
      Neptune/Resources/Images/Raster/24/PersonLocation.png
  55. BIN
      Neptune/Resources/Images/Raster/24/PlaceMarker.png
  56. BIN
      Neptune/Resources/Images/Raster/24/QuestionMark.png
  57. BIN
      Neptune/Resources/Images/Raster/24/Shop.png
  58. BIN
      Neptune/Resources/Images/Raster/24/Unicorn.png
  59. 54 0
      Neptune/Resources/Styles/BaseAppStyle.axaml
  60. 54 0
      Neptune/Resources/Styles/Controls/MenuItem.axaml
  61. 8 0
      Neptune/Resources/Styles/Controls/ProjectExplorer/ListBox.axaml
  62. 25 0
      Neptune/Resources/Styles/Controls/ProjectExplorer/ListBoxItem.axaml
  63. 5 0
      Neptune/Roots.xml
  64. 41 0
      Neptune/View/Controls/EditorHost.axaml
  65. 20 0
      Neptune/View/Controls/EditorHost.axaml.cs
  66. 35 0
      Neptune/View/Controls/ProjectExplorer.axaml
  67. 20 0
      Neptune/View/Controls/ProjectExplorer.axaml.cs
  68. 164 0
      Neptune/View/Windows/MainWindow.axaml
  69. 14 0
      Neptune/View/Windows/MainWindow.axaml.cs
  70. 22 0
      Neptune/ViewModel/Controls/EditorHostViewModel.cs
  71. 42 0
      Neptune/ViewModel/Controls/ProjectExplorerViewModel.cs
  72. 130 0
      Neptune/ViewModel/Windows/MainWindowViewModel.cs
  73. 18 0
      Neptune/app.manifest

+ 5 - 0
.gitignore

@@ -0,0 +1,5 @@
+bin/
+obj/
+/packages/
+riderModule.iml
+/_ReSharper.Caches/

+ 3 - 0
.gitmodules

@@ -0,0 +1,3 @@
+[submodule "Glitonea"]
+	path = Glitonea
+	url = git@github.com:vddCore/Glitonea.git

+ 8 - 0
.idea/.idea.Neptune/.idea/.gitignore

@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Rider ignored files
+/.idea.Neptune.iml
+/modules.xml
+/projectSettingsUpdater.xml
+/contentModel.xml

+ 7 - 0
.idea/.idea.Neptune/.idea/discord.xml

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="DiscordProjectSettings">
+    <option name="show" value="PROJECT_FILES" />
+    <option name="description" value="" />
+  </component>
+</project>

+ 4 - 0
.idea/.idea.Neptune/.idea/encodings.xml

@@ -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 - 0
.idea/.idea.Neptune/.idea/indexLayout.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="UserContentModel">
+    <attachedFolders />
+    <explicitIncludes />
+    <explicitExcludes />
+  </component>
+</project>

+ 6 - 0
.idea/.idea.Neptune/.idea/misc.xml

@@ -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 - 0
.idea/.idea.Neptune/.idea/vcs.xml

@@ -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>

+ 1 - 0
Glitonea

@@ -0,0 +1 @@
+Subproject commit 559e69a2dfe7e5b7f0b4f4329c84b4a9bbbec8ad

+ 22 - 0
Neptune.sln

@@ -0,0 +1,22 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Neptune", "Neptune\Neptune.csproj", "{9B6FCBB8-E7B8-4DDC-A192-7E2C80EB8523}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Glitonea", "Glitonea\Glitonea.csproj", "{41698457-5A66-4304-8F3E-7D551945F538}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Any CPU = Debug|Any CPU
+		Release|Any CPU = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{9B6FCBB8-E7B8-4DDC-A192-7E2C80EB8523}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{9B6FCBB8-E7B8-4DDC-A192-7E2C80EB8523}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{9B6FCBB8-E7B8-4DDC-A192-7E2C80EB8523}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{9B6FCBB8-E7B8-4DDC-A192-7E2C80EB8523}.Release|Any CPU.Build.0 = Release|Any CPU
+		{41698457-5A66-4304-8F3E-7D551945F538}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{41698457-5A66-4304-8F3E-7D551945F538}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{41698457-5A66-4304-8F3E-7D551945F538}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{41698457-5A66-4304-8F3E-7D551945F538}.Release|Any CPU.Build.0 = Release|Any CPU
+	EndGlobalSection
+EndGlobal

+ 454 - 0
Neptune/.gitignore

@@ -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 - 0
Neptune/App.axaml

@@ -0,0 +1,13 @@
+<Application x:Class="Neptune.App"
+             xmlns="https://github.com/avaloniaui"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:glitonea="clr-namespace:Glitonea;assembly=Glitonea"
+             xmlns:converters="clr-namespace:Glitonea.Mvvm.Converters;assembly=Glitonea">
+    <Application.Resources>
+        <converters:EnumDescriptionConverter x:Key="EnumDescriptionConverter" />    
+    </Application.Resources>
+    
+    <Application.Styles>
+        <StyleInclude Source="avares://Neptune/Resources/Styles/BaseAppStyle.axaml" />
+    </Application.Styles>
+</Application>

+ 29 - 0
Neptune/App.axaml.cs

@@ -0,0 +1,29 @@
+using Avalonia;
+using Avalonia.Controls.ApplicationLifetimes;
+using Avalonia.Markup.Xaml;
+using Glitonea;
+using PropertyChanged;
+
+namespace Neptune
+{
+    [DoNotNotify]
+    public partial class App : Application
+    {
+        public override void Initialize()
+        {
+            AvaloniaXamlLoader.Load(this);
+        }
+        
+        public override void OnFrameworkInitializationCompleted()
+        {
+            GlitoneaCore.Initialize();
+            
+            if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
+            {
+                desktop.MainWindow = new View.Windows.MainWindow();
+            }
+
+            base.OnFrameworkInitializationCompleted();
+        }
+    }
+}

+ 3 - 0
Neptune/FodyWeavers.xml

@@ -0,0 +1,3 @@
+<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
+  <PropertyChanged />
+</Weavers>

+ 8 - 0
Neptune/Infrastructure/Messaging/ProjectClosedMessage.cs

@@ -0,0 +1,8 @@
+using Glitonea.Mvvm.Messaging;
+
+namespace Neptune.Infrastructure.Messaging
+{
+    public class ProjectClosedMessage : Message
+    {
+    }
+}

+ 15 - 0
Neptune/Infrastructure/Messaging/ProjectLoadedMessage.cs

@@ -0,0 +1,15 @@
+using Glitonea.Mvvm.Messaging;
+using Neptune.Model.ProjectManagement;
+
+namespace Neptune.Infrastructure.Messaging
+{
+    public class ProjectLoadedMessage : Message
+    {
+        public Project Project { get; }
+
+        public ProjectLoadedMessage(Project project)
+        {
+            Project = project;
+        }
+    }
+}

+ 15 - 0
Neptune/Infrastructure/Messaging/SelectedConfigMemberChangedMessage.cs

@@ -0,0 +1,15 @@
+using Glitonea.Mvvm.Messaging;
+using Neptune.Model.ProjectManagement;
+
+namespace Neptune.Infrastructure.Messaging
+{
+    public class SelectedConfigMemberChangedMessage : Message
+    {
+        public ConfigMember ConfigMember { get; }
+
+        public SelectedConfigMemberChangedMessage(ConfigMember configMember)
+        {
+            ConfigMember = configMember;
+        }
+    }
+}

+ 87 - 0
Neptune/Infrastructure/Services/AppStorageService.cs

@@ -0,0 +1,87 @@
+using System;
+using System.ComponentModel;
+using System.IO;
+using System.Text.Json;
+using System.Threading.Tasks;
+using Neptune.Model.Storage;
+
+namespace Neptune.Infrastructure.Services
+{
+    public class AppStorageService : IAppStorageService
+    {
+        private const string AppStorageFileName = "settings.json";
+
+        private AppStorage Current { get; set; }
+        private string AppStorageFilePath { get; }
+
+        public bool AutoSave { get; set; } = true;
+        public string AppStorageDirectoryPath { get; }
+
+        public AppStorageService()
+        {
+            var userHomeSettingsDirectory = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
+
+            AppStorageDirectoryPath = Path.Combine(userHomeSettingsDirectory, "2009scape-dev", "Neptune");
+            AppStorageFilePath = Path.Combine(AppStorageDirectoryPath, AppStorageFileName);
+        }
+
+        public AppStorage Retrieve()
+        {
+            if (Current == null)
+            {
+                EnsureStorageDirectoryExists();
+
+                if (File.Exists(AppStorageFilePath))
+                {
+                    try
+                    {
+                        using (var fs = new FileStream(AppStorageFilePath, FileMode.Open))
+                        {
+                            Current = JsonSerializer.Deserialize<AppStorage>(fs);
+                            return Current;
+                        }
+                    }
+                    catch
+                    {
+                        // Failure state, so we default to new app storage.
+                        // todo: probably log that later.
+                    }
+                }
+
+                Current = new AppStorage(AppStorageFilePath);
+                Save();
+            }
+
+            Current.PropertyChanged += Current_PropetyChanged;
+            return Current;
+        }
+
+        private void Current_PropetyChanged(object sender, PropertyChangedEventArgs e)
+        {
+            if (AutoSave)
+            {
+                Save();
+            }
+        }
+
+        public void Save()
+        {
+            if (Current == null)
+            {
+                throw new InvalidOperationException("Attempt to save a null application storage file.");
+            }
+
+            EnsureStorageDirectoryExists();
+
+            using (var fs = new FileStream(AppStorageFilePath, FileMode.Create))
+            {
+                JsonSerializer.Serialize(fs, Current);
+            }
+        }
+
+        private void EnsureStorageDirectoryExists()
+        {
+            Directory.CreateDirectory(AppStorageDirectoryPath);
+        }
+    }
+}

+ 14 - 0
Neptune/Infrastructure/Services/IAppStorageService.cs

@@ -0,0 +1,14 @@
+using Glitonea.Mvvm;
+using Neptune.Model.Storage;
+
+namespace Neptune.Infrastructure.Services
+{
+    public interface IAppStorageService : IService
+    {
+        bool AutoSave { get; set; }
+        string AppStorageDirectoryPath { get; }
+        
+        AppStorage Retrieve();
+        void Save();
+    }
+}

+ 12 - 0
Neptune/Infrastructure/Services/IProjectService.cs

@@ -0,0 +1,12 @@
+using System.Threading.Tasks;
+using Glitonea.Mvvm;
+using Neptune.Model.ProjectManagement;
+
+namespace Neptune.Infrastructure.Services
+{
+    public interface IProjectService : IService
+    {
+        Task<Project> OpenProject(string directoryPath);
+        Task SaveProject(Project project);
+    }
+}

+ 13 - 0
Neptune/Infrastructure/Services/IRecentProjectService.cs

@@ -0,0 +1,13 @@
+using System.Collections.ObjectModel;
+using Glitonea.Mvvm;
+
+namespace Neptune.Infrastructure.Services
+{
+    public interface IRecentProjectService : IService
+    {
+        void AddEntry(string projectPath);
+        void Clear();
+        
+        ObservableCollection<string> GetEntries();
+    }
+}

+ 71 - 0
Neptune/Infrastructure/Services/ProjectService.cs

@@ -0,0 +1,71 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.IO;
+using System.Linq;
+using System.Text.Json;
+using System.Threading.Tasks;
+using Neptune.Model.ProjectManagement;
+
+namespace Neptune.Infrastructure.Services
+{
+    public class ProjectService : IProjectService
+    {
+        public async Task<Project> OpenProject(string directoryPath)
+        {
+            var project = new Project { DirectoryPath = directoryPath };
+
+            if (File.Exists(project.FilePath))
+            {
+                using (var fs = new FileStream(project.FilePath, FileMode.Open))
+                {
+                    var members = await JsonSerializer.DeserializeAsync<ObservableCollection<ConfigMember>>(fs);
+
+                    if (members == null)
+                    {
+                        throw new InvalidOperationException("Failed to open the project file. It might be corrupted.");
+                    }
+                    
+                    foreach (var member in members)
+                    {
+                        project.ConfigurationMembers.Add(member);
+                    }
+                }
+            }
+            else
+            {
+                var configFiles = FindServerConfigurationFiles(project);
+
+                foreach (var configFile in configFiles)
+                {
+                    project.ConfigurationMembers.Add(new ConfigMember(configFile));
+                }
+
+                await SaveProject(project);
+            }
+
+            project.ReorderMembers();
+            return project;
+        }
+
+        public async Task SaveProject(Project project)
+        {
+            using (var fs = new FileStream(project.FilePath, FileMode.Create))
+            {
+                await JsonSerializer.SerializeAsync(fs, project.ConfigurationMembers);
+            }
+        }
+
+        private List<string> FindServerConfigurationFiles(Project project)
+        {
+            return Directory.GetFiles(
+                Path.Combine(project.DirectoryPath,
+                    "Server",
+                    "data",
+                    "configs"
+                ),
+                "*.json"
+            ).ToList();
+        }
+    }
+}

+ 49 - 0
Neptune/Infrastructure/Services/RecentProjectService.cs

@@ -0,0 +1,49 @@
+using System.Collections.ObjectModel;
+using Neptune.Model.Storage;
+
+namespace Neptune.Infrastructure.Services
+{
+    public class RecentProjectService : IRecentProjectService
+    {
+        private readonly IAppStorageService _appStorageService;
+        private readonly AppStorage _appStorage;
+
+        public RecentProjectService(IAppStorageService appStorageService)
+        {
+            _appStorageService = appStorageService;
+            _appStorage = _appStorageService.Retrieve();
+        }
+
+        public void AddEntry(string projectPath)
+        {
+            if (_appStorage.RecentFiles.Contains(projectPath))
+            {
+                var index = _appStorage.RecentFiles.IndexOf(projectPath);
+                _appStorage.RecentFiles.RemoveAt(index);
+                _appStorage.RecentFiles.Insert(0, projectPath);
+            }
+            else
+            {
+                _appStorage.RecentFiles.Insert(0, projectPath);
+            }
+
+            if (_appStorage.RecentFiles.Count > 10)
+            {
+                _appStorage.RecentFiles.RemoveAt(_appStorage.RecentFiles.Count - 1);
+            }
+
+            _appStorageService.Save();
+        }
+
+        public void Clear()
+        {
+            _appStorage.RecentFiles.Clear();
+            _appStorageService.Save();
+        }
+
+        public ObservableCollection<string> GetEntries()
+        {
+            return _appStorage.RecentFiles;
+        }
+    }
+}

+ 105 - 0
Neptune/Model/ProjectManagement/ConfigMember.cs

@@ -0,0 +1,105 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text.Json.Serialization;
+using Avalonia;
+using Avalonia.Media.Imaging;
+using Avalonia.Platform;
+using Glitonea.Extensions;
+
+namespace Neptune.Model.ProjectManagement
+{
+    public class ConfigMember
+    {
+        private static readonly Dictionary<ConfigMemberType, IBitmap> _iconCache = new();
+        
+        private static readonly Dictionary<ConfigMemberType, string> _iconResourceMap = new()
+        {
+            { ConfigMemberType.ClueScrollRewards, "avares://Neptune/Resources/Images/Raster/24/Adventure.png" },
+            { ConfigMemberType.DoorProperties, "avares://Neptune/Resources/Images/Raster/24/OpenDoor.png" },
+            { ConfigMemberType.InterfaceProperties, "avares://Neptune/Resources/Images/Raster/24/Interface.png" },
+            { ConfigMemberType.NpcDropTables, "avares://Neptune/Resources/Images/Raster/24/Loot.png" },
+            { ConfigMemberType.NpcProperties, "avares://Neptune/Resources/Images/Raster/24/Person.png" },
+            { ConfigMemberType.NpcSpawns, "avares://Neptune/Resources/Images/Raster/24/PersonLocation.png" },
+            { ConfigMemberType.ObjectProperties, "avares://Neptune/Resources/Images/Raster/24/Cube.png" },
+            { ConfigMemberType.ItemProperties, "avares://Neptune/Resources/Images/Raster/24/Boxes.png" },
+            { ConfigMemberType.ItemGroundSpawns, "avares://Neptune/Resources/Images/Raster/24/PlaceMarker.png" },
+            { ConfigMemberType.MusicAreaDefinitions, "avares://Neptune/Resources/Images/Raster/24/MusicNote.png" },
+            { ConfigMemberType.ShopDefinitions, "avares://Neptune/Resources/Images/Raster/24/Shop.png" },
+            { ConfigMemberType.RangedAmmunitionProperties, "avares://Neptune/Resources/Images/Raster/24/ArcherArrow.png" },
+            { ConfigMemberType.RangedWeaponProperties, "avares://Neptune/Resources/Images/Raster/24/ArcherBow.png" },
+            { ConfigMemberType.VarbitDefinitions, "avares://Neptune/Resources/Images/Raster/24/Binary.png" },
+            { ConfigMemberType.XteaKeys, "avares://Neptune/Resources/Images/Raster/24/Key.png" },
+            { ConfigMemberType.Unknown, "avares://Neptune/Resources/Images/Raster/24/QuestionMark.png" },
+        };
+
+        [JsonIgnore]
+        public string JsonFileName => Path.GetFileName(JsonFilePath ?? string.Empty);
+
+        [JsonIgnore]
+        public string FriendlyName => Type.ToDescription().Description;
+
+        [JsonIgnore]
+        public IBitmap Icon
+        {
+            get
+            {
+                var type = Type;
+                
+                if (!_iconResourceMap.ContainsKey(Type))
+                    type = ConfigMemberType.Unknown;
+                
+                if (!_iconCache.ContainsKey(type))
+                {
+                    var resourcePath = _iconResourceMap[type];
+                    var uri = new Uri(resourcePath, UriKind.RelativeOrAbsolute);
+
+                    _iconCache.Add(type, new Bitmap(AvaloniaLocator.Current.GetService<IAssetLoader>()!.Open(uri)));
+                }
+
+                return _iconCache[type];
+            }
+        }
+
+        [JsonPropertyName("file_path")]
+        public string JsonFilePath { get; set; }
+
+        [JsonPropertyName("member_type")]
+        public ConfigMemberType Type { get; set; }
+
+        public ConfigMember()
+        {
+        }
+
+        public ConfigMember(string jsonFilePath)
+        {
+            JsonFilePath = jsonFilePath;
+            AutodetectType();
+        }
+
+        private void AutodetectType()
+        {
+            var fileName = Path.GetFileNameWithoutExtension(JsonFilePath);
+
+            Type = fileName switch
+            {
+                "ammo_configs" => ConfigMemberType.RangedAmmunitionProperties,
+                "clue_rewards" => ConfigMemberType.ClueScrollRewards,
+                "door_configs" => ConfigMemberType.DoorProperties,
+                "drop_tables" => ConfigMemberType.NpcDropTables,
+                "ground_spawns" => ConfigMemberType.ItemGroundSpawns,
+                "interface_configs" => ConfigMemberType.InterfaceProperties,
+                "item_configs" => ConfigMemberType.ItemProperties,
+                "music_configs" => ConfigMemberType.MusicAreaDefinitions,
+                "npc_configs" => ConfigMemberType.NpcProperties,
+                "npc_spawns" => ConfigMemberType.NpcSpawns,
+                "object_configs" => ConfigMemberType.ObjectProperties,
+                "ranged_weapon_configs" => ConfigMemberType.RangedWeaponProperties,
+                "shops" => ConfigMemberType.ShopDefinitions,
+                "varbit_definitions" => ConfigMemberType.VarbitDefinitions,
+                "xteas" => ConfigMemberType.XteaKeys,
+                _ => ConfigMemberType.Unknown
+            };
+        }
+    }
+}

+ 55 - 0
Neptune/Model/ProjectManagement/ConfigMemberType.cs

@@ -0,0 +1,55 @@
+using System.ComponentModel;
+
+namespace Neptune.Model.ProjectManagement
+{
+    public enum ConfigMemberType
+    {
+        [Description("Clue scroll rewards")]
+        ClueScrollRewards,
+        
+        [Description("Door properties")]
+        DoorProperties,
+        
+        [Description("Interface properties")]
+        InterfaceProperties,
+        
+        [Description("Item properties")]
+        ItemProperties,
+        
+        [Description("Item spawn locations")]
+        ItemGroundSpawns,
+        
+        [Description("Music area definitions")]
+        MusicAreaDefinitions,
+        
+        [Description("NPC drop tables")]
+        NpcDropTables,
+
+        [Description("NPC properties")]
+        NpcProperties,
+        
+        [Description("NPC spawn locations")]
+        NpcSpawns,
+        
+        [Description("Object properties")]
+        ObjectProperties,
+        
+        [Description("Ranged weapon properties")]
+        RangedWeaponProperties,
+        
+        [Description("Ranged ammunition properties")]
+        RangedAmmunitionProperties,
+        
+        [Description("Shop definitions")]
+        ShopDefinitions,
+        
+        [Description("Varbit definitions")]
+        VarbitDefinitions,
+        
+        [Description("XTEA keys")]
+        XteaKeys,
+        
+        [Description("Unknown/unsupported")]
+        Unknown
+    } 
+}

+ 33 - 0
Neptune/Model/ProjectManagement/Project.cs

@@ -0,0 +1,33 @@
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.IO;
+using System.Linq;
+using System.Reactive.Linq;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace Neptune.Model.ProjectManagement
+{
+    public class Project : INotifyPropertyChanged
+    {
+        private const string ProjectFileName = "server.neptune.json";
+        
+        public event PropertyChangedEventHandler PropertyChanged;
+        
+        public string DirectoryPath { get; init; }
+        
+        public string FilePath => Path.Combine(DirectoryPath, ProjectFileName);
+        
+        public bool IsDirty { get; set; }
+
+        public ObservableCollection<ConfigMember> ConfigurationMembers { get; private set; } = new();
+
+        public void ReorderMembers()
+        {
+            ConfigurationMembers = new ObservableCollection<ConfigMember>(
+                ConfigurationMembers.OrderBy(x => x.FriendlyName)
+            );
+        }
+    }
+}

+ 30 - 0
Neptune/Model/Storage/AppStorage.cs

@@ -0,0 +1,30 @@
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.Text.Json.Serialization;
+
+namespace Neptune.Model.Storage
+{
+    public class AppStorage : INotifyPropertyChanged
+    {
+        public event PropertyChangedEventHandler PropertyChanged;
+        
+        [JsonIgnore]
+        public string FilePath { get; init; }
+
+        [JsonPropertyName("recent_files")]
+        public ObservableCollection<string> RecentFiles { get; set; } = new();
+
+        [JsonPropertyName("gridsplitter_position")]
+        public double GridSplitterPosition { get; set; } = 250;
+
+        public AppStorage()
+        {
+        }
+
+        public AppStorage(string filePath)
+        {
+            FilePath = filePath;
+        }
+
+    }
+}

+ 80 - 0
Neptune/Neptune.csproj

@@ -0,0 +1,80 @@
+<Project Sdk="Microsoft.NET.Sdk">
+    <PropertyGroup>
+        <OutputType>WinExe</OutputType>
+        <TargetFramework>net6.0</TargetFramework>
+        <BuiltInComInteropSupport>true</BuiltInComInteropSupport>
+        <ApplicationManifest>app.manifest</ApplicationManifest>
+    </PropertyGroup>
+
+    <ItemGroup>
+        <TrimmerRootDescriptor Include="Roots.xml" />
+    </ItemGroup>
+
+    <ItemGroup>
+        <PackageReference Include="Avalonia" Version="0.10.18" />
+        <PackageReference Include="Avalonia.Desktop" Version="0.10.18" />
+        <!--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.18" />
+        <PackageReference Include="Avalonia.Xaml.Behaviors" Version="0.10.18" />
+        <PackageReference Include="PropertyChanged.Fody" Version="4.1.0" />
+        <PackageReference Include="XamlNameReferenceGenerator" Version="1.5.1" />
+    </ItemGroup>
+
+    <ItemGroup>
+      <ProjectReference Include="..\Glitonea\Glitonea.csproj" />
+    </ItemGroup>
+
+    <ItemGroup>
+      <Folder Include="Resources\Images\Raster\24" />
+      <Folder Include="Resources\Images\Scalable" />
+      <Folder Include="View\Controls\Editors" />
+    </ItemGroup>
+
+    <ItemGroup>
+      <Compile Update="View\Windows\MainWindow.axaml.cs">
+        <DependentUpon>MainWindow.axaml</DependentUpon>
+        <SubType>Code</SubType>
+      </Compile>
+      <Compile Update="View\Controls\ProjectExplorer.axaml.cs">
+        <DependentUpon>ProjectExplorer.axaml</DependentUpon>
+      </Compile>
+      <Compile Update="View\Controls\EditorHost.axaml.cs">
+        <DependentUpon>EditorHost.axaml</DependentUpon>
+      </Compile>
+    </ItemGroup>
+
+    <ItemGroup>
+        <AvaloniaResource Include="Resources\Fonts\OpenSans.ttf" />
+    </ItemGroup>
+    
+    <ItemGroup>
+      <AvaloniaResource Include="Resources\Images\Raster\16\Exit.png" />
+      <AvaloniaResource Include="Resources\Images\Raster\16\History.png" />
+      <AvaloniaResource Include="Resources\Images\Raster\16\Interface.png" />
+      <AvaloniaResource Include="Resources\Images\Raster\16\Key.png" />
+      <AvaloniaResource Include="Resources\Images\Raster\16\Map.png" />
+      <AvaloniaResource Include="Resources\Images\Raster\16\OpenDoor.png" />
+      <AvaloniaResource Include="Resources\Images\Raster\16\OpenFolder.png" />
+      <AvaloniaResource Include="Resources\Images\Raster\16\QuestionMark.png" />
+      <AvaloniaResource Include="Resources\Images\Raster\16\Save.png" />
+        
+      <AvaloniaResource Include="Resources\Images\Raster\24\Adventure.png" />
+      <AvaloniaResource Include="Resources\Images\Raster\24\ArcherArrow.png" />
+      <AvaloniaResource Include="Resources\Images\Raster\24\ArcherBow.png" />
+      <AvaloniaResource Include="Resources\Images\Raster\24\Binary.png" />
+      <AvaloniaResource Include="Resources\Images\Raster\24\Boxes.png" />
+      <AvaloniaResource Include="Resources\Images\Raster\24\BoxesPin.png" />
+      <AvaloniaResource Include="Resources\Images\Raster\24\Cube.png" />
+      <AvaloniaResource Include="Resources\Images\Raster\24\Interface.png" />
+      <AvaloniaResource Include="Resources\Images\Raster\24\Key.png" />
+      <AvaloniaResource Include="Resources\Images\Raster\24\Loot.png" />
+      <AvaloniaResource Include="Resources\Images\Raster\24\MusicNote.png" />
+      <AvaloniaResource Include="Resources\Images\Raster\24\OpenDoor.png" />
+      <AvaloniaResource Include="Resources\Images\Raster\24\Person.png" />
+      <AvaloniaResource Include="Resources\Images\Raster\24\PersonLocation.png" />
+      <AvaloniaResource Include="Resources\Images\Raster\24\PlaceMarker.png" />
+      <AvaloniaResource Include="Resources\Images\Raster\24\QuestionMark.png" />
+      <AvaloniaResource Include="Resources\Images\Raster\24\Shop.png" />
+      <AvaloniaResource Include="Resources\Images\Raster\24\Unicorn.png" />        
+    </ItemGroup>
+</Project>

+ 20 - 0
Neptune/Program.cs

@@ -0,0 +1,20 @@
+using Avalonia;
+using System;
+
+namespace Neptune;
+
+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();
+}

+ 34 - 0
Neptune/Resources/Definitions/ImageDefinitions.axaml

@@ -0,0 +1,34 @@
+<Styles xmlns="https://github.com/avaloniaui"
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+        xmlns:sys="clr-namespace:System;assembly=System.Runtime">
+    <Styles.Resources>
+        <Image x:Key="NeptuneIconExit_16" Source="avares://Neptune/Resources/Images/Raster/16/Exit.png" />
+        <Image x:Key="NeptuneIconHistory_16" Source="avares://Neptune/Resources/Images/Raster/16/History.png" />
+        <Image x:Key="NeptuneIconInterface_16" Source="avares://Neptune/Resources/Images/Raster/16/Interface.png" />
+        <Image x:Key="NeptuneIconKey_16" Source="avares://Neptune/Resources/Images/Raster/16/Key.png" />
+        <Image x:Key="NeptuneIconMap_16" Source="avares://Neptune/Resources/Images/Raster/16/Map.png" />
+        <Image x:Key="NeptuneIconOpenDoor_16" Source="avares://Neptune/Resources/Images/Raster/16/OpenDoor.png" />
+        <Image x:Key="NeptuneIconOpenFolder_16" Source="avares://Neptune/Resources/Images/Raster/16/OpenFolder.png" />
+        <Image x:Key="NeptuneIconSave_16" Source="avares://Neptune/Resources/Images/Raster/16/Save.png" />
+        <Image x:Key="NeptuneIconQuestionMark_16" Source="avares://Neptune/Resources/Images/Raster/16/QuestionMark.png" />
+        
+        <Image x:Key="NeptuneIconAdventure_24" Source="avares://Neptune/Resources/Images/Raster/24/Adventure.png" />
+        <Image x:Key="NeptuneIconArcherArrow_24" Source="avares://Neptune/Resources/Images/Raster/24/ArcherArrow.png" />
+        <Image x:Key="NeptuneIconArcherBow_24" Source="avares://Neptune/Resources/Images/Raster/24/ArcherBow.png" />
+        <Image x:Key="NeptuneIconBinary_24" Source="avares://Neptune/Resources/Images/Raster/24/Binary.png" />
+        <Image x:Key="NeptuneIconBoxes_24" Source="avares://Neptune/Resources/Images/Raster/24/Boxes.png" />
+        <Image x:Key="NeptuneIconBoxesPin_24" Source="avares://Neptune/Resources/Images/Raster/24/BoxesPin.png" />
+        <Image x:Key="NeptuneIconCube_24" Source="avares://Neptune/Resources/Images/Raster/24/Cube.png" />
+        <Image x:Key="NeptuneIconInterface_24" Source="avares://Neptune/Resources/Images/Raster/24/Interface.png" />
+        <Image x:Key="NeptuneIconKey_24" Source="avares://Neptune/Resources/Images/Raster/24/Key.png" />
+        <Image x:Key="NeptuneIconLoot_24" Source="avares://Neptune/Resources/Images/Raster/24/Loot.png" />
+        <Image x:Key="NeptuneIconMusicNote_24" Source="avares://Neptune/Resources/Images/Raster/24/MusicNote.png" />
+        <Image x:Key="NeptuneIconOpenDoor_24" Source="avares://Neptune/Resources/Images/Raster/24/OpenDoor.png" />
+        <Image x:Key="NeptuneIconPlaceMarker_24" Source="avares://Neptune/Resources/Images/Raster/24/PlaceMarker.png" />
+        <Image x:Key="NeptuneIconPerson_24" Source="avares://Neptune/Resources/Images/Raster/24/Person.png" />
+        <Image x:Key="NeptuneIconPersonLocation_24" Source="avares://Neptune/Resources/Images/Raster/24/PersonLocation.png" />
+        <Image x:Key="NeptuneIconQuestionMark_24" Source="avares://Neptune/Resources/Images/Raster/24/QuestionMark.png" />
+        <Image x:Key="NeptuneIconShop_24" Source="avares://Neptune/Resources/Images/Raster/24/Shop.png" />
+        <Image x:Key="NeptuneIconUnicorn_24" Source="avares://Neptune/Resources/Images/Raster/24/Unicorn.png" />
+    </Styles.Resources>
+</Styles>

BIN
Neptune/Resources/Fonts/OpenSans.ttf


BIN
Neptune/Resources/Images/Raster/16/Exit.png


BIN
Neptune/Resources/Images/Raster/16/History.png


BIN
Neptune/Resources/Images/Raster/16/Interface.png


BIN
Neptune/Resources/Images/Raster/16/Key.png


BIN
Neptune/Resources/Images/Raster/16/Map.png


BIN
Neptune/Resources/Images/Raster/16/OpenDoor.png


BIN
Neptune/Resources/Images/Raster/16/OpenFolder.png


BIN
Neptune/Resources/Images/Raster/16/QuestionMark.png


BIN
Neptune/Resources/Images/Raster/16/Save.png


BIN
Neptune/Resources/Images/Raster/24/Adventure.png


BIN
Neptune/Resources/Images/Raster/24/ArcherArrow.png


BIN
Neptune/Resources/Images/Raster/24/ArcherBow.png


BIN
Neptune/Resources/Images/Raster/24/Binary.png


BIN
Neptune/Resources/Images/Raster/24/Boxes.png


BIN
Neptune/Resources/Images/Raster/24/BoxesPin.png


BIN
Neptune/Resources/Images/Raster/24/Cube.png


BIN
Neptune/Resources/Images/Raster/24/Interface.png


BIN
Neptune/Resources/Images/Raster/24/Key.png


BIN
Neptune/Resources/Images/Raster/24/Loot.png


BIN
Neptune/Resources/Images/Raster/24/MusicNote.png


BIN
Neptune/Resources/Images/Raster/24/OpenDoor.png


BIN
Neptune/Resources/Images/Raster/24/Person.png


BIN
Neptune/Resources/Images/Raster/24/PersonLocation.png


BIN
Neptune/Resources/Images/Raster/24/PlaceMarker.png


BIN
Neptune/Resources/Images/Raster/24/QuestionMark.png


BIN
Neptune/Resources/Images/Raster/24/Shop.png


BIN
Neptune/Resources/Images/Raster/24/Unicorn.png


+ 54 - 0
Neptune/Resources/Styles/BaseAppStyle.axaml

@@ -0,0 +1,54 @@
+<Styles xmlns="https://github.com/avaloniaui"
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+        xmlns:sys="clr-namespace:System;assembly=System.Runtime">
+    <Styles.Resources>
+        <SolidColorBrush x:Key="NeptuneMediumBorderBrush"
+                         Color="#686868" />
+        
+        <SolidColorBrush x:Key="ThemeBorderMidBrush"
+                         Color="#686868" />
+
+        <SolidColorBrush x:Key="NeptuneDarkBorderBrush"
+                         Color="#1C1C1C" />
+
+        <SolidColorBrush x:Key="NeptuneSelectedActiveItemBackgroundBrush"
+                         Color="#304885" />
+        
+        <SolidColorBrush x:Key="NeptuneHoveredItemBackgroundBrush"
+                         Color="#484848" />
+        
+        <SolidColorBrush x:Key="NeptuneBackgroundBrush"
+                         Color="#282828" />
+        
+        <SolidColorBrush x:Key="NeptuneDarkBackgroundBrush"
+                         Color="#181818" />
+        
+        <SolidColorBrush x:Key="NeptuneSemiTransparentForegroundBrush"
+                         Color="#66FFFFFF" />
+    </Styles.Resources>
+    
+    <Style Selector="AccessText">
+        <Setter Property="FontFamily" 
+                Value="avares://Neptune/Resources/Fonts/OpenSans.ttf#Open Sans" />
+        
+        <Setter Property="FontSize" Value="13" />
+        <Setter Property="FontWeight" Value="400" />
+    </Style>
+    
+    <Style Selector="TextBlock">
+        <Setter Property="FontFamily" 
+                Value="avares://Neptune/Resources/Fonts/OpenSans.ttf#Open Sans" />
+        
+        <Setter Property="FontSize" Value="13" />
+        <Setter Property="FontWeight" Value="400" />
+    </Style>
+
+    <StyleInclude Source="avares://Avalonia.Themes.Default/Accents/BaseDark.xaml" />
+    <StyleInclude Source="avares://Avalonia.Themes.Default/DefaultTheme.xaml" />
+    
+    <StyleInclude Source="avares://Neptune/Resources/Definitions/ImageDefinitions.axaml" />
+    
+    <StyleInclude Source="avares://Neptune/Resources/Styles/Controls/ProjectExplorer/ListBox.axaml" />
+    <StyleInclude Source="avares://Neptune/Resources/Styles/Controls/ProjectExplorer/ListBoxItem.axaml" />
+    <StyleInclude Source="avares://Neptune/Resources/Styles/Controls/MenuItem.axaml" />
+</Styles>

+ 54 - 0
Neptune/Resources/Styles/Controls/MenuItem.axaml

@@ -0,0 +1,54 @@
+<Styles xmlns="https://github.com/avaloniaui"
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+        xmlns:sys="clr-namespace:System;assembly=System.Runtime">
+    <Style Selector="MenuItem">
+        <Setter Property="Padding" Value="6,0" />
+        <Setter Property="Margin" Value="0" />
+    </Style>
+
+    <Style Selector="MenuItem /template/ TextBlock#PART_InputGestureText">
+        <Setter Property="Margin" Value="8,0,0,0" />
+        <Setter Property="Foreground" Value="{StaticResource NeptuneSemiTransparentForegroundBrush}" />
+    </Style>
+
+    <Style Selector="MenuItem > Separator">
+        <Setter Property="Margin" Value="0,1" />
+    </Style>
+
+    <Style Selector="MenuItem /template/ Popup">
+        <Setter Property="HorizontalOffset" Value="-1" />
+    </Style>
+
+    <Style Selector="MenuItem /template/ Popup > Border">
+        <Setter Property="Padding" Value="0" />
+        <Setter Property="Margin" Value="0" />
+        <Setter Property="BorderBrush" Value="Transparent" />
+    </Style>
+
+    <Style Selector="MenuItem /template/ ItemsPresenter#PART_ItemsPresenter">
+        <Setter Property="Margin" Value="1" />
+    </Style>
+
+    <Style Selector="MenuItem:pointerover /template/ Border">
+        <Setter Property="BorderBrush" Value="{StaticResource NeptuneSelectedActiveItemBackgroundBrush}" />
+        <Setter Property="Background" Value="{StaticResource NeptuneSelectedActiveItemBackgroundBrush}" />
+    </Style>
+
+    <Style Selector="MenuItem:selected /template/ Border">
+        <Setter Property="BorderBrush" Value="{StaticResource NeptuneSelectedActiveItemBackgroundBrush}" />
+        <Setter Property="Background" Value="{StaticResource NeptuneSelectedActiveItemBackgroundBrush}" />
+    </Style>
+
+    <Style Selector="MenuItem /template/ ContentPresenter">
+        <Setter Property="VerticalContentAlignment" Value="Center" />
+    </Style>
+
+    <Style Selector="MenuItem > MenuItem">
+        <Setter Property="Padding" Value="0" />
+    </Style>
+
+    <Style Selector="MenuItem > MenuItem /template/ Popup">
+        <Setter Property="HorizontalOffset" Value="2" />
+        <Setter Property="VerticalOffset" Value="-3" />
+    </Style>
+</Styles>

+ 8 - 0
Neptune/Resources/Styles/Controls/ProjectExplorer/ListBox.axaml

@@ -0,0 +1,8 @@
+<Styles xmlns="https://github.com/avaloniaui"
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+        xmlns:sys="clr-namespace:System;assembly=System.Runtime">
+  <Style Selector="ListBox.ProjectExplorer">
+      <Setter Property="Padding" Value="1" />
+      <Setter Property="BorderThickness" Value="0" />
+  </Style>
+</Styles>

+ 25 - 0
Neptune/Resources/Styles/Controls/ProjectExplorer/ListBoxItem.axaml

@@ -0,0 +1,25 @@
+<Styles xmlns="https://github.com/avaloniaui"
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+        xmlns:sys="clr-namespace:System;assembly=System.Runtime">
+    <Style Selector="ListBox.ProjectExplorer ListBoxItem">
+        <Setter Property="Background" Value="{StaticResource NeptuneBackgroundBrush}" />
+        <Setter Property="Padding" Value="0" />
+        <Setter Property="ToolTip.Tip" Value="{Binding JsonFileName}" />
+    </Style>
+    
+    <Style Selector="ListBox.ProjectExplorer ListBoxItem:pointerover /template/ ContentPresenter">
+        <Setter Property="Background" Value="Transparent" />
+    </Style>
+    
+    <Style Selector="ListBox.ProjectExplorer ListBoxItem:pointerover /template/ ContentPresenter">
+        <Setter Property="Background" Value="{StaticResource NeptuneHoveredItemBackgroundBrush}" />
+    </Style>
+
+    <Style Selector="ListBox.ProjectExplorer ListBoxItem:selected /template/ ContentPresenter">
+        <Setter Property="Background" Value="{StaticResource NeptuneSelectedActiveItemBackgroundBrush}" />
+    </Style>
+
+    <Style Selector="ListBox.ProjectExplorer ListBoxItem:pointerover:selected /template/ ContentPresenter">
+        <Setter Property="Background" Value="{StaticResource NeptuneSelectedActiveItemBackgroundBrush}" />
+    </Style>
+</Styles>

+ 5 - 0
Neptune/Roots.xml

@@ -0,0 +1,5 @@
+<linker>
+  <!-- Can be removed if CompiledBinding and no reflection are used -->
+  <assembly fullname="Neptune" preserve="All" />
+  <assembly fullname="Avalonia.Themes.Fluent" preserve="All" />
+</linker>

+ 41 - 0
Neptune/View/Controls/EditorHost.axaml

@@ -0,0 +1,41 @@
+<UserControl xmlns="https://github.com/avaloniaui"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:mvvm="clr-namespace:Glitonea.Mvvm;assembly=Glitonea"
+             xmlns:vm="clr-namespace:Neptune.ViewModel.Controls"
+             x:Class="Neptune.View.Controls.EditorHost"
+             DataContext="{mvvm:DataContextSource vm:EditorHostViewModel}">
+    <Grid HorizontalAlignment="Center"
+          VerticalAlignment="Center"
+          ColumnDefinitions="Auto,Auto,Auto"
+          RowDefinitions="Auto,Auto">
+        <TextBlock Grid.Column="0"
+                   Grid.Row="0"
+                   HorizontalAlignment="Center"
+                   FontSize="16"
+                   Text="And here I would display an editor for the "
+                   Opacity="0.4" />
+
+        <TextBlock Grid.Column="1"
+                   Grid.Row="0"
+                   HorizontalAlignment="Center"
+                   FontSize="16"
+                   Text="{Binding SelectedConfigMember.FriendlyName}"
+                   Opacity="0.8" />
+        
+        <TextBlock Grid.Column="2"
+                   Grid.Row="0"
+                   HorizontalAlignment="Center"
+                   FontSize="16"
+                   Text="."
+                   Opacity="0.4" />
+        
+        <TextBlock Grid.ColumnSpan="3"
+                   Grid.Row="1"
+                   HorizontalAlignment="Center"
+                   FontSize="16"
+                   Text="IF I HAD ONE"
+                   Foreground="Red"
+                   FontWeight="800"
+                   Opacity="0.8" />
+    </Grid>
+</UserControl>

+ 20 - 0
Neptune/View/Controls/EditorHost.axaml.cs

@@ -0,0 +1,20 @@
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+using PropertyChanged;
+
+namespace Neptune.View.Controls
+{
+    [DoNotNotify]
+    public partial class EditorHost : UserControl
+    {
+        public EditorHost()
+        {
+            InitializeComponent();
+        }
+
+        private void InitializeComponent()
+        {
+            AvaloniaXamlLoader.Load(this);
+        }
+    }
+}

+ 35 - 0
Neptune/View/Controls/ProjectExplorer.axaml

@@ -0,0 +1,35 @@
+<UserControl xmlns="https://github.com/avaloniaui"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:mvvm="clr-namespace:Glitonea.Mvvm;assembly=Glitonea"
+             xmlns:vm="clr-namespace:Neptune.ViewModel.Controls"
+             xmlns:model="clr-namespace:Neptune.Model.ProjectManagement"
+             xmlns:controls="clr-namespace:Neptune.View.Controls"
+             xmlns:converters="clr-namespace:Glitonea.Mvvm.Converters;assembly=Glitonea"
+             x:Class="Neptune.View.Controls.ProjectExplorer"
+             DataContext="{mvvm:DataContextSource vm:ProjectExplorerViewModel}">
+    <ListBox Items="{Binding Project.ConfigurationMembers}"
+             SelectedItem="{Binding SelectedMember, Mode=TwoWay}"
+             SelectionMode="Single"
+             Classes="ProjectExplorer">
+        <ListBox.ItemTemplate>
+            <DataTemplate DataType="model:ConfigMember">
+                <Grid Height="32"
+                      ColumnDefinitions="Auto,*">
+                    <Image Grid.Row="0"
+                           Width="24"
+                           Height="24"
+                           Margin="10,0,0,0" 
+                           VerticalAlignment="Center"
+                           Source="{Binding Icon}" />
+
+                    <!-- ToolTip.Tip bound in Resources/Styles/ProjectExplorer/ListBoxItem.axaml -->
+                    <TextBlock Grid.Column="1"
+                               Margin="10,0,0,0"
+                               VerticalAlignment="Center"
+                               FontSize="15"
+                               Text="{Binding FriendlyName}" />
+                </Grid>
+            </DataTemplate>
+        </ListBox.ItemTemplate>
+    </ListBox>
+</UserControl>

+ 20 - 0
Neptune/View/Controls/ProjectExplorer.axaml.cs

@@ -0,0 +1,20 @@
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+using PropertyChanged;
+
+namespace Neptune.View.Controls
+{
+    [DoNotNotify]
+    public partial class ProjectExplorer : UserControl
+    {
+        public ProjectExplorer()
+        {
+            InitializeComponent();
+        }
+
+        private void InitializeComponent()
+        {
+            AvaloniaXamlLoader.Load(this);
+        }
+    }
+}

+ 164 - 0
Neptune/View/Windows/MainWindow.axaml

@@ -0,0 +1,164 @@
+<glitonea:WindowEx x:Class="Neptune.View.Windows.MainWindow"
+                   xmlns="https://github.com/avaloniaui"
+                   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+                   xmlns:glitonea="clr-namespace:Glitonea;assembly=Glitonea"
+                   xmlns:mvvm="clr-namespace:Glitonea.Mvvm;assembly=Glitonea"
+                   xmlns:vm="clr-namespace:Neptune.ViewModel.Windows"
+                   xmlns:controls="clr-namespace:Neptune.View.Controls"
+                   xmlns:view="clr-namespace:Neptune.View.Windows"
+                   Name="WindowRoot"
+                   Title="Neptune"
+                   DataContext="{mvvm:DataContextSource vm:MainWindowViewModel}">
+    <Grid RowDefinitions="Auto,*">
+        <Menu Grid.Row="0"
+              Margin="0,4,0,0"
+              Height="26"
+              BorderBrush="{StaticResource NeptuneDarkBorderBrush}"
+              BorderThickness="0,0,0,1">
+            <MenuItem Header="_File">
+                <MenuItem Header="_Open source tree"
+                          Command="{Binding BrowseForProjectDirectory}"
+                          Icon="{StaticResource NeptuneIconOpenFolder_16}"
+                          InputGesture="Ctrl+O"
+                          HotKey="Ctrl+O" />
+
+                <MenuItem Name="RecentsMenuItem"
+                          Header="Open _recent"
+                          Items="{Binding RecentProjects}"
+                          Command="{Binding LoadProject}"
+                          CommandParameter="{Binding #RecentsMenuItem.Selection.SelectedItem}"
+                          IsEnabled="{Binding !!RecentProjects.Count}"
+                          Icon="{StaticResource NeptuneIconHistory_16}"
+                          InputGesture="Ctrl+R"
+                          HotKey="Ctrl+R" />
+
+                <MenuItem Header="_Save"
+                          Command="{Binding SaveCurrentProject}"
+                          IsEnabled="{Binding IsProjectLoaded}"
+                          Icon="{StaticResource NeptuneIconSave_16}"
+                          InputGesture="Ctrl+S"
+                          HotKey="Ctrl+S" />
+
+                <MenuItem Header="_Close project"
+                          Command="{Binding CloseCurrentProject}"
+                          IsEnabled="{Binding IsProjectLoaded}"
+                          InputGesture="Ctrl+Alt+C"
+                          HotKey="Ctrl+Alt+C" />
+
+                <Separator />
+
+                <MenuItem Header="_Quit"
+                          Command="{Binding ExitApplication}"
+                          Icon="{StaticResource NeptuneIconExit_16}"
+                          InputGesture="Ctrl+Q"
+                          HotKey="Ctrl+Q" />
+            </MenuItem>
+
+            <MenuItem Header="Edit" />
+            <MenuItem Header="View" />
+            
+            <MenuItem Header="Help">
+                <MenuItem Header="Documentation" />
+                <MenuItem Header="Report a bug" />
+                
+                <Separator />
+                
+                <MenuItem Header="About this program" />
+            </MenuItem>
+        </Menu>
+
+        <Grid Grid.Row="1">
+            <Grid.ColumnDefinitions>
+                <ColumnDefinition Width="{Binding GridSplitterPosition, Mode=TwoWay}"
+                                  MinWidth="35" />
+                <ColumnDefinition Width="3" />
+                <ColumnDefinition Width="*" />
+            </Grid.ColumnDefinitions>
+
+            <Grid Grid.Column="0"
+                  HorizontalAlignment="Stretch"
+                  IsVisible="{Binding IsProjectLoaded}">
+                <controls:ProjectExplorer />
+            </Grid>
+
+            <GridSplitter Grid.Column="1"
+                          Name="MainViewSplitter"
+                          BorderBrush="{StaticResource NeptuneBackgroundBrush}"
+                          BorderThickness="0,0,2,0"
+                          Background="{StaticResource NeptuneDarkBorderBrush}"
+                          IsVisible="{Binding IsProjectLoaded}" />
+
+            <Grid Grid.Column="2"
+                  Name="EditorRoot"
+                  Background="{StaticResource NeptuneBackgroundBrush}">
+                <Interaction.Behaviors>
+                    <DataTriggerBehavior Binding="{Binding IsProjectLoaded}"
+                                         ComparisonCondition="Equal"
+                                         Value="True">
+                        <ChangePropertyAction TargetObject="EditorRoot"
+                                              PropertyName="Background"
+                                              Value="{StaticResource NeptuneDarkBackgroundBrush}" />
+
+                        <ChangePropertyAction TargetObject="MainViewSplitter"
+                                              PropertyName="BorderBrush"
+                                              Value="{StaticResource NeptuneDarkBackgroundBrush}" />
+                    </DataTriggerBehavior>
+
+                    <DataTriggerBehavior Binding="{Binding IsProjectLoaded}"
+                                         ComparisonCondition="Equal"
+                                         Value="False">
+                        <ChangePropertyAction TargetObject="EditorRoot"
+                                              PropertyName="Background"
+                                              Value="{StaticResource NeptuneBackgroundBrush}" />
+
+                        <ChangePropertyAction TargetObject="MainViewSplitter"
+                                              PropertyName="BorderBrush"
+                                              Value="{StaticResource NeptuneBackgroundBrush}" />
+                    </DataTriggerBehavior>
+                </Interaction.Behaviors>
+
+                <controls:EditorHost IsVisible="{Binding !!SelectedConfigMember}" />
+            </Grid>
+
+            <Grid VerticalAlignment="Center"
+                  HorizontalAlignment="Center"
+                  IsVisible="{Binding !IsProjectLoaded}"
+                  ZIndex="999"
+                  Grid.ColumnSpan="3"
+                  Grid.ColumnDefinitions="Auto,Auto,Auto"
+                  Grid.RowDefinitions="Auto,Auto">
+                <TextBlock Text="No project open!"
+                           Grid.Row="0"
+                           Grid.ColumnSpan="3"
+                           FontSize="18"
+                           FontWeight="SemiLight"
+                           HorizontalAlignment="Center"
+                           Opacity="0.4" />
+
+                <TextBlock Grid.Row="1"
+                           Grid.Column="0"
+                           Text="Use "
+                           FontSize="18"
+                           FontWeight="SemiLight"
+                           HorizontalAlignment="Center"
+                           Opacity="0.4" />
+
+                <TextBlock Grid.Row="1"
+                           Grid.Column="1"
+                           Text="File > Open source tree (Ctrl+O)"
+                           FontSize="18"
+                           FontWeight="SemiLight"
+                           HorizontalAlignment="Center"
+                           Opacity="0.8" />
+
+                <TextBlock Grid.Row="1"
+                           Grid.Column="2"
+                           Text=" and browse for 2009scape server directory."
+                           FontSize="18"
+                           FontWeight="SemiLight"
+                           HorizontalAlignment="Center"
+                           Opacity="0.4" />
+            </Grid>
+        </Grid>
+    </Grid>
+</glitonea:WindowEx>

+ 14 - 0
Neptune/View/Windows/MainWindow.axaml.cs

@@ -0,0 +1,14 @@
+using Glitonea;
+using PropertyChanged;
+
+namespace Neptune.View.Windows
+{
+    [DoNotNotify]
+    public partial class MainWindow : WindowEx
+    {
+        public MainWindow()
+        {
+            InitializeComponent();
+        }
+    }
+}

+ 22 - 0
Neptune/ViewModel/Controls/EditorHostViewModel.cs

@@ -0,0 +1,22 @@
+using Glitonea.Mvvm;
+using Glitonea.Mvvm.Messaging;
+using Neptune.Infrastructure.Messaging;
+using Neptune.Model.ProjectManagement;
+
+namespace Neptune.ViewModel.Controls
+{
+    public class EditorHostViewModel : ViewModelBase
+    {
+        public ConfigMember SelectedConfigMember { get; private set; }
+
+        public EditorHostViewModel()
+        {
+            Message.Subscribe<SelectedConfigMemberChangedMessage>(this, SelectedConfigMemberChanged);
+        }
+
+        private void SelectedConfigMemberChanged(SelectedConfigMemberChangedMessage msg)
+        {
+            SelectedConfigMember = msg.ConfigMember;
+        }
+    }
+}

+ 42 - 0
Neptune/ViewModel/Controls/ProjectExplorerViewModel.cs

@@ -0,0 +1,42 @@
+using Glitonea.Mvvm;
+using Glitonea.Mvvm.Messaging;
+using Neptune.Infrastructure.Messaging;
+using Neptune.Model.ProjectManagement;
+
+namespace Neptune.ViewModel.Controls
+{
+    public class ProjectExplorerViewModel : ViewModelBase
+    {
+        public Project Project { get; set; }
+        public ConfigMember SelectedMember { get; set; }
+
+        public ProjectExplorerViewModel()
+        {
+            Message.Subscribe<ProjectLoadedMessage>(this, ProjectLoaded);
+            Message.Subscribe<ProjectClosedMessage>(this, ProjectClosed);
+        }
+
+        protected override void OnPropertyChanged(string propertyName)
+        {
+            base.OnPropertyChanged(propertyName);
+
+            if (propertyName == nameof(SelectedMember))
+            {
+                new SelectedConfigMemberChangedMessage(SelectedMember)
+                    .Broadcast();
+            }
+        }
+
+        private void ProjectLoaded(ProjectLoadedMessage msg)
+        {
+            SelectedMember = null;
+            Project = msg.Project;
+        }
+
+        private void ProjectClosed(ProjectClosedMessage _)
+        {
+            SelectedMember = null;
+            Project = null;
+        }
+    }
+}

+ 130 - 0
Neptune/ViewModel/Windows/MainWindowViewModel.cs

@@ -0,0 +1,130 @@
+using System.Collections.ObjectModel;
+using System.Threading.Tasks;
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Controls.ApplicationLifetimes;
+using Glitonea.Extensions;
+using Glitonea.Mvvm;
+using Glitonea.Mvvm.Messaging;
+using Neptune.Infrastructure.Messaging;
+using Neptune.Infrastructure.Services;
+using Neptune.Model.ProjectManagement;
+using Neptune.Model.Storage;
+
+namespace Neptune.ViewModel.Windows
+{
+    public class MainWindowViewModel : ViewModelBase
+    {
+        private readonly IProjectService _projectService;
+        private readonly IRecentProjectService _recentProjectService;
+        private readonly IAppStorageService _appStorageService;
+
+        private AppStorage _appStorage;
+        private Project _project;
+
+        public GridLength GridSplitterPosition
+        {
+            get => new(_appStorage.GridSplitterPosition, GridUnitType.Pixel);
+            set => _appStorage.GridSplitterPosition = value.Value;
+        }
+
+        public bool IsProjectLoaded => Project != null;
+        public ObservableCollection<string> RecentProjects { get; private set; }
+        
+        public Project Project
+        {
+            get => _project;
+            
+            set
+            {
+                _project = value;
+                
+                OnPropertyChanged(nameof(Project));
+                OnPropertyChanged(nameof(IsProjectLoaded));
+            }
+        }
+
+        public ConfigMember SelectedConfigMember { get; private set; }
+
+        public MainWindowViewModel(
+            IProjectService projectService,
+            IRecentProjectService recentProjectService,
+            IAppStorageService appStorageService)
+        {
+            _projectService = projectService;
+            _recentProjectService = recentProjectService;
+            _appStorageService = appStorageService;
+
+            _appStorage = _appStorageService.Retrieve();
+            RecentProjects = _recentProjectService.GetEntries();
+
+            Application.Current
+                .GetDesktopLifetime()
+                .ShutdownRequested += Application_ShutdownRequested;
+            
+            Message.Subscribe<SelectedConfigMemberChangedMessage>(this, SelectedConfigMemberChanged);
+        }
+
+        public async Task BrowseForProjectDirectory()
+        {            
+            var ofd = new OpenFolderDialog
+            {
+                Title = "Browse for 2009scape source tree...",
+            };
+
+            var path = await ofd.ShowAsync(
+                Application.Current
+                    .GetMainWindow()
+            );
+
+            if (path == null)
+                return;
+
+            await LoadProject(path);
+        }
+
+        public async Task SaveCurrentProject()
+        {
+            await _projectService.SaveProject(Project);
+        }
+
+        public void CloseCurrentProject()
+        {
+            if (Project == null)
+                return;
+            
+            Project = null;
+            
+            new ProjectClosedMessage()
+                .Broadcast();
+        }
+
+        public async Task LoadProject(string path)
+        {
+            CloseCurrentProject();
+            
+            Project = await _projectService.OpenProject(path);
+            
+            new ProjectLoadedMessage(Project)
+                .Broadcast();
+
+            _recentProjectService.AddEntry(path);
+        }
+
+        public void ExitApplication()
+        {
+            Application.Current
+                .GetDesktopLifetime()
+                .TryShutdown();
+        }
+
+        private void Application_ShutdownRequested(object sender, ShutdownRequestedEventArgs e)
+        {
+        }
+        
+        private void SelectedConfigMemberChanged(SelectedConfigMemberChangedMessage msg)
+        {
+            SelectedConfigMember = msg.ConfigMember;
+        }
+    }
+}

+ 18 - 0
Neptune/app.manifest

@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
+  <!-- This manifest is used on Windows only.
+       Don't remove it as it might cause problems with window transparency and embeded controls.
+       For more details visit https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests -->
+  <assemblyIdentity version="1.0.0.0" name="AvaloniaTest.Desktop"/>
+
+  <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
+    <application>
+      <!-- A list of the Windows versions that this application has been tested on
+           and is designed to work with. Uncomment the appropriate elements
+           and Windows will automatically select the most compatible environment. -->
+
+      <!-- Windows 10 -->
+      <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
+    </application>
+  </compatibility>
+</assembly>