Added ui
4
.gitignore
vendored
@@ -3,5 +3,5 @@ target/
|
||||
.idea/
|
||||
**/Cargo.lock
|
||||
|
||||
logs/
|
||||
app/
|
||||
.next/
|
||||
node_modules/
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
RUST_LOG=warn,bot=info
|
||||
COMPOSE_PROJECT_NAME=siren
|
||||
|
||||
SERVICE_HOST=localhost
|
||||
SERVICE_PORT=5000
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
version: '3.8'
|
||||
|
||||
name: siren
|
||||
services:
|
||||
bot:
|
||||
image: siren-bot:${BOT_VERSION:-latest}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
RUST_LOG=warn,service=info
|
||||
COMPOSE_PROJECT_NAME=siren
|
||||
|
||||
DATABASE_USER=siren
|
||||
DATABASE_PASSWORD=
|
||||
|
||||
@@ -1 +1 @@
|
||||
SIREN_VERSION=0.2.4
|
||||
SIREN_VERSION=0.2.5
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "service"
|
||||
version = "0.2.4"
|
||||
version = "0.2.5"
|
||||
edition = "2021"
|
||||
authors = ["Ben Sherriff <hello@bensherriff.com>"]
|
||||
repository = "https://github.com/bensherriff/siren"
|
||||
@@ -14,6 +14,7 @@ path = "src/lib.rs"
|
||||
[dependencies]
|
||||
actix-web = "4.4.0"
|
||||
actix-rt = "2.9.0"
|
||||
actix-cors = "0.6.4"
|
||||
actix-web-httpauth = "0.8.1"
|
||||
chrono = { version = "0.4.31", features = ["serde"] }
|
||||
dotenv = "0.15.0"
|
||||
|
||||
@@ -5,12 +5,12 @@
|
||||
"level": 0,
|
||||
"ritual": false,
|
||||
"casting_time": {
|
||||
"amount": 1,
|
||||
"value": 1,
|
||||
"unit": "action"
|
||||
},
|
||||
"range": {
|
||||
"type": "point",
|
||||
"amount": 60,
|
||||
"value": 60,
|
||||
"unit": "feet"
|
||||
},
|
||||
"saving_throw": [
|
||||
@@ -48,7 +48,7 @@
|
||||
"level": 0,
|
||||
"ritual": false,
|
||||
"casting_time": {
|
||||
"amount": 1,
|
||||
"value": 1,
|
||||
"unit": "action"
|
||||
},
|
||||
"range": {
|
||||
@@ -67,7 +67,7 @@
|
||||
"durations": [
|
||||
{
|
||||
"type": "timed",
|
||||
"amount": 1,
|
||||
"value": 1,
|
||||
"unit": "round"
|
||||
}
|
||||
],
|
||||
@@ -87,7 +87,7 @@
|
||||
"level": 0,
|
||||
"ritual": false,
|
||||
"casting_time": {
|
||||
"amount": 1,
|
||||
"value": 1,
|
||||
"unit": "action"
|
||||
},
|
||||
"range": {
|
||||
@@ -95,7 +95,7 @@
|
||||
},
|
||||
"area": {
|
||||
"type": "sphere",
|
||||
"amount": 5,
|
||||
"value": 5,
|
||||
"unit": "feet"
|
||||
},
|
||||
"damage_inflict": [
|
||||
@@ -111,7 +111,7 @@
|
||||
"durations": [
|
||||
{
|
||||
"type": "timed",
|
||||
"amount": 1,
|
||||
"value": 1,
|
||||
"unit": "round"
|
||||
}
|
||||
],
|
||||
@@ -133,12 +133,12 @@
|
||||
"level": 0,
|
||||
"ritual": false,
|
||||
"casting_time": {
|
||||
"amount": 1,
|
||||
"value": 1,
|
||||
"unit": "action"
|
||||
},
|
||||
"range": {
|
||||
"type": "point",
|
||||
"amount": 120,
|
||||
"value": 120,
|
||||
"unit": "feet"
|
||||
},
|
||||
"damage_inflict": [
|
||||
@@ -153,7 +153,7 @@
|
||||
"durations": [
|
||||
{
|
||||
"type": "timed",
|
||||
"amount": 1,
|
||||
"value": 1,
|
||||
"unit": "round"
|
||||
}
|
||||
],
|
||||
@@ -176,17 +176,17 @@
|
||||
"level": 0,
|
||||
"ritual": false,
|
||||
"casting_time": {
|
||||
"amount": 1,
|
||||
"value": 1,
|
||||
"unit": "action"
|
||||
},
|
||||
"range": {
|
||||
"type": "point",
|
||||
"amount": 60,
|
||||
"value": 60,
|
||||
"unit": "feet"
|
||||
},
|
||||
"area": {
|
||||
"type": "cube",
|
||||
"amount": 5,
|
||||
"value": 5,
|
||||
"unit": "feet"
|
||||
},
|
||||
"components": {
|
||||
@@ -200,7 +200,7 @@
|
||||
},
|
||||
{
|
||||
"type": "timed",
|
||||
"amount": 1,
|
||||
"value": 1,
|
||||
"unit": "hour"
|
||||
}
|
||||
],
|
||||
@@ -231,17 +231,17 @@
|
||||
"level": 0,
|
||||
"ritual": false,
|
||||
"casting_time": {
|
||||
"amount": 1,
|
||||
"value": 1,
|
||||
"unit": "action"
|
||||
},
|
||||
"range": {
|
||||
"type": "point",
|
||||
"amount": 60,
|
||||
"value": 60,
|
||||
"unit": "feet"
|
||||
},
|
||||
"area": {
|
||||
"type": "cube",
|
||||
"amount": 5,
|
||||
"value": 5,
|
||||
"unit": "feet"
|
||||
},
|
||||
"components": {
|
||||
@@ -252,7 +252,7 @@
|
||||
"durations": [
|
||||
{
|
||||
"type": "concentration",
|
||||
"amount": 1,
|
||||
"value": 1,
|
||||
"unit": "minutes"
|
||||
}
|
||||
],
|
||||
@@ -275,12 +275,12 @@
|
||||
"level": 0,
|
||||
"ritual": false,
|
||||
"casting_time": {
|
||||
"amount": 1,
|
||||
"value": 1,
|
||||
"unit": "action"
|
||||
},
|
||||
"range": {
|
||||
"type": "point",
|
||||
"amount": 120,
|
||||
"value": 120,
|
||||
"unit": "feet"
|
||||
},
|
||||
"components": {
|
||||
@@ -292,7 +292,7 @@
|
||||
"durations": [
|
||||
{
|
||||
"type": "concentration",
|
||||
"amount": 1,
|
||||
"value": 1,
|
||||
"unit": "minutes"
|
||||
}
|
||||
],
|
||||
@@ -303,7 +303,7 @@
|
||||
],
|
||||
"description": {
|
||||
"entries": [
|
||||
"You create up to four torch-amountd lights within range, making them appear as torches, lanterns, or glowing orbs that hover in the air for the duration. You can also combine the four lights into one glowing vaguely humanoid form of Medium amount. Whichever form you choose, each light sheds dim light in a 10-foot radius.",
|
||||
"You create up to four torch-valued lights within range, making them appear as torches, lanterns, or glowing orbs that hover in the air for the duration. You can also combine the four lights into one glowing vaguely humanoid form of Medium value. Whichever form you choose, each light sheds dim light in a 10-foot radius.",
|
||||
"As a bonus action on your turn, you can move the lights up to 60 feet to a new spot within range. A light must be within 20 feet of another light created by this spell, and a light winks out if it exceeds the spell's range."
|
||||
]
|
||||
}
|
||||
@@ -314,12 +314,12 @@
|
||||
"level": 0,
|
||||
"ritual": false,
|
||||
"casting_time": {
|
||||
"amount": 1,
|
||||
"value": 1,
|
||||
"unit": "action"
|
||||
},
|
||||
"range": {
|
||||
"type": "point",
|
||||
"amount": 30,
|
||||
"value": 30,
|
||||
"unit": "feet"
|
||||
},
|
||||
"components": {
|
||||
@@ -358,12 +358,12 @@
|
||||
"level": 0,
|
||||
"ritual": false,
|
||||
"casting_time": {
|
||||
"amount": 1,
|
||||
"value": 1,
|
||||
"unit": "action"
|
||||
},
|
||||
"range": {
|
||||
"type": "point",
|
||||
"amount": 120,
|
||||
"value": 120,
|
||||
"unit": "feet"
|
||||
},
|
||||
"damage_inflict": [
|
||||
@@ -398,7 +398,7 @@
|
||||
"level": 0,
|
||||
"ritual": true,
|
||||
"casting_time": {
|
||||
"amount": 1,
|
||||
"value": 1,
|
||||
"unit": "action"
|
||||
},
|
||||
"range": {
|
||||
@@ -412,7 +412,7 @@
|
||||
"durations": [
|
||||
{
|
||||
"type": "timed",
|
||||
"amount": 8,
|
||||
"value": 8,
|
||||
"unit": "hours"
|
||||
}
|
||||
],
|
||||
@@ -434,12 +434,12 @@
|
||||
"level": 0,
|
||||
"ritual": false,
|
||||
"casting_time": {
|
||||
"amount": 1,
|
||||
"value": 1,
|
||||
"unit": "action"
|
||||
},
|
||||
"range": {
|
||||
"type": "point",
|
||||
"amount": 120,
|
||||
"value": 120,
|
||||
"unit": "feet"
|
||||
},
|
||||
"damage_inflict": [
|
||||
@@ -474,7 +474,7 @@
|
||||
"level": 0,
|
||||
"ritual": false,
|
||||
"casting_time": {
|
||||
"amount": 1,
|
||||
"value": 1,
|
||||
"unit": "action"
|
||||
},
|
||||
"range": {
|
||||
@@ -484,12 +484,12 @@
|
||||
"verbal": false,
|
||||
"somatic": true,
|
||||
"material": true,
|
||||
"materials_needed": "a small amount of makeup applied to the face as this spell is cast"
|
||||
"materials_needed": "a small value of makeup applied to the face as this spell is cast"
|
||||
},
|
||||
"durations": [
|
||||
{
|
||||
"type": "concentration",
|
||||
"amount": 1,
|
||||
"value": 1,
|
||||
"unit": "minutes"
|
||||
}
|
||||
],
|
||||
@@ -510,12 +510,12 @@
|
||||
"level": 0,
|
||||
"ritual": false,
|
||||
"casting_time": {
|
||||
"amount": 1,
|
||||
"value": 1,
|
||||
"unit": "action"
|
||||
},
|
||||
"range": {
|
||||
"type": "point",
|
||||
"amount": 60,
|
||||
"value": 60,
|
||||
"unit": "feet"
|
||||
},
|
||||
"saving_throw": [
|
||||
@@ -552,7 +552,7 @@
|
||||
"level": 0,
|
||||
"ritual": false,
|
||||
"casting_time": {
|
||||
"amount": 1,
|
||||
"value": 1,
|
||||
"unit": "action"
|
||||
},
|
||||
"range": {
|
||||
@@ -560,7 +560,7 @@
|
||||
},
|
||||
"area": {
|
||||
"type": "sphere",
|
||||
"amount": 5,
|
||||
"value": 5,
|
||||
"unit": "feet"
|
||||
},
|
||||
"damage_inflict": [
|
||||
@@ -596,7 +596,7 @@
|
||||
"level": 0,
|
||||
"ritual": false,
|
||||
"casting_time": {
|
||||
"amount": 1,
|
||||
"value": 1,
|
||||
"unit": "action"
|
||||
},
|
||||
"range": {
|
||||
@@ -610,7 +610,7 @@
|
||||
"durations": [
|
||||
{
|
||||
"type": "concentration",
|
||||
"amount": 1,
|
||||
"value": 1,
|
||||
"unit": "minutes"
|
||||
}
|
||||
],
|
||||
@@ -631,12 +631,12 @@
|
||||
"level": 0,
|
||||
"ritual": false,
|
||||
"casting_time": {
|
||||
"amount": 1,
|
||||
"value": 1,
|
||||
"unit": "action"
|
||||
},
|
||||
"range": {
|
||||
"type": "point",
|
||||
"amount": 30,
|
||||
"value": 30,
|
||||
"unit": "feet"
|
||||
},
|
||||
"components": {
|
||||
@@ -674,12 +674,12 @@
|
||||
"level": 0,
|
||||
"ritual": false,
|
||||
"casting_time": {
|
||||
"amount": 1,
|
||||
"value": 1,
|
||||
"unit": "action"
|
||||
},
|
||||
"range": {
|
||||
"type": "point",
|
||||
"amount": 30,
|
||||
"value": 30,
|
||||
"unit": "feet"
|
||||
},
|
||||
"saving_throw": [
|
||||
@@ -716,7 +716,7 @@
|
||||
"level": 0,
|
||||
"ritual": false,
|
||||
"casting_time": {
|
||||
"amount": 1,
|
||||
"value": 1,
|
||||
"unit": "action"
|
||||
},
|
||||
"range": {
|
||||
@@ -724,7 +724,7 @@
|
||||
},
|
||||
"area": {
|
||||
"type": "sphere",
|
||||
"amount": 20,
|
||||
"value": 20,
|
||||
"unit": "feet"
|
||||
},
|
||||
"saving_throw": [
|
||||
@@ -739,7 +739,7 @@
|
||||
"durations": [
|
||||
{
|
||||
"type": "timed",
|
||||
"amount": 1,
|
||||
"value": 1,
|
||||
"unit": "hour"
|
||||
}
|
||||
],
|
||||
@@ -761,12 +761,12 @@
|
||||
"level": 0,
|
||||
"ritual": false,
|
||||
"casting_time": {
|
||||
"amount": 1,
|
||||
"value": 1,
|
||||
"unit": "action"
|
||||
},
|
||||
"range": {
|
||||
"type": "self",
|
||||
"amount": 15,
|
||||
"value": 15,
|
||||
"unit": "feet"
|
||||
},
|
||||
"saving_throw": [
|
||||
@@ -803,12 +803,12 @@
|
||||
"level": 0,
|
||||
"ritual": false,
|
||||
"casting_time": {
|
||||
"amount": 1,
|
||||
"value": 1,
|
||||
"unit": "action"
|
||||
},
|
||||
"range": {
|
||||
"type": "point",
|
||||
"amount": 30,
|
||||
"value": 30,
|
||||
"unit": "feet"
|
||||
},
|
||||
"components": {
|
||||
@@ -819,7 +819,7 @@
|
||||
"durations": [
|
||||
{
|
||||
"type": "timed",
|
||||
"amount": 1,
|
||||
"value": 1,
|
||||
"unit": "minutes"
|
||||
}
|
||||
],
|
||||
@@ -842,7 +842,7 @@
|
||||
"level": 0,
|
||||
"ritual": false,
|
||||
"casting_time": {
|
||||
"amount": 1,
|
||||
"value": 1,
|
||||
"unit": "bonus"
|
||||
},
|
||||
"range": {
|
||||
@@ -860,7 +860,7 @@
|
||||
"durations": [
|
||||
{
|
||||
"type": "timed",
|
||||
"amount": 1,
|
||||
"value": 1,
|
||||
"unit": "minutes"
|
||||
}
|
||||
],
|
||||
@@ -881,7 +881,7 @@
|
||||
"level": 0,
|
||||
"ritual": false,
|
||||
"casting_time": {
|
||||
"amount": 1,
|
||||
"value": 1,
|
||||
"unit": "minutes"
|
||||
},
|
||||
"range": {
|
||||
@@ -916,12 +916,12 @@
|
||||
"level": 0,
|
||||
"ritual": false,
|
||||
"casting_time": {
|
||||
"amount": 1,
|
||||
"value": 1,
|
||||
"unit": "action"
|
||||
},
|
||||
"range": {
|
||||
"type": "point",
|
||||
"amount": 120,
|
||||
"value": 120,
|
||||
"unit": "feet"
|
||||
},
|
||||
"components": {
|
||||
@@ -933,7 +933,7 @@
|
||||
"durations": [
|
||||
{
|
||||
"type": "timed",
|
||||
"amount": 1,
|
||||
"value": 1,
|
||||
"unit": "round"
|
||||
}
|
||||
],
|
||||
@@ -955,12 +955,12 @@
|
||||
"level": 0,
|
||||
"ritual": false,
|
||||
"casting_time": {
|
||||
"amount": 1,
|
||||
"value": 1,
|
||||
"unit": "action"
|
||||
},
|
||||
"range": {
|
||||
"type": "point",
|
||||
"amount": 60,
|
||||
"value": 60,
|
||||
"unit": "feet"
|
||||
},
|
||||
"saving_throw": [
|
||||
@@ -996,12 +996,12 @@
|
||||
"level": 0,
|
||||
"ritual": false,
|
||||
"casting_time": {
|
||||
"amount": 1,
|
||||
"value": 1,
|
||||
"unit": "action"
|
||||
},
|
||||
"range": {
|
||||
"type": "point",
|
||||
"amount": 30,
|
||||
"value": 30,
|
||||
"unit": "feet"
|
||||
},
|
||||
"components": {
|
||||
@@ -1013,7 +1013,7 @@
|
||||
"durations": [
|
||||
{
|
||||
"type": "timed",
|
||||
"amount": 1,
|
||||
"value": 1,
|
||||
"unit": "minutes"
|
||||
}
|
||||
],
|
||||
@@ -1037,17 +1037,17 @@
|
||||
"level": 0,
|
||||
"ritual": false,
|
||||
"casting_time": {
|
||||
"amount": 1,
|
||||
"value": 1,
|
||||
"unit": "action"
|
||||
},
|
||||
"range": {
|
||||
"type": "point",
|
||||
"amount": 30,
|
||||
"value": 30,
|
||||
"unit": "feet"
|
||||
},
|
||||
"area": {
|
||||
"type": "cube",
|
||||
"amount": 5,
|
||||
"value": 5,
|
||||
"unit": "feet"
|
||||
},
|
||||
"components": {
|
||||
@@ -1061,7 +1061,7 @@
|
||||
},
|
||||
{
|
||||
"type": "timed",
|
||||
"amount": 1,
|
||||
"value": 1,
|
||||
"unit": "hour"
|
||||
}
|
||||
],
|
||||
@@ -1090,12 +1090,12 @@
|
||||
"level": 0,
|
||||
"ritual": false,
|
||||
"casting_time": {
|
||||
"amount": 1,
|
||||
"value": 1,
|
||||
"unit": "action"
|
||||
},
|
||||
"range": {
|
||||
"type": "point",
|
||||
"amount": 10,
|
||||
"value": 10,
|
||||
"unit": "feet"
|
||||
},
|
||||
"saving_throw": [
|
||||
@@ -1134,11 +1134,11 @@
|
||||
"ritual": false,
|
||||
"casting_time": {
|
||||
"unit": "action",
|
||||
"amount": 1
|
||||
"value": 1
|
||||
},
|
||||
"range": {
|
||||
"type": "point",
|
||||
"amount": 10,
|
||||
"value": 10,
|
||||
"unit": "feet"
|
||||
},
|
||||
"components": {
|
||||
@@ -1149,7 +1149,7 @@
|
||||
"durations": [
|
||||
{
|
||||
"type": "timed",
|
||||
"amount": 1,
|
||||
"value": 1,
|
||||
"unit": "hour"
|
||||
}
|
||||
],
|
||||
@@ -1183,7 +1183,7 @@
|
||||
"ritual": false,
|
||||
"casting_time": {
|
||||
"unit": "action",
|
||||
"amount": 1
|
||||
"value": 1
|
||||
},
|
||||
"range": {
|
||||
"type": "self"
|
||||
@@ -1220,7 +1220,7 @@
|
||||
"ritual": false,
|
||||
"casting_time": {
|
||||
"unit": "action",
|
||||
"amount": 1
|
||||
"value": 1
|
||||
},
|
||||
"range": {
|
||||
"type": "self"
|
||||
@@ -1237,7 +1237,7 @@
|
||||
"durations": [
|
||||
{
|
||||
"type": "timed",
|
||||
"amount": 10,
|
||||
"value": 10,
|
||||
"unit": "minutes"
|
||||
}
|
||||
],
|
||||
@@ -1261,11 +1261,11 @@
|
||||
"ritual": false,
|
||||
"casting_time": {
|
||||
"unit": "action",
|
||||
"amount": 1
|
||||
"value": 1
|
||||
},
|
||||
"range": {
|
||||
"type": "point",
|
||||
"amount": 60,
|
||||
"value": 60,
|
||||
"unit": "feet"
|
||||
},
|
||||
"damage_inflict": [
|
||||
@@ -1301,7 +1301,7 @@
|
||||
"ritual": false,
|
||||
"casting_time": {
|
||||
"unit": "action",
|
||||
"amount": 1
|
||||
"value": 1
|
||||
},
|
||||
"range": {
|
||||
"type": "touch"
|
||||
@@ -1315,7 +1315,7 @@
|
||||
"durations": [
|
||||
{
|
||||
"type": "concentration",
|
||||
"amount": 1,
|
||||
"value": 1,
|
||||
"unit": "round"
|
||||
}
|
||||
],
|
||||
@@ -1336,12 +1336,12 @@
|
||||
"level": 0,
|
||||
"ritual": false,
|
||||
"casting_time": {
|
||||
"amount": 1,
|
||||
"value": 1,
|
||||
"unit": "action"
|
||||
},
|
||||
"range": {
|
||||
"type": "point",
|
||||
"amount": 60,
|
||||
"value": 60,
|
||||
"unit": "feet"
|
||||
},
|
||||
"saving_throw": [
|
||||
@@ -1378,7 +1378,7 @@
|
||||
"level": 0,
|
||||
"ritual": false,
|
||||
"casting_time": {
|
||||
"amount": 1,
|
||||
"value": 1,
|
||||
"unit": "action"
|
||||
},
|
||||
"damage_inflict": [
|
||||
@@ -1389,7 +1389,7 @@
|
||||
],
|
||||
"range": {
|
||||
"type": "point",
|
||||
"amount": 30,
|
||||
"value": 30,
|
||||
"unit": "feet"
|
||||
},
|
||||
"components": {
|
||||
@@ -1419,17 +1419,17 @@
|
||||
"level": 0,
|
||||
"ritual": false,
|
||||
"casting_time": {
|
||||
"amount": 1,
|
||||
"value": 1,
|
||||
"unit": "action"
|
||||
},
|
||||
"range": {
|
||||
"type": "point",
|
||||
"amount": 30,
|
||||
"value": 30,
|
||||
"unit": "feet"
|
||||
},
|
||||
"area": {
|
||||
"type": "cube",
|
||||
"amount": 5,
|
||||
"value": 5,
|
||||
"unit": "feet"
|
||||
},
|
||||
"components": {
|
||||
@@ -1443,7 +1443,7 @@
|
||||
},
|
||||
{
|
||||
"type": "timed",
|
||||
"amount": 1,
|
||||
"value": 1,
|
||||
"unit": "hour"
|
||||
}
|
||||
],
|
||||
@@ -1473,7 +1473,7 @@
|
||||
"level": 0,
|
||||
"ritual": false,
|
||||
"casting_time": {
|
||||
"amount": 1,
|
||||
"value": 1,
|
||||
"unit": "bonus"
|
||||
},
|
||||
"range": {
|
||||
@@ -1492,7 +1492,7 @@
|
||||
"durations": [
|
||||
{
|
||||
"type": "timed",
|
||||
"amount": 1,
|
||||
"value": 1,
|
||||
"unit": "minutes"
|
||||
}
|
||||
],
|
||||
@@ -1513,7 +1513,7 @@
|
||||
"level": 0,
|
||||
"ritual": false,
|
||||
"casting_time": {
|
||||
"amount": 1,
|
||||
"value": 1,
|
||||
"unit": "action"
|
||||
},
|
||||
"damage_inflict": [
|
||||
@@ -1551,7 +1551,7 @@
|
||||
"level": 0,
|
||||
"ritual": false,
|
||||
"casting_time": {
|
||||
"amount": 1,
|
||||
"value": 1,
|
||||
"unit": "action"
|
||||
},
|
||||
"range": {
|
||||
@@ -1584,7 +1584,7 @@
|
||||
"level": 0,
|
||||
"ritual": false,
|
||||
"casting_time": {
|
||||
"amount": 1,
|
||||
"value": 1,
|
||||
"unit": "action"
|
||||
},
|
||||
"damage_inflict": [
|
||||
@@ -1595,7 +1595,7 @@
|
||||
],
|
||||
"range": {
|
||||
"type": "self",
|
||||
"amount": 5,
|
||||
"value": 5,
|
||||
"unit": "feet"
|
||||
},
|
||||
"components": {
|
||||
@@ -1625,12 +1625,12 @@
|
||||
"level": 0,
|
||||
"ritual": false,
|
||||
"casting_time": {
|
||||
"amount": 1,
|
||||
"value": 1,
|
||||
"unit": "action"
|
||||
},
|
||||
"range": {
|
||||
"type": "point",
|
||||
"amount": 30,
|
||||
"value": 30,
|
||||
"unit": "feet"
|
||||
},
|
||||
"components": {
|
||||
@@ -1641,7 +1641,7 @@
|
||||
"durations": [
|
||||
{
|
||||
"type": "timed",
|
||||
"amount": 1,
|
||||
"value": 1,
|
||||
"unit": "minutes"
|
||||
}
|
||||
],
|
||||
@@ -1674,7 +1674,7 @@
|
||||
"level": 0,
|
||||
"ritual": false,
|
||||
"casting_time": {
|
||||
"amount": 1,
|
||||
"value": 1,
|
||||
"unit": "action"
|
||||
},
|
||||
"damage_inflict": [
|
||||
@@ -1683,7 +1683,7 @@
|
||||
"attack_type": "melee",
|
||||
"range": {
|
||||
"type": "point",
|
||||
"amount": 30,
|
||||
"value": 30,
|
||||
"unit": "feet"
|
||||
},
|
||||
"components": {
|
||||
@@ -1715,7 +1715,7 @@
|
||||
"level": 0,
|
||||
"ritual": false,
|
||||
"casting_time": {
|
||||
"amount": 1,
|
||||
"value": 1,
|
||||
"unit": "action"
|
||||
},
|
||||
"damage_inflict": [
|
||||
@@ -1726,7 +1726,7 @@
|
||||
],
|
||||
"range": {
|
||||
"type": "point",
|
||||
"amount": 5,
|
||||
"value": 5,
|
||||
"unit": "feet"
|
||||
},
|
||||
"components": {
|
||||
@@ -1756,7 +1756,7 @@
|
||||
"level": 0,
|
||||
"ritual": false,
|
||||
"casting_time": {
|
||||
"amount": 1,
|
||||
"value": 1,
|
||||
"unit": "action"
|
||||
},
|
||||
"damage_inflict": [
|
||||
@@ -1767,7 +1767,7 @@
|
||||
],
|
||||
"range": {
|
||||
"type": "point",
|
||||
"amount": 60,
|
||||
"value": 60,
|
||||
"unit": "feet"
|
||||
},
|
||||
"components": {
|
||||
@@ -1797,12 +1797,12 @@
|
||||
"level": 0,
|
||||
"ritual": false,
|
||||
"casting_time": {
|
||||
"amount": 1,
|
||||
"value": 1,
|
||||
"unit": "action"
|
||||
},
|
||||
"range": {
|
||||
"type": "point",
|
||||
"amount": 30,
|
||||
"value": 30,
|
||||
"unit": "feet"
|
||||
},
|
||||
"components": {
|
||||
@@ -1813,7 +1813,7 @@
|
||||
"durations": [
|
||||
{
|
||||
"type": "concentration",
|
||||
"amount": 1,
|
||||
"value": 1,
|
||||
"unit": "round"
|
||||
}
|
||||
],
|
||||
@@ -1834,7 +1834,7 @@
|
||||
"level": 0,
|
||||
"ritual": false,
|
||||
"casting_time": {
|
||||
"amount": 1,
|
||||
"value": 1,
|
||||
"unit": "action"
|
||||
},
|
||||
"damage_inflict": [
|
||||
@@ -1845,7 +1845,7 @@
|
||||
],
|
||||
"range": {
|
||||
"type": "point",
|
||||
"amount": 60,
|
||||
"value": 60,
|
||||
"unit": "feet"
|
||||
},
|
||||
"components": {
|
||||
@@ -1877,7 +1877,7 @@
|
||||
"ritual": false,
|
||||
"casting_time": {
|
||||
"unit": "action",
|
||||
"amount": 1
|
||||
"value": 1
|
||||
},
|
||||
"damage_inflict": [
|
||||
"radiant"
|
||||
@@ -1887,7 +1887,7 @@
|
||||
],
|
||||
"range": {
|
||||
"type": "point",
|
||||
"amount": 5,
|
||||
"value": 5,
|
||||
"unit": "feet"
|
||||
},
|
||||
"components": {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
version: '3.8'
|
||||
|
||||
name: siren
|
||||
services:
|
||||
service:
|
||||
image: siren-service:${SIREN_VERSION:-latest}
|
||||
@@ -14,7 +15,7 @@ services:
|
||||
environment:
|
||||
DATABASE_HOST: db
|
||||
DATABASE_PORT: 5432
|
||||
SERVICE_HOST: siren
|
||||
SERVICE_HOST: service
|
||||
SERVICE_PORT: 5000
|
||||
ports:
|
||||
- ${SERVICE_PORT:-5000}:5000
|
||||
|
||||
@@ -6,7 +6,7 @@ use crate::db::{schema::spells::{self}, classes::AbilityType, conditions::Condit
|
||||
|
||||
use super::{SchoolType, CastingTime, CastingType, SpellAttackType, SpellDamageType, Range, Area, Components, Duration, Source, Description, DurationType};
|
||||
|
||||
#[derive(Queryable, QueryableByName)]
|
||||
#[derive(Queryable, QueryableByName, Serialize, Deserialize)]
|
||||
#[diesel(table_name = spells)]
|
||||
pub struct QuerySpell {
|
||||
pub id: i32,
|
||||
@@ -191,6 +191,7 @@ impl InsertSpell {
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Spell {
|
||||
pub id: Option<i32>,
|
||||
pub name: String,
|
||||
pub school: SchoolType,
|
||||
pub level: i32,
|
||||
@@ -226,17 +227,18 @@ impl From<QuerySpell> for Spell {
|
||||
Err(err) => {
|
||||
log::error!("Failed to parse spell: {}", err);
|
||||
Self {
|
||||
id: None,
|
||||
name: "".to_string(),
|
||||
school: SchoolType::Abjuration,
|
||||
level: 0,
|
||||
ritual: false,
|
||||
casting_time: CastingTime { amount: 0, casting_type: CastingType::Action },
|
||||
casting_time: CastingTime { value: 0, casting_type: CastingType::Action },
|
||||
saving_throw: None,
|
||||
attack_type: None,
|
||||
damage_inflict: None,
|
||||
damage_resist: None,
|
||||
conditions: None,
|
||||
range: Range { range_type: "".to_string(), amount: None, unit: None },
|
||||
range: Range { range_type: "".to_string(), value: None, unit: None },
|
||||
area: None,
|
||||
components: Components { verbal: false, somatic: false, material: false, materials_needed: None, materials_cost: None, materials_consumed: None },
|
||||
durations: vec![],
|
||||
|
||||
@@ -73,7 +73,7 @@ async fn get_all(req: HttpRequest) -> HttpResponse {
|
||||
None => None
|
||||
};
|
||||
// Limit must be between 1 and 100
|
||||
let limit = std::cmp::min(std::cmp::max(params.limit.unwrap_or(20), 1), 100);
|
||||
let limit = std::cmp::min(std::cmp::max(params.limit.unwrap_or(100), 1), 100);
|
||||
let total_count = QuerySpell::get_count(&filters).unwrap();
|
||||
let max_page = std::cmp::max((total_count as f64 / limit as f64).ceil() as i32, 1);
|
||||
// Page must be between 1 and max_page
|
||||
@@ -82,8 +82,11 @@ async fn get_all(req: HttpRequest) -> HttpResponse {
|
||||
match web::block(move || QuerySpell::get_all(&filters, limit, page)).await.unwrap() {
|
||||
Ok(spells) => {
|
||||
let mut response: Vec<Spell> = Vec::new();
|
||||
for spell in spells {
|
||||
response.push(Spell::from(spell));
|
||||
for query_spell in spells {
|
||||
let id = query_spell.id;
|
||||
let mut spell = Spell::from(query_spell);
|
||||
spell.id = Some(id);
|
||||
response.push(spell);
|
||||
}
|
||||
HttpResponse::Ok().json(GetResponse {
|
||||
data: response,
|
||||
@@ -112,10 +115,15 @@ async fn get_by_id(id: web::Path<String>) -> HttpResponse {
|
||||
})
|
||||
};
|
||||
match web::block(move || QuerySpell::get_by_id(id)).await.unwrap() {
|
||||
Ok(spell) => HttpResponse::Ok().json(GetResponse {
|
||||
data: Spell::from(spell),
|
||||
metadata: None
|
||||
}),
|
||||
Ok(query_spell) => {
|
||||
let id = query_spell.id;
|
||||
let mut spell = Spell::from(query_spell);
|
||||
spell.id = Some(id);
|
||||
HttpResponse::Ok().json(GetResponse {
|
||||
data: spell,
|
||||
metadata: None
|
||||
})
|
||||
},
|
||||
Err(err) => {
|
||||
error!("{:?}", err.message);
|
||||
ResponseError::error_response(&err)
|
||||
|
||||
@@ -56,7 +56,7 @@ impl FromStr for SchoolType {
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct CastingTime {
|
||||
pub amount: i32,
|
||||
pub value: i32,
|
||||
#[serde(rename = "unit")]
|
||||
pub casting_type: CastingType
|
||||
}
|
||||
@@ -209,7 +209,7 @@ pub struct Range {
|
||||
#[serde(rename = "type")]
|
||||
pub range_type: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub amount: Option<i32>,
|
||||
pub value: Option<i32>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub unit: Option<String>
|
||||
}
|
||||
@@ -219,7 +219,7 @@ pub struct Area {
|
||||
#[serde(rename = "type")]
|
||||
pub area_type: AreaType,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub amount: Option<i32>,
|
||||
pub value: Option<i32>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub unit: Option<String>
|
||||
}
|
||||
@@ -270,7 +270,7 @@ pub struct Duration {
|
||||
#[serde(rename = "type")]
|
||||
pub duration_type: DurationType,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub amount: Option<i32>,
|
||||
pub value: Option<i32>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub unit: Option<String>
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ extern crate diesel_migrations;
|
||||
|
||||
use std::env;
|
||||
|
||||
use actix_cors::Cors;
|
||||
use actix_web::{HttpServer, App};
|
||||
|
||||
use dotenv::dotenv;
|
||||
@@ -22,9 +23,15 @@ async fn main() -> std::io::Result<()> {
|
||||
let port = env::var("SERVICE_PORT").unwrap_or("5000".to_string());
|
||||
|
||||
match HttpServer::new(|| {
|
||||
let cors = Cors::default()
|
||||
.allow_any_origin()
|
||||
.allow_any_method()
|
||||
.allow_any_header()
|
||||
.max_age(3600);
|
||||
App::new()
|
||||
.configure(db::messages::init_routes)
|
||||
.configure(db::spells::init_routes)
|
||||
.wrap(cors)
|
||||
})
|
||||
.bind(format!("{}:{}", host, port)) {
|
||||
Ok(b) => {
|
||||
|
||||
5
ui/.env.TEMPLATE
Normal file
@@ -0,0 +1,5 @@
|
||||
SERVICE_HOST=service
|
||||
SERVICE_PORT=5000
|
||||
|
||||
UI_PORT=8080
|
||||
NODE_ENV=development
|
||||
17
ui/.eslintrc.json
Executable file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"root": true,
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"plugins": [
|
||||
"@typescript-eslint/eslint-plugin"
|
||||
],
|
||||
"extends": [
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"plugin:prettier/recommended"
|
||||
],
|
||||
"rules": {
|
||||
"@typescript-eslint/interface-name-prefix": "off",
|
||||
"@typescript-eslint/explicit-function-return-type": "off",
|
||||
"@typescript-eslint/explicit-module-boundary-types": "off",
|
||||
"@typescript-eslint/no-explicit-any": "off"
|
||||
}
|
||||
}
|
||||
8
ui/.prettierrc.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"trailingComma": "none",
|
||||
"tabWidth": 2,
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"jsxSingleQuote": true,
|
||||
"printWidth": 120
|
||||
}
|
||||
39
ui/Dockerfile
Normal file
@@ -0,0 +1,39 @@
|
||||
# Base
|
||||
FROM node:18-alpine AS base
|
||||
|
||||
# Dependencies
|
||||
FROM base as deps
|
||||
RUN apk add --no-cache libc6-compat
|
||||
WORKDIR /app
|
||||
COPY package.json package-lock.json ./
|
||||
RUN npm ci
|
||||
|
||||
# Dev
|
||||
FROM base AS dev
|
||||
WORKDIR /app
|
||||
COPY --from=deps /app/node_modules ./node_modules
|
||||
COPY . .
|
||||
|
||||
# Builder
|
||||
FROM base AS builder
|
||||
WORKDIR /app
|
||||
COPY --from=deps /app/node_modules ./node_modules
|
||||
COPY . .
|
||||
RUN npm run build
|
||||
|
||||
# Runner
|
||||
FROM base AS runner
|
||||
WORKDIR /app
|
||||
RUN addgroup --system --gid 1001 nodejs && adduser --system --uid 1001 nextjs
|
||||
COPY --from=builder /app/next.config.js ./
|
||||
COPY --from=builder /app/public ./public
|
||||
COPY --from=builder /app/package.json ./package.json
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
||||
|
||||
USER nextjs
|
||||
EXPOSE 3000
|
||||
ENV PORT 3000
|
||||
ENV NEXT_TELEMETRY_DISABLED 1
|
||||
|
||||
CMD ["node", "server.js"]
|
||||
26
ui/Makefile
Normal file
@@ -0,0 +1,26 @@
|
||||
#!make
|
||||
|
||||
SHELL := /bin/bash
|
||||
|
||||
.PHONY: help build start stop lint
|
||||
|
||||
help: ## This info
|
||||
@echo
|
||||
@cat Makefile | grep -E '^[a-zA-Z\/_-]+:.*?## .*$$' | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
|
||||
@echo
|
||||
|
||||
build: ## Install the dependencies and build
|
||||
docker compose build
|
||||
|
||||
up: ## Start the dev instance
|
||||
docker compose up -d
|
||||
|
||||
down: ## Stop the dev instance
|
||||
docker compose down
|
||||
|
||||
lint: ## Run the linter
|
||||
npm run lint
|
||||
|
||||
clean: ## Remove node modules
|
||||
docker compose down && \
|
||||
docker image rm siren-ui
|
||||
26
ui/docker-compose.yml
Normal file
@@ -0,0 +1,26 @@
|
||||
version: '3'
|
||||
|
||||
name: siren
|
||||
services:
|
||||
ui:
|
||||
container_name: siren-ui
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
- NODE_ENV=${NODE_ENV:-development}
|
||||
ports:
|
||||
- ${UI_PORT:-8080}:3000
|
||||
build:
|
||||
context: ./
|
||||
target: dev
|
||||
command: "npm run dev"
|
||||
volumes:
|
||||
- ./src:/app/src
|
||||
- ./public:/app/public
|
||||
- ./styles:/app/styles
|
||||
networks:
|
||||
- siren-frontend
|
||||
restart: unless-stopped
|
||||
|
||||
networks:
|
||||
siren-frontend: {}
|
||||
5
ui/next-env.d.ts
vendored
Executable file
@@ -0,0 +1,5 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/basic-features/typescript for more information.
|
||||
22
ui/next.config.js
Executable file
@@ -0,0 +1,22 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
reactStrictMode: true,
|
||||
swcMinify: true,
|
||||
eslint: {
|
||||
ignoreDuringBuilds: true
|
||||
},
|
||||
webpackDevMiddleware: (config) => {
|
||||
config.watchOptions = {
|
||||
poll: 1000,
|
||||
aggregateTimeout: 300
|
||||
};
|
||||
return config;
|
||||
},
|
||||
publicRuntimeConfig: {
|
||||
// remove private variables from processEnv
|
||||
processEnv: Object.fromEntries(Object.entries(process.env).filter(([key]) => key.includes('NEXT_PUBLIC_')))
|
||||
},
|
||||
output: 'standalone'
|
||||
};
|
||||
|
||||
module.exports = nextConfig;
|
||||
5425
ui/package-lock.json
generated
Normal file
42
ui/package.json
Normal file
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"name": "siren-ui",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@mantine/core": "^7.1.2",
|
||||
"@mantine/hooks": "^7.1.2",
|
||||
"@mantine/modals": "^7.1.2",
|
||||
"@mantine/notifications": "^7.1.2",
|
||||
"axios": "^1.5.1",
|
||||
"next": "^13.5.4",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-icons": "^4.11.0",
|
||||
"react-leaflet": "^4.2.1",
|
||||
"recharts": "^2.8.0",
|
||||
"recoil": "^0.7.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "20.8.2",
|
||||
"@types/react": "18.2.24",
|
||||
"@types/react-dom": "18.2.8",
|
||||
"@typescript-eslint/eslint-plugin": "^6.7.4",
|
||||
"@typescript-eslint/parser": "^6.7.4",
|
||||
"autoprefixer": "^10.4.16",
|
||||
"eslint": "8.50.0",
|
||||
"eslint-config-next": "13.5.4",
|
||||
"eslint-config-prettier": "^9.0.0",
|
||||
"eslint-plugin-prettier": "^5.0.0",
|
||||
"postcss": "^8.4.31",
|
||||
"postcss-import": "^15.1.0",
|
||||
"postcss-preset-mantine": "^1.8.0",
|
||||
"prettier": "^3.0.3",
|
||||
"typescript": "5.2.2"
|
||||
}
|
||||
}
|
||||
7
ui/postcss.config.js
Normal file
@@ -0,0 +1,7 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
'postcss-preset-mantine': {},
|
||||
'postcss-import': {},
|
||||
autoprefixer: {}
|
||||
}
|
||||
};
|
||||
BIN
ui/public/favicon.ico
Executable file
|
After Width: | Height: | Size: 25 KiB |
BIN
ui/public/layers-2x.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
ui/public/layers.png
Normal file
|
After Width: | Height: | Size: 696 B |
BIN
ui/public/marker-icon-2x.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
ui/public/marker-icon.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
ui/public/marker-shadow.png
Normal file
|
After Width: | Height: | Size: 618 B |
4
ui/public/vercel.svg
Executable file
@@ -0,0 +1,4 @@
|
||||
<svg width="283" height="64" viewBox="0 0 283 64" fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M141.04 16c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.46 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zM248.72 16c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.45 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zM200.24 34c0 6 3.92 10 10 10 4.12 0 7.21-1.87 8.8-4.92l7.68 4.43c-3.18 5.3-9.14 8.49-16.48 8.49-11.05 0-19-7.2-19-18s7.96-18 19-18c7.34 0 13.29 3.19 16.48 8.49l-7.68 4.43c-1.59-3.05-4.68-4.92-8.8-4.92-6.07 0-10 4-10 10zm82.48-29v46h-9V5h9zM36.95 0L73.9 64H0L36.95 0zm92.38 5l-27.71 48L73.91 5H84.3l17.32 30 17.32-30h10.39zm58.91 12v9.69c-1-.29-2.06-.49-3.2-.49-5.81 0-10 4-10 10V51h-9V17h9v9.2c0-5.08 5.91-9.2 13.2-9.2z" fill="#000"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
25
ui/src/api/index.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import axios, { AxiosResponse } from 'axios';
|
||||
|
||||
const serviceHost = process.env.SERVICE_HOST || 'http://localhost';
|
||||
const servicePort = process.env.SERVICE_PORT || 5000;
|
||||
|
||||
export async function getRequest(endpoint: string, params: any): Promise<AxiosResponse<any, any> | undefined> {
|
||||
const response = await axios
|
||||
.get(`${serviceHost}:${servicePort}/${endpoint}`, { params })
|
||||
.catch((error) => console.error(error));
|
||||
return response || undefined;
|
||||
}
|
||||
|
||||
export async function postRequest(endpoint: string, body: any): Promise<AxiosResponse<any, any> | undefined> {
|
||||
const response = await axios
|
||||
.post(`${serviceHost}:${servicePort}/${endpoint}`, { body })
|
||||
.catch((error) => console.error(error));
|
||||
return response || undefined;
|
||||
}
|
||||
|
||||
export interface Metadata {
|
||||
limit: number;
|
||||
page: number;
|
||||
pages: number;
|
||||
total: number;
|
||||
}
|
||||
37
ui/src/api/spells.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { getRequest } from '.';
|
||||
import { GetSpellsResponse } from './spells.types';
|
||||
|
||||
interface GetSpellsParams {
|
||||
name?: string;
|
||||
schools?: string[];
|
||||
levels?: number[];
|
||||
ritual?: boolean;
|
||||
concentration?: boolean;
|
||||
classes?: string[];
|
||||
damage_inflict?: string[];
|
||||
damage_resist?: string[];
|
||||
conditions?: string[];
|
||||
saving_throw?: string[];
|
||||
attack_type?: string[];
|
||||
limit?: number;
|
||||
page?: number;
|
||||
}
|
||||
|
||||
export async function getSpells(params?: GetSpellsParams): Promise<GetSpellsResponse> {
|
||||
const response = await getRequest('spells', {
|
||||
name: params?.name,
|
||||
schools: params?.schools?.join(','),
|
||||
levels: params?.levels?.join(','),
|
||||
ritual: params?.ritual,
|
||||
concentration: params?.concentration,
|
||||
classes: params?.classes?.join(','),
|
||||
damage_inflict: params?.damage_inflict?.join(','),
|
||||
damage_resist: params?.damage_resist?.join(','),
|
||||
conditions: params?.conditions?.join(','),
|
||||
saving_throw: params?.saving_throw?.join(','),
|
||||
attack_type: params?.attack_type?.join(','),
|
||||
limit: params?.limit,
|
||||
page: params?.page
|
||||
});
|
||||
return response?.data || { data: [] };
|
||||
}
|
||||
82
ui/src/api/spells.types.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
import { Metadata } from '.';
|
||||
|
||||
export interface Spell {
|
||||
id: string;
|
||||
name: string;
|
||||
school: string;
|
||||
level: number;
|
||||
ritual: boolean;
|
||||
casting_time: CastingTime;
|
||||
saving_throw?: string[];
|
||||
attack_type?: string;
|
||||
damage_inflict?: string[];
|
||||
damage_resist?: string[];
|
||||
conditions?: string[];
|
||||
range: Range;
|
||||
area?: Area;
|
||||
components: Components;
|
||||
durations: Duration[];
|
||||
classes: string[];
|
||||
sources: Source[];
|
||||
tags?: string[];
|
||||
description?: Description;
|
||||
}
|
||||
|
||||
export interface CastingTime {
|
||||
value: number;
|
||||
unit: string;
|
||||
}
|
||||
|
||||
export interface Range {
|
||||
type: string;
|
||||
value?: number;
|
||||
unit?: string;
|
||||
}
|
||||
|
||||
export interface Area {
|
||||
type: string;
|
||||
value?: number;
|
||||
unit?: string;
|
||||
}
|
||||
|
||||
export interface Components {
|
||||
verbal: boolean;
|
||||
somatic: boolean;
|
||||
material: boolean;
|
||||
materials_needed?: string;
|
||||
materials_cost?: number;
|
||||
materials_consumed?: boolean;
|
||||
}
|
||||
|
||||
export interface Duration {
|
||||
type: string;
|
||||
value?: number;
|
||||
unit?: string;
|
||||
// concentration: boolean;
|
||||
}
|
||||
|
||||
export interface Source {
|
||||
source: string;
|
||||
page?: number;
|
||||
}
|
||||
|
||||
export interface Description {
|
||||
entries: EntryType[];
|
||||
}
|
||||
|
||||
type EntryType = string | Entry;
|
||||
|
||||
export interface Entry {
|
||||
type: string;
|
||||
items: string[];
|
||||
}
|
||||
|
||||
export interface GetSpellResponse {
|
||||
data: Spell;
|
||||
metadata: Metadata;
|
||||
}
|
||||
|
||||
export interface GetSpellsResponse {
|
||||
data: Spell[];
|
||||
metadata: Metadata;
|
||||
}
|
||||
5
ui/src/app/backgrounds/page.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
import React from 'react';
|
||||
|
||||
export default function Page() {
|
||||
return <></>;
|
||||
}
|
||||
0
ui/src/app/bot/page.tsx
Normal file
5
ui/src/app/classes/page.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
import React from 'react';
|
||||
|
||||
export default function Page() {
|
||||
return <></>;
|
||||
}
|
||||
5
ui/src/app/feats/page.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
import React from 'react';
|
||||
|
||||
export default function Page() {
|
||||
return <></>;
|
||||
}
|
||||
5
ui/src/app/items/page.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
import React from 'react';
|
||||
|
||||
export default function Page() {
|
||||
return <></>;
|
||||
}
|
||||
40
ui/src/app/layout.tsx
Normal file
@@ -0,0 +1,40 @@
|
||||
import React from 'react';
|
||||
import RecoilRootWrapper from '@app/recoil-root-wrapper';
|
||||
import Topbar from '@/components/Topbar';
|
||||
import { Inter } from 'next/font/google';
|
||||
import { Box, MantineProvider } from '@mantine/core';
|
||||
import { ModalsProvider } from '@mantine/modals';
|
||||
import { Notifications } from '@mantine/notifications';
|
||||
import 'styles/globals.css';
|
||||
import '@mantine/core/styles.css';
|
||||
import '@mantine/notifications/styles.css';
|
||||
|
||||
export const metadata = {
|
||||
title: 'Siren',
|
||||
description: ''
|
||||
};
|
||||
|
||||
const inter = Inter({ subsets: ['latin'] });
|
||||
|
||||
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<html lang='en' className='h-full bg-white'>
|
||||
<head>
|
||||
<title>Siren</title>
|
||||
</head>
|
||||
<body className={`${inter.className} wrapper h-full`}>
|
||||
<RecoilRootWrapper>
|
||||
<MantineProvider>
|
||||
<Notifications />
|
||||
<ModalsProvider>
|
||||
<Topbar />
|
||||
<Box p='xl' pt='sm' className='h-full'>
|
||||
{children}
|
||||
</Box>
|
||||
</ModalsProvider>
|
||||
</MantineProvider>
|
||||
</RecoilRootWrapper>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
5
ui/src/app/options/page.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
import React from 'react';
|
||||
|
||||
export default function Page() {
|
||||
return <></>;
|
||||
}
|
||||
5
ui/src/app/page.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
import React from 'react';
|
||||
|
||||
export default function Page() {
|
||||
return <></>;
|
||||
}
|
||||
5
ui/src/app/races/page.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
import React from 'react';
|
||||
|
||||
export default function Page() {
|
||||
return <></>;
|
||||
}
|
||||
8
ui/src/app/recoil-root-wrapper.tsx
Normal file
@@ -0,0 +1,8 @@
|
||||
'use client';
|
||||
|
||||
import { RecoilRoot } from 'recoil';
|
||||
import React, { ReactNode } from 'react';
|
||||
|
||||
export default function RecoilRootWrapper({ children }: { children: ReactNode }) {
|
||||
return <RecoilRoot>{children}</RecoilRoot>;
|
||||
}
|
||||
91
ui/src/app/spells/page.tsx
Normal file
@@ -0,0 +1,91 @@
|
||||
'use client';
|
||||
|
||||
import { getSpells } from '@/api/spells';
|
||||
import { Spell } from '@/api/spells.types';
|
||||
import SpellModal from '@/components/SpellModal';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import './spells.css';
|
||||
import { Box, TextInput } from '@mantine/core';
|
||||
import { AiOutlineVerticalAlignTop } from 'react-icons/ai';
|
||||
|
||||
export default function Page() {
|
||||
const [cantrips, setCantrips] = useState<Spell[]>([]);
|
||||
const [level1, setLevel1] = useState<Spell[]>([]);
|
||||
const [level2, setLevel2] = useState<Spell[]>([]);
|
||||
const [level3, setLevel3] = useState<Spell[]>([]);
|
||||
const [level4, setLevel4] = useState<Spell[]>([]);
|
||||
const [level5, setLevel5] = useState<Spell[]>([]);
|
||||
const [level6, setLevel6] = useState<Spell[]>([]);
|
||||
const [level7, setLevel7] = useState<Spell[]>([]);
|
||||
const [level8, setLevel8] = useState<Spell[]>([]);
|
||||
const [level9, setLevel9] = useState<Spell[]>([]);
|
||||
const [activeSpell, setActiveSpell] = useState<Spell | undefined>(undefined);
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [searchName, setSearchName] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
getSpells({ levels: [0] }).then((s) => setCantrips(s.data));
|
||||
getSpells({ levels: [1] }).then((s) => setLevel1(s.data));
|
||||
getSpells({ levels: [2] }).then((s) => setLevel2(s.data));
|
||||
getSpells({ levels: [3] }).then((s) => setLevel3(s.data));
|
||||
getSpells({ levels: [4] }).then((s) => setLevel4(s.data));
|
||||
getSpells({ levels: [5] }).then((s) => setLevel5(s.data));
|
||||
getSpells({ levels: [6] }).then((s) => setLevel6(s.data));
|
||||
getSpells({ levels: [7] }).then((s) => setLevel7(s.data));
|
||||
getSpells({ levels: [8] }).then((s) => setLevel8(s.data));
|
||||
getSpells({ levels: [9] }).then((s) => setLevel9(s.data));
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Box style={{ width: '60%', margin: 'auto' }}>
|
||||
<h1>Spells</h1>
|
||||
<TextInput
|
||||
label='Search by name'
|
||||
placeholder='Acid Splash...'
|
||||
onChange={(e) => setSearchName(e.target.value)}
|
||||
style={{ width: '25%' }}
|
||||
/>
|
||||
<SpellSection
|
||||
title='Cantrips'
|
||||
spells={cantrips.filter((s) => s.name.toLowerCase().includes(searchName.toLowerCase()))}
|
||||
onClick={(spell) => {
|
||||
setActiveSpell(spell);
|
||||
setIsOpen(true);
|
||||
}}
|
||||
/>
|
||||
<hr />
|
||||
{activeSpell && <SpellModal spell={activeSpell} isOpen={isOpen} onClose={() => setIsOpen(false)} />}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
function SpellSection({ title, spells, onClick }: { title: string; spells: Spell[]; onClick: (spell: Spell) => void }) {
|
||||
const isBrowser = () => typeof window !== 'undefined'; //The approach recommended by Next.js
|
||||
|
||||
function scrollToTop() {
|
||||
if (!isBrowser()) return;
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
}
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<h2>{title}</h2>
|
||||
<ul>
|
||||
{spells.map((spell) => (
|
||||
<li
|
||||
key={spell.id}
|
||||
className='link spell-item'
|
||||
style={{ width: 'fit-content' }}
|
||||
onClick={() => onClick(spell)}
|
||||
>
|
||||
{spell.name}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<div style={{ cursor: 'pointer', display: 'flex', alignItems: 'center', color: 'gray' }} onClick={scrollToTop}>
|
||||
<span style={{ paddingRight: '0.2em' }}>Back to top</span>
|
||||
<AiOutlineVerticalAlignTop />
|
||||
</div>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
7
ui/src/app/spells/spells.css
Normal file
@@ -0,0 +1,7 @@
|
||||
.spell-item {
|
||||
padding: 0.2rem;
|
||||
}
|
||||
|
||||
.spell-item:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
153
ui/src/components/SpellModal.tsx
Normal file
@@ -0,0 +1,153 @@
|
||||
'use client';
|
||||
|
||||
import { Spell } from '@/api/spells.types';
|
||||
import { levelText, rollDice } from '@/js/spells';
|
||||
import { capitalize } from '@/js/utils';
|
||||
import { Grid, Modal } from '@mantine/core';
|
||||
import { notifications } from '@mantine/notifications';
|
||||
|
||||
interface SpellModalProps {
|
||||
spell: Spell;
|
||||
isOpen: boolean;
|
||||
onClose(): void;
|
||||
}
|
||||
|
||||
export default function SpellModal({ spell, isOpen, onClose }: SpellModalProps) {
|
||||
return (
|
||||
<Modal opened={isOpen} onClose={onClose} withCloseButton={false} size={'50%'} className='modal'>
|
||||
<h1 style={{ padding: '0', margin: '0' }}>{spell.name}</h1>
|
||||
<Grid gutter={1}>
|
||||
<Grid.Col span={4} style={{ paddingBottom: '1rem' }}>
|
||||
<span style={{ fontWeight: 'bold' }}>
|
||||
{capitalize(spell.school)} {levelText(spell)}
|
||||
</span>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={8} style={{ paddingBottom: '1rem' }}>
|
||||
<div style={{ float: 'right' }}>
|
||||
<span style={{ float: 'right' }}>
|
||||
{spell.components.verbal && spell.components.somatic ? 'V, ' : 'V '}
|
||||
{spell.components.somatic && spell.components.material ? 'S, ' : 'S '}
|
||||
{spell.components.material && spell.components.materials_needed ? 'M*' : 'M'}
|
||||
</span>
|
||||
{spell.components.materials_needed && (
|
||||
<span style={{ fontSize: '0.8em', color: 'gray' }}>
|
||||
<br />*{capitalize(spell.components.materials_needed)}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={6}>
|
||||
<span style={{ fontWeight: 'bold', paddingRight: '1em' }}>Sources:</span>
|
||||
{spell.sources.map((s) => (
|
||||
<span style={{ paddingRight: '0.6em' }}>
|
||||
{s.source}
|
||||
{s.page ? `.${s.page}` : ''}
|
||||
</span>
|
||||
))}
|
||||
</Grid.Col>
|
||||
<Grid.Col span={6}>
|
||||
<span style={{ fontWeight: 'bold', marginRight: '1em' }}>Classes:</span>
|
||||
<span style={{ overflowWrap: 'break-word' }}>
|
||||
{spell.classes.map((c) => (
|
||||
<span style={{ paddingRight: '0.6em', display: 'inline-block' }} className='link'>
|
||||
{capitalize(c)}
|
||||
</span>
|
||||
))}
|
||||
</span>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={6}>
|
||||
<span style={{ fontWeight: 'bold', paddingRight: '1em' }}>Casting Time:</span>
|
||||
<span style={{ paddingRight: '0.6em' }}>
|
||||
{spell.casting_time.value} {capitalize(spell.casting_time.unit)}
|
||||
</span>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={6}>
|
||||
<span style={{ fontWeight: 'bold', paddingRight: '1em' }}>Range:</span>
|
||||
<span style={{ paddingRight: '0.6em' }}>
|
||||
{spell.range.type != 'point' && capitalize(spell.range.type)} {spell.range.value}{' '}
|
||||
{capitalize(spell.range.unit)}
|
||||
</span>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={6}>
|
||||
<span style={{ fontWeight: 'bold', paddingRight: '1em' }}>Duration:</span>
|
||||
<span style={{ paddingRight: '0.6em' }}>
|
||||
{spell.durations.map((d) => (
|
||||
<span>
|
||||
{capitalize(d.type)} {d.value} {capitalize(d.unit)}
|
||||
</span>
|
||||
))}
|
||||
</span>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={12}>
|
||||
<SpellDescription spell={spell} />
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
function SpellDescription({ spell }: { spell: Spell }) {
|
||||
function parseText(text: string) {
|
||||
const regex = /{@(.*?) (.*?)}/g;
|
||||
const matches = text.matchAll(regex);
|
||||
const result = [];
|
||||
let lastIndex = 0;
|
||||
for (const match of matches) {
|
||||
const [full, type, name] = match;
|
||||
result.push(text.slice(lastIndex, match.index));
|
||||
if (match.index !== undefined) {
|
||||
result.push(
|
||||
<span onClick={() => handleLink(type, name)} className='link'>
|
||||
{name}
|
||||
</span>
|
||||
);
|
||||
lastIndex = match.index + full.length;
|
||||
}
|
||||
}
|
||||
result.push(text.slice(lastIndex));
|
||||
return result;
|
||||
}
|
||||
|
||||
function handleLink(type: string, name: string) {
|
||||
if (type == 'spell') {
|
||||
console.log(`Link to spell: ${name}`);
|
||||
} else if (type == 'dice' || type == 'damage') {
|
||||
const rolls = rollDice(name);
|
||||
notifications.show({
|
||||
title: `Rolling ${name}`,
|
||||
message: `${rolls.join(' + ')} = ${rolls.reduce((a, b) => a + b, 0)}`,
|
||||
color: 'blue',
|
||||
autoClose: 5000,
|
||||
withCloseButton: false
|
||||
});
|
||||
} else {
|
||||
console.error(`Unknown link type: ${type}`);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{spell.description && (
|
||||
<>
|
||||
{spell.description.entries.map((e) =>
|
||||
typeof e === 'string' ? (
|
||||
<p>{parseText(e)}</p>
|
||||
) : (
|
||||
<>
|
||||
{e.type == 'list' ? (
|
||||
<ul>
|
||||
{e.items.map((text) => (
|
||||
<li>{parseText(text)}</li>
|
||||
))}
|
||||
</ul>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
57
ui/src/components/Topbar/index.tsx
Normal file
@@ -0,0 +1,57 @@
|
||||
'use client';
|
||||
|
||||
import Link from 'next/link';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import './topbar.css';
|
||||
|
||||
const headerItems = [
|
||||
{
|
||||
name: 'Races',
|
||||
link: '/races'
|
||||
},
|
||||
{
|
||||
name: 'Classes',
|
||||
link: '/classes'
|
||||
},
|
||||
{
|
||||
name: 'Feats',
|
||||
link: '/feats'
|
||||
},
|
||||
{
|
||||
name: 'Options & Features',
|
||||
link: '/options'
|
||||
},
|
||||
{
|
||||
name: 'Backgrounds',
|
||||
link: '/backgrounds'
|
||||
},
|
||||
{
|
||||
name: 'Items',
|
||||
link: '/items'
|
||||
},
|
||||
{
|
||||
name: 'Spells',
|
||||
link: '/spells'
|
||||
}
|
||||
];
|
||||
|
||||
export default function Topbar() {
|
||||
const pathName = usePathname();
|
||||
|
||||
return (
|
||||
<nav className='navbar'>
|
||||
<div className='left'>
|
||||
<Link href={'/'} className='title'>
|
||||
Siren
|
||||
</Link>
|
||||
<div className='header-items'>
|
||||
{headerItems.map((item) => (
|
||||
<Link className={`header-item ${pathName == item.link && 'active'}`} href={item.link} key={item.name}>
|
||||
{item.name}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
47
ui/src/components/Topbar/topbar.css
Normal file
@@ -0,0 +1,47 @@
|
||||
.navbar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
color: black;
|
||||
border-bottom: 1px solid #e6e6e6;
|
||||
}
|
||||
|
||||
.navbar .left {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.navbar .title {
|
||||
padding-left: 2em;
|
||||
padding-right: 2em;
|
||||
margin: auto;
|
||||
font-size: x-large;
|
||||
}
|
||||
|
||||
.navbar .left .search {
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.navbar .avatar {
|
||||
padding-right: 2em;
|
||||
margin-top: auto;
|
||||
margin-bottom: auto;
|
||||
}
|
||||
|
||||
.header-items {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.header-items .header-item {
|
||||
padding-left: 2em;
|
||||
padding-right: 2em;
|
||||
margin: auto;
|
||||
border-bottom: 2px solid transparent;
|
||||
}
|
||||
|
||||
.header-items .header-item:hover {
|
||||
border-bottom: 2px solid #e6e6e6;
|
||||
}
|
||||
|
||||
.header-items .active {
|
||||
border-bottom: 2px solid #5f5f5f;
|
||||
}
|
||||
23
ui/src/js/spells.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { Spell } from '@/api/spells.types';
|
||||
|
||||
export function levelText(spell: Spell) {
|
||||
if (spell.level === 0) {
|
||||
return 'Cantrip';
|
||||
} else {
|
||||
return `Level ${spell.level}`;
|
||||
}
|
||||
}
|
||||
|
||||
export function rollDice(dice: string): number[] {
|
||||
// eslint-disable-next-line prefer-const
|
||||
let [count, sides] = dice.split('d');
|
||||
const rolls = [];
|
||||
if (isNaN(parseInt(count))) {
|
||||
count = '1';
|
||||
}
|
||||
for (let i = 0; i < parseInt(count); i++) {
|
||||
rolls.push(Math.floor(Math.random() * parseInt(sides)) + 1);
|
||||
}
|
||||
console.log(rolls);
|
||||
return rolls;
|
||||
}
|
||||
5
ui/src/js/theme.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
'use client';
|
||||
|
||||
import { createTheme } from '@mantine/core';
|
||||
|
||||
export const theme = createTheme({});
|
||||
6
ui/src/js/utils.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export function capitalize(str: string | undefined): string {
|
||||
if (!str || str.length === 0) {
|
||||
return '';
|
||||
}
|
||||
return str.charAt(0).toUpperCase() + str.slice(1);
|
||||
}
|
||||
38
ui/styles/globals.css
Executable file
@@ -0,0 +1,38 @@
|
||||
html,
|
||||
body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
|
||||
Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.wrapper > nav {
|
||||
flex: 0 0 56px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.link {
|
||||
list-style-type: none;
|
||||
cursor: pointer;
|
||||
color: #297bff;
|
||||
}
|
||||
|
||||
.link:hover {
|
||||
color: #1c59bb;
|
||||
}
|
||||
45
ui/tsconfig.json
Executable file
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"downlevelIteration": true,
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"incremental": true,
|
||||
"plugins": [
|
||||
{
|
||||
"name": "next"
|
||||
}
|
||||
],
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"],
|
||||
"@api/*": ["src/api"],
|
||||
"@app/*": ["./src/app/*"],
|
||||
"@components/*": ["src/components/*"],
|
||||
"@lib/*": ["src/components/*"]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"next-env.d.ts",
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
".next/types/**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
||||