diff --git a/client/OrangeGuidanceTomestone.csproj b/client/OrangeGuidanceTomestone.csproj
index 6fb78bf..b345367 100755
--- a/client/OrangeGuidanceTomestone.csproj
+++ b/client/OrangeGuidanceTomestone.csproj
@@ -64,8 +64,7 @@
-
-
+
diff --git a/client/Pack.cs b/client/Pack.cs
index dfe6cca..11df96f 100644
--- a/client/Pack.cs
+++ b/client/Pack.cs
@@ -1,7 +1,29 @@
+using YamlDotNet.Serialization.NamingConventions;
+
namespace OrangeGuidanceTomestone;
[Serializable]
public class Pack {
+ internal static Lazy All { get; } = new(() => {
+ var des = new YamlDotNet.Serialization.DeserializerBuilder()
+ .WithNamingConvention(UnderscoredNamingConvention.Instance)
+ .Build();
+ return new[] {
+ "ffxiv",
+ "elden-ring",
+ "dark-souls",
+ }
+ .Select(name => {
+ try {
+ return des.Deserialize(Resourcer.Resource.AsString($"{name}.yaml"));
+ } catch {
+ return null;
+ }
+ })
+ .Where(pack => pack != null)
+ .ToArray()!;
+ });
+
public string Name { get; init; }
public Guid Id { get; init; }
public string[] Templates { get; init; }
diff --git a/client/PluginUi.cs b/client/PluginUi.cs
index e579a1f..7899aea 100644
--- a/client/PluginUi.cs
+++ b/client/PluginUi.cs
@@ -1,6 +1,5 @@
using System.Text;
using ImGuiNET;
-using YamlDotNet.Serialization.NamingConventions;
namespace OrangeGuidanceTomestone;
@@ -33,14 +32,40 @@ public class PluginUi : IDisposable {
return;
}
+ var packPrev = Pack.All.Value[this._pack].Name;
+ if (ImGui.BeginCombo("Pack", packPrev)) {
+ for (var i = 0; i < Pack.All.Value.Length; i++) {
+ var selPack = Pack.All.Value[i];
+ if (!ImGui.Selectable(selPack.Name)) {
+ continue;
+ }
+
+ this._pack = i;
+
+ this._part1 = -1;
+ this._word1 = (-1, -1);
+ this._conj = -1;
+ this._part2 = -1;
+ this._word2 = (-1, -1);
+ }
+
+ ImGui.EndCombo();
+ }
+
+ const string placeholder = "****";
+
void DrawPicker(string id, IReadOnlyList items, ref int x) {
- var preview = x == -1 ? "" : items[x].Replace("{0}", "****");
+ var preview = x == -1 ? "" : items[x].Replace("{0}", placeholder);
if (!ImGui.BeginCombo(id, preview)) {
return;
}
+ if (ImGui.Selectable("")) {
+ x = -1;
+ }
+
for (var i = 0; i < items.Count; i++) {
- var template = items[i].Replace("{0}", "****");
+ var template = items[i].Replace("{0}", placeholder);
if (ImGui.Selectable(template, i == x)) {
x = i;
}
@@ -73,18 +98,15 @@ public class PluginUi : IDisposable {
ImGui.EndCombo();
}
- var pack = new YamlDotNet.Serialization.DeserializerBuilder()
- .WithNamingConvention(UnderscoredNamingConvention.Instance)
- .Build()
- .Deserialize(Resourcer.Resource.AsString("elden-ring.yaml"));
+ var pack = Pack.All.Value[this._pack];
if (this._part1 == -1) {
- ImGui.TextUnformatted("****");
+ ImGui.TextUnformatted(placeholder);
} else {
var preview = new StringBuilder();
var template1 = pack.Templates[this._part1];
- var word1 = this._word1 == (-1, -1) ? "****" : pack.Words[this._word1.Item1].Words[this._word1.Item2];
+ var word1 = this._word1 == (-1, -1) ? placeholder : pack.Words[this._word1.Item1].Words[this._word1.Item2];
preview.Append(string.Format(template1, word1));
if (this._conj != -1) {
@@ -98,7 +120,7 @@ public class PluginUi : IDisposable {
if (this._part2 != -1) {
var template2 = pack.Templates[this._part2];
- var word2 = this._word2 == (-1, -1) ? "****" : pack.Words[this._word2.Item1].Words[this._word2.Item2];
+ var word2 = this._word2 == (-1, -1) ? placeholder : pack.Words[this._word2.Item1].Words[this._word2.Item2];
preview.Append(string.Format(template2, word2));
}
}
@@ -109,12 +131,90 @@ public class PluginUi : IDisposable {
ImGui.Separator();
DrawPicker("Template##part-1", pack.Templates, ref this._part1);
- DrawWordPicker("Word##word-1", pack.Words, ref this._word1);
- DrawPicker("Conjugation##conj", pack.Conjunctions, ref this._conj);
- DrawPicker("Template##part-2", pack.Templates, ref this._part2);
- DrawWordPicker("Word##word-2", pack.Words, ref this._word2);
+ if (this._part1 > -1 && pack.Templates[this._part1].Contains("{0}")) {
+ DrawWordPicker("Word##word-1", pack.Words, ref this._word1);
+ }
+ DrawPicker("Conjunction##conj", pack.Conjunctions, ref this._conj);
+
+ if (this._conj != -1) {
+ DrawPicker("Template##part-2", pack.Templates, ref this._part2);
+ if (this._part2 > -1 && pack.Templates[this._part2].Contains("{0}")) {
+ DrawWordPicker("Word##word-2", pack.Words, ref this._word2);
+ }
+ }
+
+ this.ClearIfNecessary();
+
+ var valid = this.ValidSetup();
+ if (valid) {
+ ImGui.BeginDisabled();
+ }
+
+ if (ImGui.Button("Write") && valid) {
+ }
+
+ if (valid) {
+ ImGui.EndDisabled();
+ }
ImGui.End();
}
+
+ private void ClearIfNecessary() {
+ if (this._pack == -1) {
+ this._part1 = -1;
+ }
+
+ var pack = Pack.All.Value[this._pack];
+
+ if (this._part1 == -1 || !pack.Templates[this._part1].Contains("{0}")) {
+ this._word1 = (-1, -1);
+ }
+
+ if (this._conj == -1) {
+ this._part2 = -1;
+ }
+
+ if (this._part2 == -1 || !pack.Templates[this._part2].Contains("{0}")) {
+ this._word2 = (-1, -1);
+ }
+ }
+
+ private bool ValidSetup() {
+ if (this._pack == -1 || this._part1 == -1) {
+ return false;
+ }
+
+ var pack = Pack.All.Value[this._pack];
+ var template1 = pack.Templates[this._part1];
+ var temp1Variable = template1.Contains("{0}");
+
+ switch (temp1Variable) {
+ case true when this._word1 == (-1, -1):
+ case false when this._word1 != (-1, -1):
+ return false;
+ }
+
+ if (this._conj == -1 && (this._part2 != -1 || this._word2 != (-1, -1))) {
+ return false;
+ }
+
+ if (this._conj != -1) {
+ if (this._part2 == -1) {
+ return false;
+ }
+
+ var template2 = pack.Templates[this._part2];
+ var temp2Variable = template2.Contains("{0}");
+
+ switch (temp2Variable) {
+ case true when this._word2 == (-1, -1):
+ case false when this._word2 != (-1, -1):
+ return false;
+ }
+ }
+
+ return true;
+ }
}
diff --git a/server/.env b/server/.env
new file mode 100644
index 0000000..b2a38e5
--- /dev/null
+++ b/server/.env
@@ -0,0 +1 @@
+DATABASE_URL="sqlite://database.sqlite"
diff --git a/server/.gitignore b/server/.gitignore
index aa193d1..33363fe 100644
--- a/server/.gitignore
+++ b/server/.gitignore
@@ -1,2 +1,3 @@
/target
/database.sqlite*
+/config.toml
diff --git a/server/Cargo.lock b/server/Cargo.lock
index 88e5f5b..83da124 100644
--- a/server/Cargo.lock
+++ b/server/Cargo.lock
@@ -1038,6 +1038,7 @@ dependencies = [
"sha3",
"sqlx",
"tokio",
+ "toml",
"uuid",
"warp",
]
@@ -1388,6 +1389,15 @@ dependencies = [
"tracing",
]
+[[package]]
+name = "toml"
+version = "0.5.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7"
+dependencies = [
+ "serde",
+]
+
[[package]]
name = "tower-service"
version = "0.3.2"
diff --git a/server/Cargo.toml b/server/Cargo.toml
index ef3e977..d232ab1 100644
--- a/server/Cargo.toml
+++ b/server/Cargo.toml
@@ -13,6 +13,7 @@ serde = { version = "1", features = ["derive"] }
serde_yaml = "0.9"
sha3 = "0.10"
sqlx = { version = "0.6", features = ["runtime-tokio-rustls", "sqlite", "chrono"] }
+toml = "0.5"
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
uuid = { version = "1", features = ["serde", "v4"] }
warp = "0.3"
diff --git a/server/src/config.rs b/server/src/config.rs
new file mode 100644
index 0000000..880761e
--- /dev/null
+++ b/server/src/config.rs
@@ -0,0 +1,11 @@
+use std::net::SocketAddr;
+use std::path::PathBuf;
+
+use serde::Deserialize;
+
+#[derive(Debug, Deserialize)]
+pub struct Config {
+ pub address: SocketAddr,
+ pub packs: PathBuf,
+ pub database: String,
+}
diff --git a/server/src/main.rs b/server/src/main.rs
index 70cea9a..cff3af1 100644
--- a/server/src/main.rs
+++ b/server/src/main.rs
@@ -1,7 +1,6 @@
#![feature(let_chains)]
use std::collections::HashMap;
-use std::net::SocketAddr;
use std::sync::Arc;
use anyhow::{Context, Result};
@@ -11,16 +10,19 @@ use sqlx::sqlite::{SqliteConnectOptions, SqlitePoolOptions};
use tokio::sync::RwLock;
use uuid::Uuid;
+use crate::config::Config;
use crate::pack::Pack;
mod pack;
mod message;
mod web;
mod util;
+mod config;
static MIGRATOR: Migrator = sqlx::migrate!();
pub struct State {
+ pub config: Config,
pub db: Pool,
pub packs: RwLock>,
}
@@ -29,7 +31,7 @@ impl State {
pub async fn update_packs(&self) -> Result<()> {
let mut packs = HashMap::new();
- let mut dir = tokio::fs::read_dir("packs").await?;
+ let mut dir = tokio::fs::read_dir(&self.config.packs).await?;
while let Ok(Some(entry)) = dir.next_entry().await {
if !entry.path().is_file() {
continue;
@@ -64,6 +66,18 @@ impl State {
#[tokio::main]
async fn main() -> Result<()> {
+ let args: Vec = std::env::args().skip(1).collect();
+ if args.is_empty() {
+ eprintln!("usage: server [config]");
+ return Ok(());
+ }
+
+ let config_str = tokio::fs::read_to_string(&args[0])
+ .await
+ .with_context(|| format!("could not read config file at {}", args[0]))?;
+ let config: Config = toml::from_str(&config_str)
+ .context("could not parse config file")?;
+
let options = SqliteConnectOptions::new();
// options.log_statements(LevelFilter::Debug);
@@ -76,7 +90,7 @@ async fn main() -> Result<()> {
Ok(())
}))
// .connect_with(options.filename(&config.database.path))
- .connect_with(options.filename("./database.sqlite"))
+ .connect_with(options.filename(&config.database))
.await
.context("could not connect to database")?;
MIGRATOR.run(&pool)
@@ -84,12 +98,14 @@ async fn main() -> Result<()> {
.context("could not run database migrations")?;
let state = Arc::new(State {
+ config,
db: pool,
packs: Default::default(),
});
state.update_packs().await?;
- warp::serve(web::routes(state)).run("127.0.0.1:8080".parse::()?).await;
+ let address = state.config.address.clone();
+ warp::serve(web::routes(state)).run(address).await;
Ok(())
}
diff --git a/server/src/web/vote.rs b/server/src/web/vote.rs
index b7cd5be..5e99435 100644
--- a/server/src/web/vote.rs
+++ b/server/src/web/vote.rs
@@ -30,10 +30,11 @@ async fn logic(state: Arc, id: i64, message_id: Uuid, vote: i8) -> Result
};
sqlx::query!(
// language=sqlite
- "insert or ignore into votes (user, message, vote) values (?, ?, ?)",
+ "insert into votes (user, message, vote) values (?, ?, ?) on conflict do update set vote = ?",
id,
message_id,
vote,
+ vote,
)
.execute(&state.db)
.await