feat: initialize Blazor web project and update application configuration
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
package git.yawaflua.tech.command;
|
||||
|
||||
import git.yawaflua.tech.database.DatabaseManager;
|
||||
import git.yawaflua.tech.messages.Messages;
|
||||
import io.papermc.paper.command.brigadier.BasicCommand;
|
||||
import io.papermc.paper.command.brigadier.CommandSourceStack;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class PtCommand implements BasicCommand {
|
||||
|
||||
private final DatabaseManager databaseManager;
|
||||
|
||||
public PtCommand(DatabaseManager databaseManager) {
|
||||
this.databaseManager = databaseManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(@NotNull CommandSourceStack stack, @NotNull String[] args) {
|
||||
CommandSender sender = stack.getSender();
|
||||
if (!(sender instanceof Player player)) {
|
||||
sender.sendMessage(Messages.parse(Messages.ONLY_PLAYERS));
|
||||
return;
|
||||
}
|
||||
|
||||
System.out.println(String.join(",", args));
|
||||
if (args.length == 3 && args[0].equalsIgnoreCase("web") && args[1].equalsIgnoreCase("auth")) {
|
||||
String code = args[2];
|
||||
System.out.println(code);
|
||||
boolean success = databaseManager.resolveWebAuth(code, player.getUniqueId());
|
||||
System.out.println(success);
|
||||
if (success) {
|
||||
player.sendMessage(Messages.parse(Messages.WEB_AUTH_SUCCESS));
|
||||
} else {
|
||||
player.sendMessage(Messages.parse(Messages.WEB_AUTH_FAILED));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
player.sendMessage(Messages.parse(Messages.WEB_AUTH_USAGE));
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,8 @@ import com.mongodb.client.MongoCollection;
|
||||
import com.mongodb.client.MongoDatabase;
|
||||
import com.mongodb.client.model.Filters;
|
||||
import com.mongodb.client.model.ReplaceOptions;
|
||||
import com.mongodb.client.model.Updates;
|
||||
import com.mongodb.client.result.UpdateResult;
|
||||
import git.yawaflua.tech.model.PlayerData;
|
||||
import org.bson.Document;
|
||||
import org.bukkit.configuration.file.FileConfiguration;
|
||||
@@ -20,6 +22,7 @@ public class DatabaseManager {
|
||||
private MongoDatabase database;
|
||||
private MongoCollection<Document> playersCollection;
|
||||
private MongoCollection<Document> reportsCollection;
|
||||
private MongoCollection<Document> webAuthCollection;
|
||||
|
||||
public DatabaseManager(Logger logger, FileConfiguration config) {
|
||||
this.logger = logger;
|
||||
@@ -30,7 +33,7 @@ public class DatabaseManager {
|
||||
String host = config.getString("database.host", "localhost");
|
||||
int port = config.getInt("database.port", 27017);
|
||||
String dbName = config.getString("database.database", "pixeltalk");
|
||||
|
||||
|
||||
String username = config.getString("database.username", "");
|
||||
String password = config.getString("database.password", "");
|
||||
|
||||
@@ -44,6 +47,7 @@ public class DatabaseManager {
|
||||
database = mongoClient.getDatabase(dbName);
|
||||
playersCollection = database.getCollection("players");
|
||||
reportsCollection = database.getCollection("reports");
|
||||
webAuthCollection = database.getCollection("auth_requests");
|
||||
|
||||
logger.info("Connected to MongoDB -> " + dbName);
|
||||
}
|
||||
@@ -69,8 +73,7 @@ public class DatabaseManager {
|
||||
doc.getInteger("age", 0),
|
||||
doc.getDouble("points"),
|
||||
doc.getBoolean("registered", false),
|
||||
doc.getLong("firstJoin")
|
||||
);
|
||||
doc.getLong("firstJoin"));
|
||||
}
|
||||
|
||||
public void savePlayer(PlayerData player) {
|
||||
@@ -86,8 +89,7 @@ public class DatabaseManager {
|
||||
playersCollection.replaceOne(
|
||||
Filters.eq("uuid", player.getUuid().toString()),
|
||||
doc,
|
||||
new ReplaceOptions().upsert(true)
|
||||
);
|
||||
new ReplaceOptions().upsert(true));
|
||||
}
|
||||
|
||||
public void logReport(UUID reporter, UUID target, String reason) {
|
||||
@@ -98,4 +100,15 @@ public class DatabaseManager {
|
||||
.append("resolved", false);
|
||||
reportsCollection.insertOne(report);
|
||||
}
|
||||
|
||||
public boolean resolveWebAuth(String code, UUID playerUuid) {
|
||||
UpdateResult result = webAuthCollection.updateOne(
|
||||
Filters.and(
|
||||
Filters.eq("code", code),
|
||||
Filters.eq("resolved", false)),
|
||||
Updates.combine(
|
||||
Updates.set("uuid", playerUuid.toString()),
|
||||
Updates.set("resolved", true)));
|
||||
return result.getModifiedCount() > 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
package git.yawaflua.tech.listener;
|
||||
|
||||
import git.yawaflua.tech.messages.Messages;
|
||||
import git.yawaflua.tech.score.ScoreManager;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.configuration.file.FileConfiguration;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Item;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.entity.EntityPickupItemEvent;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class ItemShareListener implements Listener {
|
||||
|
||||
private final ScoreManager scoreManager;
|
||||
private final Set<Material> valuableItems = new HashSet<>();
|
||||
private final double pointsPerShare;
|
||||
private final long cooldownMs;
|
||||
|
||||
// Key: "dropper_uuid:picker_uuid", Value: last reward timestamp
|
||||
private final Map<String, Long> cooldowns = new ConcurrentHashMap<>();
|
||||
|
||||
// Key: Item entity UUID -> dropper player UUID
|
||||
private final Map<UUID, UUID> droppedByMap = new ConcurrentHashMap<>();
|
||||
|
||||
public ItemShareListener(ScoreManager scoreManager, FileConfiguration config) {
|
||||
this.scoreManager = scoreManager;
|
||||
this.pointsPerShare = config.getDouble("sharing.points-per-share", 3.0);
|
||||
this.cooldownMs = config.getLong("sharing.cooldown-seconds", 300) * 1000L;
|
||||
|
||||
List<String> items = config.getStringList("sharing.valuable-items");
|
||||
for (String itemName : items) {
|
||||
try {
|
||||
valuableItems.add(Material.valueOf(itemName.toUpperCase()));
|
||||
} catch (IllegalArgumentException ignored) {
|
||||
// Invalid material name in config, skip
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Track when a player drops an item so we know who the original dropper is.
|
||||
*/
|
||||
@EventHandler
|
||||
public void onPlayerDropItem(org.bukkit.event.player.PlayerDropItemEvent event) {
|
||||
Player dropper = event.getPlayer();
|
||||
Item itemEntity = event.getItemDrop();
|
||||
Material mat = itemEntity.getItemStack().getType();
|
||||
|
||||
if (valuableItems.contains(mat)) {
|
||||
droppedByMap.put(itemEntity.getUniqueId(), dropper.getUniqueId());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* When a player picks up an item dropped by another player, reward the dropper with points.
|
||||
*/
|
||||
@EventHandler
|
||||
public void onEntityPickupItem(EntityPickupItemEvent event) {
|
||||
Entity entity = event.getEntity();
|
||||
if (!(entity instanceof Player picker)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Item itemEntity = event.getItem();
|
||||
Material mat = itemEntity.getItemStack().getType();
|
||||
|
||||
if (!valuableItems.contains(mat)) {
|
||||
return;
|
||||
}
|
||||
|
||||
UUID dropperUuid = droppedByMap.remove(itemEntity.getUniqueId());
|
||||
if (dropperUuid == null) {
|
||||
return; // Not tracked (e.g. world-generated drop)
|
||||
}
|
||||
|
||||
// Don't reward picking up your own items
|
||||
if (dropperUuid.equals(picker.getUniqueId())) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check cooldown between this pair
|
||||
String cooldownKey = dropperUuid.toString() + ":" + picker.getUniqueId().toString();
|
||||
long now = System.currentTimeMillis();
|
||||
long lastReward = cooldowns.getOrDefault(cooldownKey, 0L);
|
||||
|
||||
if (now - lastReward < cooldownMs) {
|
||||
return;
|
||||
}
|
||||
|
||||
cooldowns.put(cooldownKey, now);
|
||||
scoreManager.addPoints(dropperUuid, pointsPerShare);
|
||||
|
||||
// Notify the dropper if they're online
|
||||
Player dropper = org.bukkit.Bukkit.getPlayer(dropperUuid);
|
||||
if (dropper != null && dropper.isOnline()) {
|
||||
String msg = Messages.ITEM_SHARE_REWARD
|
||||
.replace("<amount>", String.valueOf(pointsPerShare))
|
||||
.replace("<item>", formatMaterialName(mat))
|
||||
.replace("<player>", picker.getName());
|
||||
dropper.sendMessage(Messages.parse(msg));
|
||||
}
|
||||
}
|
||||
|
||||
private String formatMaterialName(Material mat) {
|
||||
return mat.name().replace('_', ' ').toLowerCase();
|
||||
}
|
||||
}
|
||||
@@ -43,4 +43,10 @@ public class Messages {
|
||||
public static final String REPORT_COOLDOWN = PREFIX
|
||||
+ "<red>Wait <time> sec. before sending another report.</red>";
|
||||
public static final String REPORT_NOTIFICATION_OP = "<red>[REPORT]</red> <yellow><reporter></yellow> reported <yellow><target></yellow>. Reason: <gray><reason></gray>";
|
||||
|
||||
public static final String WEB_AUTH_SUCCESS = PREFIX + "<green>Website authentication successful!</green>";
|
||||
public static final String WEB_AUTH_FAILED = PREFIX + "<red>Invalid or already used authentication code.</red>";
|
||||
public static final String WEB_AUTH_USAGE = PREFIX + "<yellow>Usage: /pt web auth <code></yellow>";
|
||||
|
||||
public static final String ITEM_SHARE_REWARD = PREFIX + "<green>+<amount> points for sharing <item> with <player>!</green>";
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package git.yawaflua.tech;
|
||||
|
||||
import git.yawaflua.tech.command.PtCommand;
|
||||
import git.yawaflua.tech.command.ReportCommand;
|
||||
import git.yawaflua.tech.database.DatabaseManager;
|
||||
import git.yawaflua.tech.filter.ProfanityFilter;
|
||||
import git.yawaflua.tech.listener.ChatListener;
|
||||
import git.yawaflua.tech.listener.ItemShareListener;
|
||||
import git.yawaflua.tech.listener.PlayerJoinListener;
|
||||
import git.yawaflua.tech.listener.PlayerQuitListener;
|
||||
import git.yawaflua.tech.listener.PvPListener;
|
||||
@@ -52,9 +54,11 @@ public final class pixeltalk extends JavaPlugin {
|
||||
getServer().getPluginManager().registerEvents(new PlayerJoinListener(scoreManager, questionnaireManager, tabManager), this);
|
||||
getServer().getPluginManager().registerEvents(new PlayerQuitListener(scoreManager), this);
|
||||
getServer().getPluginManager().registerEvents(new PvPListener(scoreManager, getConfig()), this);
|
||||
getServer().getPluginManager().registerEvents(new ItemShareListener(scoreManager, getConfig()), this);
|
||||
|
||||
this.getLifecycleManager().registerEventHandler(io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents.COMMANDS, event -> {
|
||||
event.registrar().register("report", new ReportCommand(databaseManager, getConfig(), voiceIntegration));
|
||||
event.registrar().register("pt", new PtCommand(databaseManager));
|
||||
});
|
||||
|
||||
getLogger().info("PixelTalk enabled successfully!");
|
||||
|
||||
@@ -41,7 +41,8 @@ public class QuestionnaireManager {
|
||||
|
||||
public void handleAnswer(Player player, String answer, PlayerData playerData) {
|
||||
QuestionnaireState state = activeQuestionnaires.get(player.getUniqueId());
|
||||
if (state == null) return;
|
||||
if (state == null)
|
||||
return;
|
||||
|
||||
switch (state.currentState) {
|
||||
case LANGUAGE:
|
||||
@@ -61,6 +62,7 @@ public class QuestionnaireManager {
|
||||
playerData.setAge(age);
|
||||
playerData.setRegistered(true);
|
||||
activeQuestionnaires.remove(player.getUniqueId());
|
||||
playerData.setName(player.getName());
|
||||
databaseManager.savePlayer(playerData);
|
||||
player.sendMessage(Messages.parse(Messages.REGISTRATION_COMPLETE));
|
||||
} else {
|
||||
|
||||
@@ -30,7 +30,6 @@ public class VoiceIntegration {
|
||||
private final Plugin plugin;
|
||||
private final Logger logger;
|
||||
|
||||
private final Map<UUID, Integer> speakingTime = new HashMap<>();
|
||||
|
||||
public VoiceIntegration(Plugin plugin, ScoreManager scoreManager, FileConfiguration config) {
|
||||
this.plugin = plugin;
|
||||
|
||||
@@ -99,3 +99,26 @@ profanity:
|
||||
- "يخرب بيتك"
|
||||
- "كس أمك"
|
||||
- "عيري فيك"
|
||||
|
||||
# Sharing valuable resources
|
||||
sharing:
|
||||
# Points awarded for sharing valuable items with other players
|
||||
points-per-share: 3.0
|
||||
# Cooldown in seconds between share rewards (per player pair)
|
||||
cooldown-seconds: 300
|
||||
# List of valuable items (Minecraft material names)
|
||||
valuable-items:
|
||||
- DIAMOND
|
||||
- DIAMOND_BLOCK
|
||||
- EMERALD
|
||||
- EMERALD_BLOCK
|
||||
- NETHERITE_INGOT
|
||||
- NETHERITE_BLOCK
|
||||
- GOLDEN_APPLE
|
||||
- ENCHANTED_GOLDEN_APPLE
|
||||
- ELYTRA
|
||||
- TOTEM_OF_UNDYING
|
||||
- BEACON
|
||||
- NETHER_STAR
|
||||
- TRIDENT
|
||||
- SHULKER_BOX
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
bin/
|
||||
obj/
|
||||
/packages/
|
||||
riderModule.iml
|
||||
/_ReSharper.Caches/
|
||||
@@ -0,0 +1,16 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "web.PixelTalk", "web.PixelTalk\web.PixelTalk.csproj", "{DD48A3C3-8690-442D-9D44-ED1E9271A313}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{DD48A3C3-8690-442D-9D44-ED1E9271A313}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{DD48A3C3-8690-442D-9D44-ED1E9271A313}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{DD48A3C3-8690-442D-9D44-ED1E9271A313}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{DD48A3C3-8690-442D-9D44-ED1E9271A313}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -0,0 +1,2 @@
|
||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIMongoCollectionExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2026_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Ff92749f892fb46959621e0c6a2d97fc5286400_003F40_003Fdca007d1_003FIMongoCollectionExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String></wpf:ResourceDictionary>
|
||||
@@ -0,0 +1,22 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<base href="/"/>
|
||||
<link rel="stylesheet" href="@Assets["lib/bootstrap/dist/css/bootstrap.min.css"]"/>
|
||||
<link rel="stylesheet" href="@Assets["app.css"]"/>
|
||||
<link rel="stylesheet" href="@Assets["web.PixelTalk.styles.css"]"/>
|
||||
<ImportMap/>
|
||||
<link rel="icon" type="image/png" href="favicon.png"/>
|
||||
<HeadOutlet/>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<Routes/>
|
||||
<ReconnectModal/>
|
||||
<script src="@Assets["_framework/blazor.web.js"]"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,23 @@
|
||||
@inherits LayoutComponentBase
|
||||
|
||||
<div class="page">
|
||||
<div class="sidebar">
|
||||
<NavMenu/>
|
||||
</div>
|
||||
|
||||
<main>
|
||||
<div class="top-row px-4">
|
||||
<a href="https://learn.microsoft.com/aspnet/core/" target="_blank">About</a>
|
||||
</div>
|
||||
|
||||
<article class="content px-4">
|
||||
@Body
|
||||
</article>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<div id="blazor-error-ui" data-nosnippet>
|
||||
An unhandled error has occurred.
|
||||
<a href="." class="reload">Reload</a>
|
||||
<span class="dismiss">🗙</span>
|
||||
</div>
|
||||
@@ -0,0 +1,98 @@
|
||||
.page {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
main {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%);
|
||||
}
|
||||
|
||||
.top-row {
|
||||
background-color: #f7f7f7;
|
||||
border-bottom: 1px solid #d6d5d5;
|
||||
justify-content: flex-end;
|
||||
height: 3.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.top-row ::deep a, .top-row ::deep .btn-link {
|
||||
white-space: nowrap;
|
||||
margin-left: 1.5rem;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.top-row ::deep a:hover, .top-row ::deep .btn-link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.top-row ::deep a:first-child {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
@media (max-width: 640.98px) {
|
||||
.top-row {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.top-row ::deep a, .top-row ::deep .btn-link {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 641px) {
|
||||
.page {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
width: 250px;
|
||||
height: 100vh;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.top-row {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.top-row.auth ::deep a:first-child {
|
||||
flex: 1;
|
||||
text-align: right;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.top-row, article {
|
||||
padding-left: 2rem !important;
|
||||
padding-right: 1.5rem !important;
|
||||
}
|
||||
}
|
||||
|
||||
#blazor-error-ui {
|
||||
color-scheme: light only;
|
||||
background: lightyellow;
|
||||
bottom: 0;
|
||||
box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
|
||||
box-sizing: border-box;
|
||||
display: none;
|
||||
left: 0;
|
||||
padding: 0.6rem 1.25rem 0.7rem 1.25rem;
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
#blazor-error-ui .dismiss {
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
right: 0.75rem;
|
||||
top: 0.5rem;
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<div class="top-row ps-3 navbar navbar-dark">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="">web.PixelTalk</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input type="checkbox" title="Navigation menu" class="navbar-toggler"/>
|
||||
|
||||
<div class="nav-scrollable" onclick="document.querySelector('.navbar-toggler').click()">
|
||||
<nav class="nav flex-column">
|
||||
<div class="nav-item px-3">
|
||||
<NavLink class="nav-link" href="" Match="NavLinkMatch.All">
|
||||
<span class="bi bi-house-door-fill-nav-menu" aria-hidden="true"></span> Home
|
||||
</NavLink>
|
||||
</div>
|
||||
|
||||
<AuthorizeView Roles="op">
|
||||
<Authorized>
|
||||
<div class="nav-item px-3">
|
||||
<NavLink class="nav-link" href="admin/reports">
|
||||
<span class="bi bi-exclamation-triangle-fill" aria-hidden="true"></span> Manage Reports
|
||||
</NavLink>
|
||||
</div>
|
||||
|
||||
<div class="nav-item px-3">
|
||||
<NavLink class="nav-link" href="admin/store">
|
||||
<span class="bi bi-shop" aria-hidden="true"></span> Manage Store
|
||||
</NavLink>
|
||||
</div>
|
||||
|
||||
<div class="nav-item px-3">
|
||||
<NavLink class="nav-link" href="admin/purchases">
|
||||
<span class="bi bi-receipt" aria-hidden="true"></span> Purchase Log
|
||||
</NavLink>
|
||||
</div>
|
||||
</Authorized>
|
||||
</AuthorizeView>
|
||||
|
||||
</nav>
|
||||
</div>
|
||||
@@ -0,0 +1,105 @@
|
||||
.navbar-toggler {
|
||||
appearance: none;
|
||||
cursor: pointer;
|
||||
width: 3.5rem;
|
||||
height: 2.5rem;
|
||||
color: white;
|
||||
position: absolute;
|
||||
top: 0.5rem;
|
||||
right: 1rem;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e") no-repeat center/1.75rem rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.navbar-toggler:checked {
|
||||
background-color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
.top-row {
|
||||
min-height: 3.5rem;
|
||||
background-color: rgba(0,0,0,0.4);
|
||||
}
|
||||
|
||||
.navbar-brand {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.bi {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
margin-right: 0.75rem;
|
||||
top: -1px;
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
.bi-house-door-fill-nav-menu {
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-house-door-fill' viewBox='0 0 16 16'%3E%3Cpath d='M6.5 14.5v-3.505c0-.245.25-.495.5-.495h2c.25 0 .5.25.5.5v3.5a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5v-7a.5.5 0 0 0-.146-.354L13 5.793V2.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1.293L8.354 1.146a.5.5 0 0 0-.708 0l-6 6A.5.5 0 0 0 1.5 7.5v7a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5Z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
.bi-plus-square-fill-nav-menu {
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-plus-square-fill' viewBox='0 0 16 16'%3E%3Cpath d='M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2zm6.5 4.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3a.5.5 0 0 1 1 0z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
.bi-list-nested-nav-menu {
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-list-nested' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M4.5 11.5A.5.5 0 0 1 5 11h10a.5.5 0 0 1 0 1H5a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 3 7h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 1 3h10a.5.5 0 0 1 0 1H1a.5.5 0 0 1-.5-.5z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
font-size: 0.9rem;
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.nav-item:first-of-type {
|
||||
padding-top: 1rem;
|
||||
}
|
||||
|
||||
.nav-item:last-of-type {
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
|
||||
.nav-item ::deep .nav-link {
|
||||
color: #d7d7d7;
|
||||
background: none;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
height: 3rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
line-height: 3rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.nav-item ::deep a.active {
|
||||
background-color: rgba(255,255,255,0.37);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.nav-item ::deep .nav-link:hover {
|
||||
background-color: rgba(255,255,255,0.1);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.nav-scrollable {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.navbar-toggler:checked ~ .nav-scrollable {
|
||||
display: block;
|
||||
}
|
||||
|
||||
@media (min-width: 641px) {
|
||||
.navbar-toggler {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.nav-scrollable {
|
||||
/* Never collapse the sidebar for wide screens */
|
||||
display: block;
|
||||
|
||||
/* Allow sidebar to scroll for tall menus */
|
||||
height: calc(100vh - 3.5rem);
|
||||
overflow-y: auto;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<script type="module" src="@Assets["Components/Layout/ReconnectModal.razor.js"]"></script>
|
||||
|
||||
<dialog id="components-reconnect-modal" data-nosnippet>
|
||||
<div class="components-reconnect-container">
|
||||
<div class="components-rejoining-animation" aria-hidden="true">
|
||||
<div></div>
|
||||
<div></div>
|
||||
</div>
|
||||
<p class="components-reconnect-first-attempt-visible">
|
||||
Rejoining the server...
|
||||
</p>
|
||||
<p class="components-reconnect-repeated-attempt-visible">
|
||||
Rejoin failed... trying again in <span id="components-seconds-to-next-attempt"></span> seconds.
|
||||
</p>
|
||||
<p class="components-reconnect-failed-visible">
|
||||
Failed to rejoin.<br/>Please retry or reload the page.
|
||||
</p>
|
||||
<button id="components-reconnect-button" class="components-reconnect-failed-visible">
|
||||
Retry
|
||||
</button>
|
||||
<p class="components-pause-visible">
|
||||
The session has been paused by the server.
|
||||
</p>
|
||||
<p class="components-resume-failed-visible">
|
||||
Failed to resume the session.<br/>Please retry or reload the page.
|
||||
</p>
|
||||
<button id="components-resume-button" class="components-pause-visible components-resume-failed-visible">
|
||||
Resume
|
||||
</button>
|
||||
</div>
|
||||
</dialog>
|
||||
@@ -0,0 +1,157 @@
|
||||
.components-reconnect-first-attempt-visible,
|
||||
.components-reconnect-repeated-attempt-visible,
|
||||
.components-reconnect-failed-visible,
|
||||
.components-pause-visible,
|
||||
.components-resume-failed-visible,
|
||||
.components-rejoining-animation {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#components-reconnect-modal.components-reconnect-show .components-reconnect-first-attempt-visible,
|
||||
#components-reconnect-modal.components-reconnect-show .components-rejoining-animation,
|
||||
#components-reconnect-modal.components-reconnect-paused .components-pause-visible,
|
||||
#components-reconnect-modal.components-reconnect-resume-failed .components-resume-failed-visible,
|
||||
#components-reconnect-modal.components-reconnect-retrying,
|
||||
#components-reconnect-modal.components-reconnect-retrying .components-reconnect-repeated-attempt-visible,
|
||||
#components-reconnect-modal.components-reconnect-retrying .components-rejoining-animation,
|
||||
#components-reconnect-modal.components-reconnect-failed,
|
||||
#components-reconnect-modal.components-reconnect-failed .components-reconnect-failed-visible {
|
||||
display: block;
|
||||
}
|
||||
|
||||
|
||||
#components-reconnect-modal {
|
||||
background-color: white;
|
||||
width: 20rem;
|
||||
margin: 20vh auto;
|
||||
padding: 2rem;
|
||||
border: 0;
|
||||
border-radius: 0.5rem;
|
||||
box-shadow: 0 3px 6px 2px rgba(0, 0, 0, 0.3);
|
||||
opacity: 0;
|
||||
transition: display 0.5s allow-discrete, overlay 0.5s allow-discrete;
|
||||
animation: components-reconnect-modal-fadeOutOpacity 0.5s both;
|
||||
&[open]
|
||||
|
||||
{
|
||||
animation: components-reconnect-modal-slideUp 1.5s cubic-bezier(.05, .89, .25, 1.02) 0.3s, components-reconnect-modal-fadeInOpacity 0.5s ease-in-out 0.3s;
|
||||
animation-fill-mode: both;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#components-reconnect-modal::backdrop {
|
||||
background-color: rgba(0, 0, 0, 0.4);
|
||||
animation: components-reconnect-modal-fadeInOpacity 0.5s ease-in-out;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
@keyframes components-reconnect-modal-slideUp {
|
||||
0% {
|
||||
transform: translateY(30px) scale(0.95);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes components-reconnect-modal-fadeInOpacity {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes components-reconnect-modal-fadeOutOpacity {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.components-reconnect-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
#components-reconnect-modal p {
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#components-reconnect-modal button {
|
||||
border: 0;
|
||||
background-color: #6b9ed2;
|
||||
color: white;
|
||||
padding: 4px 24px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
#components-reconnect-modal button:hover {
|
||||
background-color: #3b6ea2;
|
||||
}
|
||||
|
||||
#components-reconnect-modal button:active {
|
||||
background-color: #6b9ed2;
|
||||
}
|
||||
|
||||
.components-rejoining-animation {
|
||||
position: relative;
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
}
|
||||
|
||||
.components-rejoining-animation div {
|
||||
position: absolute;
|
||||
border: 3px solid #0087ff;
|
||||
opacity: 1;
|
||||
border-radius: 50%;
|
||||
animation: components-rejoining-animation 1.5s cubic-bezier(0, 0.2, 0.8, 1) infinite;
|
||||
}
|
||||
|
||||
.components-rejoining-animation div:nth-child(2) {
|
||||
animation-delay: -0.5s;
|
||||
}
|
||||
|
||||
@keyframes components-rejoining-animation {
|
||||
0% {
|
||||
top: 40px;
|
||||
left: 40px;
|
||||
width: 0;
|
||||
height: 0;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
4.9% {
|
||||
top: 40px;
|
||||
left: 40px;
|
||||
width: 0;
|
||||
height: 0;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
5% {
|
||||
top: 40px;
|
||||
left: 40px;
|
||||
width: 0;
|
||||
height: 0;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
100% {
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
// Set up event handlers
|
||||
const reconnectModal = document.getElementById("components-reconnect-modal");
|
||||
reconnectModal.addEventListener("components-reconnect-state-changed", handleReconnectStateChanged);
|
||||
|
||||
const retryButton = document.getElementById("components-reconnect-button");
|
||||
retryButton.addEventListener("click", retry);
|
||||
|
||||
const resumeButton = document.getElementById("components-resume-button");
|
||||
resumeButton.addEventListener("click", resume);
|
||||
|
||||
function handleReconnectStateChanged(event) {
|
||||
if (event.detail.state === "show") {
|
||||
reconnectModal.showModal();
|
||||
} else if (event.detail.state === "hide") {
|
||||
reconnectModal.close();
|
||||
} else if (event.detail.state === "failed") {
|
||||
document.addEventListener("visibilitychange", retryWhenDocumentBecomesVisible);
|
||||
} else if (event.detail.state === "rejected") {
|
||||
location.reload();
|
||||
}
|
||||
}
|
||||
|
||||
async function retry() {
|
||||
document.removeEventListener("visibilitychange", retryWhenDocumentBecomesVisible);
|
||||
|
||||
try {
|
||||
// Reconnect will asynchronously return:
|
||||
// - true to mean success
|
||||
// - false to mean we reached the server, but it rejected the connection (e.g., unknown circuit ID)
|
||||
// - exception to mean we didn't reach the server (this can be sync or async)
|
||||
const successful = await Blazor.reconnect();
|
||||
if (!successful) {
|
||||
// We have been able to reach the server, but the circuit is no longer available.
|
||||
// We'll reload the page so the user can continue using the app as quickly as possible.
|
||||
const resumeSuccessful = await Blazor.resumeCircuit();
|
||||
if (!resumeSuccessful) {
|
||||
location.reload();
|
||||
} else {
|
||||
reconnectModal.close();
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
// We got an exception, server is currently unavailable
|
||||
document.addEventListener("visibilitychange", retryWhenDocumentBecomesVisible);
|
||||
}
|
||||
}
|
||||
|
||||
async function resume() {
|
||||
try {
|
||||
const successful = await Blazor.resumeCircuit();
|
||||
if (!successful) {
|
||||
location.reload();
|
||||
}
|
||||
} catch {
|
||||
reconnectModal.classList.replace("components-reconnect-paused", "components-reconnect-resume-failed");
|
||||
}
|
||||
}
|
||||
|
||||
async function retryWhenDocumentBecomesVisible() {
|
||||
if (document.visibilityState === "visible") {
|
||||
await retry();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
@page "/admin/purchases"
|
||||
@attribute [Authorize(Roles = "op")]
|
||||
@using Microsoft.AspNetCore.Authorization
|
||||
@using MongoDB.Driver
|
||||
@using web.PixelTalk.Models
|
||||
@using web.PixelTalk.Services
|
||||
@inject MongoService MongoService
|
||||
|
||||
<PageTitle>Manage Purchases</PageTitle>
|
||||
|
||||
<div class="container mt-4">
|
||||
<h2>Player Purchase History</h2>
|
||||
<div class="table-responsive mt-3 shadow-sm rounded">
|
||||
<table class="table table-hover table-striped align-middle mb-0">
|
||||
<thead class="table-dark">
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th>Player UUID</th>
|
||||
<th>Item Redeemed</th>
|
||||
<th>Points Cost</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var p in logs)
|
||||
{
|
||||
<tr>
|
||||
<td class="text-muted small">@(DateTimeOffset.FromUnixTimeSeconds(p.Timestamp).ToString("g"))</td>
|
||||
<td><code>@p.Uuid</code></td>
|
||||
<td class="fw-semibold text-primary">@p.ItemName</td>
|
||||
<td><span class="badge bg-danger rounded-pill">-@p.Cost.ToString("N0")</span></td>
|
||||
</tr>
|
||||
}
|
||||
|
||||
@if (!logs.Any())
|
||||
{
|
||||
<tr>
|
||||
<td colspan="4" class="text-center py-5 text-muted fst-italic">No purchase records found.</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@if (totalPages > 1)
|
||||
{
|
||||
<div class="d-flex justify-content-between align-items-center mt-4">
|
||||
<button class="btn btn-outline-primary px-4" @onclick="PreviousPage" disabled="@(currentPage == 1)">
|
||||
« Previous
|
||||
</button>
|
||||
<span class="text-muted fw-bold">Page <span class="text-dark">@currentPage</span> of <span class="text-dark">@totalPages</span></span>
|
||||
<button class="btn btn-outline-primary px-4" @onclick="NextPage" disabled="@(currentPage == totalPages)">
|
||||
Next »
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
@code {
|
||||
private List<PurchaseLog> logs = new();
|
||||
private int currentPage = 1;
|
||||
private int pageSize = 15;
|
||||
private long totalItems;
|
||||
private int totalPages => (int)Math.Max(1, Math.Ceiling((double)totalItems / pageSize));
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await LoadData();
|
||||
}
|
||||
|
||||
private async Task LoadData()
|
||||
{
|
||||
var filter = Builders<PurchaseLog>.Filter.Empty;
|
||||
totalItems = await MongoService.PurchaseLogs.CountDocumentsAsync(filter);
|
||||
|
||||
logs = await MongoService.PurchaseLogs.Find(filter)
|
||||
.SortByDescending(r => r.Timestamp)
|
||||
.Skip((currentPage - 1) * pageSize)
|
||||
.Limit(pageSize)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
private async Task NextPage()
|
||||
{
|
||||
if (currentPage < totalPages)
|
||||
{
|
||||
currentPage++;
|
||||
await LoadData();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task PreviousPage()
|
||||
{
|
||||
if (currentPage > 1)
|
||||
{
|
||||
currentPage--;
|
||||
await LoadData();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
@page "/admin/reports"
|
||||
@attribute [Authorize(Roles = "op")]
|
||||
@using Microsoft.AspNetCore.Authorization
|
||||
@using MongoDB.Driver
|
||||
@using web.PixelTalk.Models
|
||||
@using web.PixelTalk.Services
|
||||
@inject MongoService MongoService
|
||||
|
||||
<PageTitle>Manage Reports</PageTitle>
|
||||
|
||||
<div class="container mt-4">
|
||||
<h2>Player Reports</h2>
|
||||
<div class="table-responsive mt-3">
|
||||
<table class="table table-hover align-middle">
|
||||
<thead class="table-dark">
|
||||
<tr>
|
||||
<th>Reporter</th>
|
||||
<th>Target</th>
|
||||
<th>Reason</th>
|
||||
<th>Date</th>
|
||||
<th>Status</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var report in reports)
|
||||
{
|
||||
<tr>
|
||||
<td><code>@report.ReporterUuid</code></td>
|
||||
<td><code>@report.TargetUuid</code></td>
|
||||
<td>@report.Reason</td>
|
||||
<td>@(DateTimeOffset.FromUnixTimeMilliseconds(report.Timestamp).ToString("g"))</td>
|
||||
<td>
|
||||
@if (report.Resolved)
|
||||
{
|
||||
<span class="badge bg-success">Resolved</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="badge bg-warning text-dark">Pending</span>
|
||||
}
|
||||
</td>
|
||||
<td>
|
||||
@if (!report.Resolved)
|
||||
{
|
||||
<button class="btn btn-sm btn-primary" @onclick="() => RunSolve(report)">Resolve</button>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mt-3">
|
||||
<button class="btn btn-outline-secondary" @onclick="PreviousPage" disabled="@(currentPage == 1)">Previous</button>
|
||||
<span>Page @currentPage of @totalPages</span>
|
||||
<button class="btn btn-outline-secondary" @onclick="NextPage" disabled="@(currentPage == totalPages)">Next</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
private List<Report> reports = new();
|
||||
private int currentPage = 1;
|
||||
private int pageSize = 10;
|
||||
private long totalItems;
|
||||
private int totalPages => (int)Math.Max(1, Math.Ceiling((double)totalItems / pageSize));
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await LoadData();
|
||||
}
|
||||
|
||||
private async Task LoadData()
|
||||
{
|
||||
var filter = Builders<Report>.Filter.Empty;
|
||||
totalItems = await MongoService.Reports.CountDocumentsAsync(filter);
|
||||
|
||||
reports = await MongoService.Reports.Find(filter)
|
||||
.SortByDescending(r => r.Timestamp)
|
||||
.Skip((currentPage - 1) * pageSize)
|
||||
.Limit(pageSize)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
private async Task RunSolve(Report report)
|
||||
{
|
||||
var filter = Builders<Report>.Filter.Eq(r => r.Id, report.Id);
|
||||
var update = Builders<Report>.Update.Set(r => r.Resolved, true);
|
||||
await MongoService.Reports.UpdateOneAsync(filter, update);
|
||||
report.Resolved = true;
|
||||
}
|
||||
|
||||
private async Task NextPage()
|
||||
{
|
||||
if (currentPage < totalPages)
|
||||
{
|
||||
currentPage++;
|
||||
await LoadData();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task PreviousPage()
|
||||
{
|
||||
if (currentPage > 1)
|
||||
{
|
||||
currentPage--;
|
||||
await LoadData();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
@page "/admin/store"
|
||||
@attribute [Authorize(Roles = "op")]
|
||||
@using Microsoft.AspNetCore.Authorization
|
||||
@using MongoDB.Driver
|
||||
@using web.PixelTalk.Models
|
||||
@using web.PixelTalk.Services
|
||||
@inject MongoService MongoService
|
||||
|
||||
<PageTitle>Manage Store</PageTitle>
|
||||
|
||||
<div class="container mt-4">
|
||||
<h2>Store Inventory</h2>
|
||||
|
||||
<div class="row mt-4">
|
||||
<!-- New Item Form -->
|
||||
<div class="col-md-5 mb-4">
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-header bg-primary text-white">Add New Item</div>
|
||||
<div class="card-body">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Name</label>
|
||||
<input type="text" class="form-control" @bind="newItem.Name" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Description</label>
|
||||
<textarea class="form-control" rows="3" @bind="newItem.Description"></textarea>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Cost (Points)</label>
|
||||
<input type="number" class="form-control" @bind="newItem.Cost" />
|
||||
</div>
|
||||
<button class="btn btn-success w-100" @onclick="AddItem">Add to Store</button>
|
||||
@if (errorMessage != null)
|
||||
{
|
||||
<div class="alert alert-danger mt-3">@errorMessage</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Current Items List -->
|
||||
<div class="col-md-7">
|
||||
<div class="list-group shadow-sm">
|
||||
@foreach (var item in items)
|
||||
{
|
||||
<div class="list-group-item list-group-item-action d-flex justify-content-between align-items-start py-3">
|
||||
<div class="ms-2 me-auto">
|
||||
<div class="fw-bold">@item.Name</div>
|
||||
<small class="text-muted d-block mt-1">@item.Description</small>
|
||||
<span class="badge bg-secondary mt-2">@item.Cost.ToString("N0") pts</span>
|
||||
</div>
|
||||
<button class="btn btn-sm btn-outline-danger ms-3 mt-1" @onclick="() => DeleteItem(item)">
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (!items.Any())
|
||||
{
|
||||
<div class="list-group-item text-center py-4 text-muted">
|
||||
No items found in the store. Add some using the form!
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
private List<StoreItem> items = new();
|
||||
private StoreItem newItem = new();
|
||||
private string? errorMessage;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await LoadData();
|
||||
}
|
||||
|
||||
private async Task LoadData()
|
||||
{
|
||||
items = await MongoService.StoreItems.Find(_ => true).ToListAsync();
|
||||
}
|
||||
|
||||
private async Task AddItem()
|
||||
{
|
||||
errorMessage = null;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(newItem.Name) || newItem.Cost < 0)
|
||||
{
|
||||
errorMessage = "Please verify item details. Name is required and Cost cannot be negative.";
|
||||
return;
|
||||
}
|
||||
|
||||
await MongoService.StoreItems.InsertOneAsync(newItem);
|
||||
items.Add(newItem);
|
||||
|
||||
newItem = new StoreItem();
|
||||
}
|
||||
|
||||
private async Task DeleteItem(StoreItem item)
|
||||
{
|
||||
var filter = Builders<StoreItem>.Filter.Eq(i => i.Id, item.Id);
|
||||
await MongoService.StoreItems.DeleteOneAsync(filter);
|
||||
items.Remove(item);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
@page "/auth"
|
||||
@inject NavigationManager NavManager
|
||||
@inject web.PixelTalk.Services.MongoService MongoService
|
||||
@using System.Timers
|
||||
@using MongoDB.Driver
|
||||
@implements IDisposable
|
||||
|
||||
<PageTitle>PixelTalk Authentication</PageTitle>
|
||||
|
||||
<div class="row min-vh-100 justify-content-center align-items-center">
|
||||
<div class="col-md-6 text-center">
|
||||
@if (isLoading)
|
||||
{
|
||||
<div class="spinner-border text-primary" role="status">
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
<p class="mt-3">Generating authorization code...</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="card shadow-lg p-5">
|
||||
<h2 class="mb-4">Link Your Minecraft Account</h2>
|
||||
<p>Run the following command in-game on the server to log in:</p>
|
||||
<h3 class="font-monospace user-select-all bg-light p-3 border rounded text-primary">/pt web auth @authCode</h3>
|
||||
<p class="text-muted mt-3">Waiting for authentication... This page will redirect automatically.</p>
|
||||
<button class="btn btn-outline-secondary mt-3" @onclick="CheckAuthStatus">Check Now</button>
|
||||
<div class="spinner-grow text-primary mt-2" role="status">
|
||||
<span class="visually-hidden">Refreshing...</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
private string authCode = string.Empty;
|
||||
private bool isLoading = true;
|
||||
private Timer? timer;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
authCode = GenerateCode();
|
||||
|
||||
var req = new web.PixelTalk.Models.AuthRequest
|
||||
{
|
||||
Code = authCode,
|
||||
Resolved = false,
|
||||
Timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds()
|
||||
};
|
||||
await MongoService.AuthRequests.InsertOneAsync(req);
|
||||
|
||||
isLoading = false;
|
||||
|
||||
timer = new Timer(1500);
|
||||
timer.Elapsed += async (sender, e) => await CheckAuthStatus();
|
||||
timer.Start();
|
||||
}
|
||||
|
||||
private async Task CheckAuthStatus()
|
||||
{
|
||||
var req = await MongoService.AuthRequests.Find(r => r.Code == authCode).FirstOrDefaultAsync();
|
||||
Console.WriteLine(req == null);
|
||||
if (req != null && req.Resolved)
|
||||
{
|
||||
timer?.Stop();
|
||||
NavManager.NavigateTo($"/login_callback?code={authCode}", true);
|
||||
}
|
||||
}
|
||||
|
||||
private string GenerateCode()
|
||||
{
|
||||
var random = new Random();
|
||||
return random.Next(100000, 999999).ToString();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
timer?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
@page "/counter"
|
||||
@rendermode InteractiveServer
|
||||
|
||||
<PageTitle>Counter</PageTitle>
|
||||
|
||||
<h1>Counter</h1>
|
||||
|
||||
<p role="status">Current count: @currentCount</p>
|
||||
|
||||
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
|
||||
|
||||
@code {
|
||||
private int currentCount = 0;
|
||||
|
||||
private void IncrementCount()
|
||||
{
|
||||
currentCount++;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
@page "/Error"
|
||||
@using System.Diagnostics
|
||||
|
||||
<PageTitle>Error</PageTitle>
|
||||
|
||||
<h1 class="text-danger">Error.</h1>
|
||||
<h2 class="text-danger">An error occurred while processing your request.</h2>
|
||||
|
||||
@if (ShowRequestId)
|
||||
{
|
||||
<p>
|
||||
<strong>Request ID:</strong> <code>@RequestId</code>
|
||||
</p>
|
||||
}
|
||||
|
||||
<h3>Development Mode</h3>
|
||||
<p>
|
||||
Swapping to <strong>Development</strong> environment will display more detailed information about the error that occurred.
|
||||
</p>
|
||||
<p>
|
||||
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
|
||||
It can result in displaying sensitive information from exceptions to end users.
|
||||
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
|
||||
and restarting the app.
|
||||
</p>
|
||||
|
||||
@code{
|
||||
[CascadingParameter] private HttpContext? HttpContext { get; set; }
|
||||
|
||||
private string? RequestId { get; set; }
|
||||
private bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
|
||||
|
||||
protected override void OnInitialized() =>
|
||||
RequestId = Activity.Current?.Id ?? HttpContext?.TraceIdentifier;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
@page "/"
|
||||
@using Microsoft.AspNetCore.Components.Authorization
|
||||
@using web.PixelTalk.Models
|
||||
@using web.PixelTalk.Services
|
||||
@using MongoDB.Driver
|
||||
@inject AuthenticationStateProvider AuthStateProvider
|
||||
@inject MongoService MongoService
|
||||
|
||||
<PageTitle>PixelTalk Dashboard</PageTitle>
|
||||
|
||||
<AuthorizeView>
|
||||
<Authorized>
|
||||
<div class="container mt-4">
|
||||
<h1 class="mb-4">Welcome, @player?.Nickname!</h1>
|
||||
<div class="row">
|
||||
<div class="col-md-4 mb-3">
|
||||
<div class="card shadow-sm text-center">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title text-muted">Current Points</h5>
|
||||
<h2 class="display-4 text-success">@player?.Points.ToString("N0")</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3 class="mt-5 mb-3">Rewards Store</h3>
|
||||
@if (message != null)
|
||||
{
|
||||
<div class="alert alert-info" role="alert">@message</div>
|
||||
}
|
||||
<div class="row">
|
||||
@foreach (var item in storeItems)
|
||||
{
|
||||
<div class="col-md-4 mb-4">
|
||||
<div class="card shadow-sm h-100">
|
||||
<div class="card-body d-flex flex-column">
|
||||
<h5 class="card-title">@item.Name</h5>
|
||||
<p class="card-text flex-grow-1">@item.Description</p>
|
||||
<div class="d-flex justify-content-between align-items-center mt-3">
|
||||
<span class="badge bg-primary fs-6">@item.Cost.ToString("N0") pts</span>
|
||||
<button class="btn btn-sm btn-outline-success" @onclick="() => BuyItem(item)" disabled="@(player?.Points < item.Cost)">
|
||||
Purchase
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</Authorized>
|
||||
<NotAuthorized>
|
||||
<div class="container text-center mt-5">
|
||||
<h1 class="display-5">Welcome to PixelTalk</h1>
|
||||
<p class="lead mt-3 text-muted">You are not authenticated. Please log in utilizing our in-game system to view your dashboard.</p>
|
||||
<a href="/auth" class="btn btn-primary btn-lg mt-4 shadow-sm">Authenticate via Game</a>
|
||||
</div>
|
||||
</NotAuthorized>
|
||||
</AuthorizeView>
|
||||
|
||||
@code {
|
||||
private PlayerData? player;
|
||||
private List<StoreItem> storeItems = new();
|
||||
private string? message;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
var authState = await AuthStateProvider.GetAuthenticationStateAsync();
|
||||
var user = authState.User;
|
||||
if (user.Identity?.IsAuthenticated ?? false)
|
||||
{
|
||||
var uuid = user.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value;
|
||||
if (uuid != null)
|
||||
{
|
||||
player = await MongoService.Players.Find(p => p.Uuid == uuid).FirstOrDefaultAsync();
|
||||
storeItems = await MongoService.StoreItems.Find(_ => true).ToListAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task BuyItem(StoreItem item)
|
||||
{
|
||||
message = null;
|
||||
if (player == null || player.Points < item.Cost) return;
|
||||
|
||||
bool success = await MongoService.DeductPointsAsync(player.Uuid, item.Cost);
|
||||
if (success)
|
||||
{
|
||||
player.Points -= item.Cost;
|
||||
|
||||
var log = new PurchaseLog
|
||||
{
|
||||
Uuid = player.Uuid,
|
||||
ItemId = item.Id,
|
||||
ItemName = item.Name,
|
||||
Cost = item.Cost,
|
||||
Timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds()
|
||||
};
|
||||
await MongoService.PurchaseLogs.InsertOneAsync(log);
|
||||
|
||||
message = $"Successfully purchased {item.Name} for {item.Cost} points!";
|
||||
}
|
||||
else
|
||||
{
|
||||
message = "Transaction failed. Insufficient points or an error occurred!";
|
||||
}
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
@page "/not-found"
|
||||
@layout MainLayout
|
||||
|
||||
<h3>Not Found</h3>
|
||||
<p>Sorry, the content you are looking for does not exist.</p>
|
||||
@@ -0,0 +1,67 @@
|
||||
@page "/weather"
|
||||
@attribute [StreamRendering]
|
||||
|
||||
<PageTitle>Weather</PageTitle>
|
||||
|
||||
<h1>Weather</h1>
|
||||
|
||||
<p>This component demonstrates showing data.</p>
|
||||
|
||||
@if (forecasts == null)
|
||||
{
|
||||
<p>
|
||||
<em>Loading...</em>
|
||||
</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th aria-label="Temperature in Celsius">Temp. (C)</th>
|
||||
<th aria-label="Temperature in Fahrenheit">Temp. (F)</th>
|
||||
<th>Summary</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var forecast in forecasts)
|
||||
{
|
||||
<tr>
|
||||
<td>@forecast.Date.ToShortDateString()</td>
|
||||
<td>@forecast.TemperatureC</td>
|
||||
<td>@forecast.TemperatureF</td>
|
||||
<td>@forecast.Summary</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
}
|
||||
|
||||
@code {
|
||||
private WeatherForecast[]? forecasts;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
// Simulate asynchronous loading to demonstrate streaming rendering
|
||||
await Task.Delay(500);
|
||||
|
||||
var startDate = DateOnly.FromDateTime(DateTime.Now);
|
||||
var summaries = new[] { "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" };
|
||||
forecasts = Enumerable.Range(1, 5).Select(index => new WeatherForecast
|
||||
{
|
||||
Date = startDate.AddDays(index),
|
||||
TemperatureC = Random.Shared.Next(-20, 55),
|
||||
Summary = summaries[Random.Shared.Next(summaries.Length)]
|
||||
}).ToArray();
|
||||
}
|
||||
|
||||
private class WeatherForecast
|
||||
{
|
||||
public DateOnly Date { get; set; }
|
||||
public int TemperatureC { get; set; }
|
||||
public string? Summary { get; set; }
|
||||
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
<Router AppAssembly="typeof(Program).Assembly" NotFoundPage="typeof(Pages.NotFound)">
|
||||
<Found Context="routeData">
|
||||
<RouteView RouteData="routeData" DefaultLayout="typeof(Layout.MainLayout)"/>
|
||||
<FocusOnNavigate RouteData="routeData" Selector="h1"/>
|
||||
</Found>
|
||||
</Router>
|
||||
@@ -0,0 +1,12 @@
|
||||
@using System.Net.Http
|
||||
@using System.Net.Http.Json
|
||||
@using Microsoft.AspNetCore.Components.Forms
|
||||
@using Microsoft.AspNetCore.Components.Routing
|
||||
@using Microsoft.AspNetCore.Components.Web
|
||||
@using Microsoft.AspNetCore.Components.Authorization
|
||||
@using static Microsoft.AspNetCore.Components.Web.RenderMode
|
||||
@using Microsoft.AspNetCore.Components.Web.Virtualization
|
||||
@using Microsoft.JSInterop
|
||||
@using BlazorApp1
|
||||
@using BlazorApp1.Components
|
||||
@using BlazorApp1.Components.Layout
|
||||
@@ -0,0 +1,84 @@
|
||||
using MongoDB.Bson;
|
||||
using MongoDB.Bson.Serialization.Attributes;
|
||||
|
||||
namespace web.PixelTalk.Models;
|
||||
|
||||
public class PlayerData
|
||||
{
|
||||
[BsonId]
|
||||
[BsonElement("_id")]
|
||||
public ObjectId Id { get; set; }
|
||||
[BsonElement("uuid")]
|
||||
public string Uuid { get; set; } = string.Empty;
|
||||
[BsonElement("language")]
|
||||
public string Language { get; set; } = string.Empty;
|
||||
|
||||
[BsonElement("age")] public int Age { get; set; } = 6;
|
||||
[BsonElement("registered")] public bool Registred { get; set; }
|
||||
[BsonElement("firstJoin")] public ulong FirstJoinTimestamp { get; set; }
|
||||
[BsonElement("interests")] public string Interests { get; set; }
|
||||
[BsonElement("name")]
|
||||
public string Nickname { get; set; } = string.Empty;
|
||||
[BsonElement("points")]
|
||||
public double Points { get; set; }
|
||||
[BsonElement("role")]
|
||||
public string Role { get; set; } = "player";
|
||||
}
|
||||
|
||||
public class Report
|
||||
{
|
||||
[BsonId]
|
||||
public ObjectId Id { get; set; }
|
||||
[BsonElement("reporter")]
|
||||
public string ReporterUuid { get; set; } = string.Empty;
|
||||
[BsonElement("target")]
|
||||
public string TargetUuid { get; set; } = string.Empty;
|
||||
[BsonElement("reason")]
|
||||
public string Reason { get; set; } = string.Empty;
|
||||
[BsonElement("timestamp")]
|
||||
public long Timestamp { get; set; }
|
||||
[BsonElement("resolved")]
|
||||
public bool Resolved { get; set; }
|
||||
}
|
||||
|
||||
public class AuthRequest
|
||||
{
|
||||
[BsonId]
|
||||
public ObjectId Id { get; set; }
|
||||
[BsonElement("code")]
|
||||
public string Code { get; set; } = string.Empty;
|
||||
[BsonElement("resolved")]
|
||||
public bool Resolved { get; set; }
|
||||
[BsonElement("uuid")]
|
||||
public string? Uuid { get; set; }
|
||||
[BsonElement("timestamp")]
|
||||
public long Timestamp { get; set; }
|
||||
}
|
||||
|
||||
public class StoreItem
|
||||
{
|
||||
[BsonId]
|
||||
public ObjectId Id { get; set; }
|
||||
[BsonElement("name")]
|
||||
public string Name { get; set; } = string.Empty;
|
||||
[BsonElement("description")]
|
||||
public string Description { get; set; } = string.Empty;
|
||||
[BsonElement("cost")]
|
||||
public int Cost { get; set; }
|
||||
}
|
||||
|
||||
public class PurchaseLog
|
||||
{
|
||||
[BsonId]
|
||||
public ObjectId Id { get; set; }
|
||||
[BsonElement("uuid")]
|
||||
public string Uuid { get; set; } = string.Empty;
|
||||
[BsonElement("itemId")]
|
||||
public ObjectId ItemId { get; set; }
|
||||
[BsonElement("itemName")]
|
||||
public string ItemName { get; set; } = string.Empty;
|
||||
[BsonElement("cost")]
|
||||
public int Cost { get; set; }
|
||||
[BsonElement("timestamp")]
|
||||
public long Timestamp { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
using BlazorApp1.Components;
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using MongoDB.Driver;
|
||||
using web.PixelTalk.Services;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
// Add services to the container.
|
||||
builder.Services.AddRazorComponents()
|
||||
.AddInteractiveServerComponents();
|
||||
|
||||
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
|
||||
.AddCookie(options =>
|
||||
{
|
||||
options.LoginPath = "/auth";
|
||||
options.Cookie.Name = "PixelTalkAuth";
|
||||
});
|
||||
builder.Services.AddAuthorization();
|
||||
builder.Services.AddCascadingAuthenticationState();
|
||||
builder.Services.AddHttpContextAccessor();
|
||||
|
||||
builder.Services.AddSingleton<IMongoClient>(sp =>
|
||||
{
|
||||
var connectionString = builder.Configuration.GetConnectionString("MongoDb");
|
||||
return new MongoClient(connectionString ?? "mongodb://witteringgray:27017");
|
||||
});
|
||||
builder.Services.AddSingleton<MongoService>();
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
// Configure the HTTP request pipeline.
|
||||
if (!app.Environment.IsDevelopment())
|
||||
{
|
||||
app.UseExceptionHandler("/Error");
|
||||
app.UseHsts();
|
||||
}
|
||||
|
||||
app.UseStatusCodePagesWithReExecute("/not-found");
|
||||
app.UseHttpsRedirection();
|
||||
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
app.UseAntiforgery();
|
||||
|
||||
app.MapStaticAssets();
|
||||
app.MapRazorComponents<App>()
|
||||
.AddInteractiveServerRenderMode();
|
||||
|
||||
app.MapGet("/login_callback", async (string code, web.PixelTalk.Services.MongoService mongoService, HttpContext context) =>
|
||||
{
|
||||
var req = await mongoService.AuthRequests.Find(r => r.Code == code && r.Resolved == true).FirstOrDefaultAsync();
|
||||
if (req != null && !string.IsNullOrEmpty(req.Uuid))
|
||||
{
|
||||
Console.WriteLine(req.Uuid);
|
||||
var player = await mongoService.Players.Find(p => p.Uuid == req.Uuid).FirstOrDefaultAsync();
|
||||
var claims = new List<System.Security.Claims.Claim>
|
||||
{
|
||||
new(System.Security.Claims.ClaimTypes.NameIdentifier, req.Uuid),
|
||||
new(System.Security.Claims.ClaimTypes.Name, player?.Nickname ?? req.Uuid)
|
||||
};
|
||||
if (player?.Role == "op")
|
||||
{
|
||||
claims.Add(new System.Security.Claims.Claim(System.Security.Claims.ClaimTypes.Role, "op"));
|
||||
}
|
||||
var identity = new System.Security.Claims.ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
|
||||
var principal = new System.Security.Claims.ClaimsPrincipal(identity);
|
||||
|
||||
await Microsoft.AspNetCore.Authentication.AuthenticationHttpContextExtensions.SignInAsync(context, CookieAuthenticationDefaults.AuthenticationScheme, principal);
|
||||
|
||||
return Results.Redirect("/");
|
||||
}
|
||||
return Results.Redirect("/auth");
|
||||
});
|
||||
|
||||
app.Run();
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/launchsettings.json",
|
||||
"profiles": {
|
||||
"http": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"applicationUrl": "http://localhost:5182",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"https": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"applicationUrl": "https://localhost:7135;http://localhost:5182",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
using MongoDB.Bson;
|
||||
using MongoDB.Driver;
|
||||
using web.PixelTalk.Models;
|
||||
|
||||
namespace web.PixelTalk.Services;
|
||||
|
||||
public class MongoService
|
||||
{
|
||||
private readonly IMongoDatabase _database;
|
||||
|
||||
public IMongoCollection<PlayerData> Players => _database.GetCollection<PlayerData>("players");
|
||||
public IMongoCollection<Report> Reports => _database.GetCollection<Report>("reports");
|
||||
public IMongoCollection<AuthRequest> AuthRequests => _database.GetCollection<AuthRequest>("auth_requests");
|
||||
public IMongoCollection<StoreItem> StoreItems => _database.GetCollection<StoreItem>("store_items");
|
||||
public IMongoCollection<PurchaseLog> PurchaseLogs => _database.GetCollection<PurchaseLog>("purchases_log");
|
||||
|
||||
public MongoService(IMongoClient client, IConfiguration config)
|
||||
{
|
||||
_database = client.GetDatabase(config.GetValue<string>("MongoDatabaseName") ?? "pixeltalk");
|
||||
}
|
||||
|
||||
public async Task<bool> DeductPointsAsync(string uuid, int amount)
|
||||
{
|
||||
var filter = Builders<PlayerData>.Filter.And(
|
||||
Builders<PlayerData>.Filter.Eq(x => x.Uuid, uuid),
|
||||
Builders<PlayerData>.Filter.Gte(x => x.Points, amount)
|
||||
);
|
||||
var update = Builders<PlayerData>.Update.Inc(x => x.Points, -amount);
|
||||
|
||||
var result = await Players.UpdateOneAsync(filter, update);
|
||||
return result.ModifiedCount > 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<AllowMissingPrunePackageData>true</AllowMissingPrunePackageData>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<BlazorDisableThrowNavigationException>true</BlazorDisableThrowNavigationException>
|
||||
<RootNamespace>BlazorApp1</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="9.0.6" />
|
||||
<PackageReference Include="MongoDB.Driver" Version="3.7.1" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,60 @@
|
||||
html, body {
|
||||
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
a, .btn-link {
|
||||
color: #006bb7;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
color: #fff;
|
||||
background-color: #1b6ec2;
|
||||
border-color: #1861ac;
|
||||
}
|
||||
|
||||
.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
|
||||
box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding-top: 1.1rem;
|
||||
}
|
||||
|
||||
h1:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.valid.modified:not([type=checkbox]) {
|
||||
outline: 1px solid #26b050;
|
||||
}
|
||||
|
||||
.invalid {
|
||||
outline: 1px solid #e50000;
|
||||
}
|
||||
|
||||
.validation-message {
|
||||
color: #e50000;
|
||||
}
|
||||
|
||||
.blazor-error-boundary {
|
||||
background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
|
||||
padding: 1rem 1rem 1rem 3.7rem;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.blazor-error-boundary::after {
|
||||
content: "An error has occurred."
|
||||
}
|
||||
|
||||
.darker-border-checkbox.form-check-input {
|
||||
border-color: #929292;
|
||||
}
|
||||
|
||||
.form-floating > .form-control-plaintext::placeholder, .form-floating > .form-control::placeholder {
|
||||
color: var(--bs-secondary-color);
|
||||
text-align: end;
|
||||
}
|
||||
|
||||
.form-floating > .form-control-plaintext:focus::placeholder, .form-floating > .form-control:focus::placeholder {
|
||||
text-align: start;
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
+4085
File diff suppressed because it is too large
Load Diff
+1
File diff suppressed because one or more lines are too long
+6
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
+4084
File diff suppressed because it is too large
Load Diff
+1
File diff suppressed because one or more lines are too long
+6
File diff suppressed because one or more lines are too long
Vendored
+1
File diff suppressed because one or more lines are too long
+597
@@ -0,0 +1,597 @@
|
||||
/*!
|
||||
* Bootstrap Reboot v5.3.3 (https://getbootstrap.com/)
|
||||
* Copyright 2011-2024 The Bootstrap Authors
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
*/
|
||||
:root,
|
||||
[data-bs-theme=light] {
|
||||
--bs-blue: #0d6efd;
|
||||
--bs-indigo: #6610f2;
|
||||
--bs-purple: #6f42c1;
|
||||
--bs-pink: #d63384;
|
||||
--bs-red: #dc3545;
|
||||
--bs-orange: #fd7e14;
|
||||
--bs-yellow: #ffc107;
|
||||
--bs-green: #198754;
|
||||
--bs-teal: #20c997;
|
||||
--bs-cyan: #0dcaf0;
|
||||
--bs-black: #000;
|
||||
--bs-white: #fff;
|
||||
--bs-gray: #6c757d;
|
||||
--bs-gray-dark: #343a40;
|
||||
--bs-gray-100: #f8f9fa;
|
||||
--bs-gray-200: #e9ecef;
|
||||
--bs-gray-300: #dee2e6;
|
||||
--bs-gray-400: #ced4da;
|
||||
--bs-gray-500: #adb5bd;
|
||||
--bs-gray-600: #6c757d;
|
||||
--bs-gray-700: #495057;
|
||||
--bs-gray-800: #343a40;
|
||||
--bs-gray-900: #212529;
|
||||
--bs-primary: #0d6efd;
|
||||
--bs-secondary: #6c757d;
|
||||
--bs-success: #198754;
|
||||
--bs-info: #0dcaf0;
|
||||
--bs-warning: #ffc107;
|
||||
--bs-danger: #dc3545;
|
||||
--bs-light: #f8f9fa;
|
||||
--bs-dark: #212529;
|
||||
--bs-primary-rgb: 13, 110, 253;
|
||||
--bs-secondary-rgb: 108, 117, 125;
|
||||
--bs-success-rgb: 25, 135, 84;
|
||||
--bs-info-rgb: 13, 202, 240;
|
||||
--bs-warning-rgb: 255, 193, 7;
|
||||
--bs-danger-rgb: 220, 53, 69;
|
||||
--bs-light-rgb: 248, 249, 250;
|
||||
--bs-dark-rgb: 33, 37, 41;
|
||||
--bs-primary-text-emphasis: #052c65;
|
||||
--bs-secondary-text-emphasis: #2b2f32;
|
||||
--bs-success-text-emphasis: #0a3622;
|
||||
--bs-info-text-emphasis: #055160;
|
||||
--bs-warning-text-emphasis: #664d03;
|
||||
--bs-danger-text-emphasis: #58151c;
|
||||
--bs-light-text-emphasis: #495057;
|
||||
--bs-dark-text-emphasis: #495057;
|
||||
--bs-primary-bg-subtle: #cfe2ff;
|
||||
--bs-secondary-bg-subtle: #e2e3e5;
|
||||
--bs-success-bg-subtle: #d1e7dd;
|
||||
--bs-info-bg-subtle: #cff4fc;
|
||||
--bs-warning-bg-subtle: #fff3cd;
|
||||
--bs-danger-bg-subtle: #f8d7da;
|
||||
--bs-light-bg-subtle: #fcfcfd;
|
||||
--bs-dark-bg-subtle: #ced4da;
|
||||
--bs-primary-border-subtle: #9ec5fe;
|
||||
--bs-secondary-border-subtle: #c4c8cb;
|
||||
--bs-success-border-subtle: #a3cfbb;
|
||||
--bs-info-border-subtle: #9eeaf9;
|
||||
--bs-warning-border-subtle: #ffe69c;
|
||||
--bs-danger-border-subtle: #f1aeb5;
|
||||
--bs-light-border-subtle: #e9ecef;
|
||||
--bs-dark-border-subtle: #adb5bd;
|
||||
--bs-white-rgb: 255, 255, 255;
|
||||
--bs-black-rgb: 0, 0, 0;
|
||||
--bs-font-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", "Liberation Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||
--bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||
--bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));
|
||||
--bs-body-font-family: var(--bs-font-sans-serif);
|
||||
--bs-body-font-size: 1rem;
|
||||
--bs-body-font-weight: 400;
|
||||
--bs-body-line-height: 1.5;
|
||||
--bs-body-color: #212529;
|
||||
--bs-body-color-rgb: 33, 37, 41;
|
||||
--bs-body-bg: #fff;
|
||||
--bs-body-bg-rgb: 255, 255, 255;
|
||||
--bs-emphasis-color: #000;
|
||||
--bs-emphasis-color-rgb: 0, 0, 0;
|
||||
--bs-secondary-color: rgba(33, 37, 41, 0.75);
|
||||
--bs-secondary-color-rgb: 33, 37, 41;
|
||||
--bs-secondary-bg: #e9ecef;
|
||||
--bs-secondary-bg-rgb: 233, 236, 239;
|
||||
--bs-tertiary-color: rgba(33, 37, 41, 0.5);
|
||||
--bs-tertiary-color-rgb: 33, 37, 41;
|
||||
--bs-tertiary-bg: #f8f9fa;
|
||||
--bs-tertiary-bg-rgb: 248, 249, 250;
|
||||
--bs-heading-color: inherit;
|
||||
--bs-link-color: #0d6efd;
|
||||
--bs-link-color-rgb: 13, 110, 253;
|
||||
--bs-link-decoration: underline;
|
||||
--bs-link-hover-color: #0a58ca;
|
||||
--bs-link-hover-color-rgb: 10, 88, 202;
|
||||
--bs-code-color: #d63384;
|
||||
--bs-highlight-color: #212529;
|
||||
--bs-highlight-bg: #fff3cd;
|
||||
--bs-border-width: 1px;
|
||||
--bs-border-style: solid;
|
||||
--bs-border-color: #dee2e6;
|
||||
--bs-border-color-translucent: rgba(0, 0, 0, 0.175);
|
||||
--bs-border-radius: 0.375rem;
|
||||
--bs-border-radius-sm: 0.25rem;
|
||||
--bs-border-radius-lg: 0.5rem;
|
||||
--bs-border-radius-xl: 1rem;
|
||||
--bs-border-radius-xxl: 2rem;
|
||||
--bs-border-radius-2xl: var(--bs-border-radius-xxl);
|
||||
--bs-border-radius-pill: 50rem;
|
||||
--bs-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
|
||||
--bs-box-shadow-sm: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
|
||||
--bs-box-shadow-lg: 0 1rem 3rem rgba(0, 0, 0, 0.175);
|
||||
--bs-box-shadow-inset: inset 0 1px 2px rgba(0, 0, 0, 0.075);
|
||||
--bs-focus-ring-width: 0.25rem;
|
||||
--bs-focus-ring-opacity: 0.25;
|
||||
--bs-focus-ring-color: rgba(13, 110, 253, 0.25);
|
||||
--bs-form-valid-color: #198754;
|
||||
--bs-form-valid-border-color: #198754;
|
||||
--bs-form-invalid-color: #dc3545;
|
||||
--bs-form-invalid-border-color: #dc3545;
|
||||
}
|
||||
|
||||
[data-bs-theme=dark] {
|
||||
color-scheme: dark;
|
||||
--bs-body-color: #dee2e6;
|
||||
--bs-body-color-rgb: 222, 226, 230;
|
||||
--bs-body-bg: #212529;
|
||||
--bs-body-bg-rgb: 33, 37, 41;
|
||||
--bs-emphasis-color: #fff;
|
||||
--bs-emphasis-color-rgb: 255, 255, 255;
|
||||
--bs-secondary-color: rgba(222, 226, 230, 0.75);
|
||||
--bs-secondary-color-rgb: 222, 226, 230;
|
||||
--bs-secondary-bg: #343a40;
|
||||
--bs-secondary-bg-rgb: 52, 58, 64;
|
||||
--bs-tertiary-color: rgba(222, 226, 230, 0.5);
|
||||
--bs-tertiary-color-rgb: 222, 226, 230;
|
||||
--bs-tertiary-bg: #2b3035;
|
||||
--bs-tertiary-bg-rgb: 43, 48, 53;
|
||||
--bs-primary-text-emphasis: #6ea8fe;
|
||||
--bs-secondary-text-emphasis: #a7acb1;
|
||||
--bs-success-text-emphasis: #75b798;
|
||||
--bs-info-text-emphasis: #6edff6;
|
||||
--bs-warning-text-emphasis: #ffda6a;
|
||||
--bs-danger-text-emphasis: #ea868f;
|
||||
--bs-light-text-emphasis: #f8f9fa;
|
||||
--bs-dark-text-emphasis: #dee2e6;
|
||||
--bs-primary-bg-subtle: #031633;
|
||||
--bs-secondary-bg-subtle: #161719;
|
||||
--bs-success-bg-subtle: #051b11;
|
||||
--bs-info-bg-subtle: #032830;
|
||||
--bs-warning-bg-subtle: #332701;
|
||||
--bs-danger-bg-subtle: #2c0b0e;
|
||||
--bs-light-bg-subtle: #343a40;
|
||||
--bs-dark-bg-subtle: #1a1d20;
|
||||
--bs-primary-border-subtle: #084298;
|
||||
--bs-secondary-border-subtle: #41464b;
|
||||
--bs-success-border-subtle: #0f5132;
|
||||
--bs-info-border-subtle: #087990;
|
||||
--bs-warning-border-subtle: #997404;
|
||||
--bs-danger-border-subtle: #842029;
|
||||
--bs-light-border-subtle: #495057;
|
||||
--bs-dark-border-subtle: #343a40;
|
||||
--bs-heading-color: inherit;
|
||||
--bs-link-color: #6ea8fe;
|
||||
--bs-link-hover-color: #8bb9fe;
|
||||
--bs-link-color-rgb: 110, 168, 254;
|
||||
--bs-link-hover-color-rgb: 139, 185, 254;
|
||||
--bs-code-color: #e685b5;
|
||||
--bs-highlight-color: #dee2e6;
|
||||
--bs-highlight-bg: #664d03;
|
||||
--bs-border-color: #495057;
|
||||
--bs-border-color-translucent: rgba(255, 255, 255, 0.15);
|
||||
--bs-form-valid-color: #75b798;
|
||||
--bs-form-valid-border-color: #75b798;
|
||||
--bs-form-invalid-color: #ea868f;
|
||||
--bs-form-invalid-border-color: #ea868f;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
:root {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: var(--bs-body-font-family);
|
||||
font-size: var(--bs-body-font-size);
|
||||
font-weight: var(--bs-body-font-weight);
|
||||
line-height: var(--bs-body-line-height);
|
||||
color: var(--bs-body-color);
|
||||
text-align: var(--bs-body-text-align);
|
||||
background-color: var(--bs-body-bg);
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
hr {
|
||||
margin: 1rem 0;
|
||||
color: inherit;
|
||||
border: 0;
|
||||
border-top: var(--bs-border-width) solid;
|
||||
opacity: 0.25;
|
||||
}
|
||||
|
||||
h6, h5, h4, h3, h2, h1 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: 500;
|
||||
line-height: 1.2;
|
||||
color: var(--bs-heading-color);
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: calc(1.375rem + 1.5vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h1 {
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: calc(1.325rem + 0.9vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h2 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: calc(1.3rem + 0.6vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h3 {
|
||||
font-size: 1.75rem;
|
||||
}
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: calc(1.275rem + 0.3vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h4 {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
h6 {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
abbr[title] {
|
||||
-webkit-text-decoration: underline dotted;
|
||||
text-decoration: underline dotted;
|
||||
cursor: help;
|
||||
-webkit-text-decoration-skip-ink: none;
|
||||
text-decoration-skip-ink: none;
|
||||
}
|
||||
|
||||
address {
|
||||
margin-bottom: 1rem;
|
||||
font-style: normal;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
ol,
|
||||
ul {
|
||||
padding-left: 2rem;
|
||||
}
|
||||
|
||||
ol,
|
||||
ul,
|
||||
dl {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
ol ol,
|
||||
ul ul,
|
||||
ol ul,
|
||||
ul ol {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
dt {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
dd {
|
||||
margin-bottom: 0.5rem;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
margin: 0 0 1rem;
|
||||
}
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
small {
|
||||
font-size: 0.875em;
|
||||
}
|
||||
|
||||
mark {
|
||||
padding: 0.1875em;
|
||||
color: var(--bs-highlight-color);
|
||||
background-color: var(--bs-highlight-bg);
|
||||
}
|
||||
|
||||
sub,
|
||||
sup {
|
||||
position: relative;
|
||||
font-size: 0.75em;
|
||||
line-height: 0;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -0.25em;
|
||||
}
|
||||
|
||||
sup {
|
||||
top: -0.5em;
|
||||
}
|
||||
|
||||
a {
|
||||
color: rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 1));
|
||||
text-decoration: underline;
|
||||
}
|
||||
a:hover {
|
||||
--bs-link-color-rgb: var(--bs-link-hover-color-rgb);
|
||||
}
|
||||
|
||||
a:not([href]):not([class]), a:not([href]):not([class]):hover {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
pre,
|
||||
code,
|
||||
kbd,
|
||||
samp {
|
||||
font-family: var(--bs-font-monospace);
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
pre {
|
||||
display: block;
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
overflow: auto;
|
||||
font-size: 0.875em;
|
||||
}
|
||||
pre code {
|
||||
font-size: inherit;
|
||||
color: inherit;
|
||||
word-break: normal;
|
||||
}
|
||||
|
||||
code {
|
||||
font-size: 0.875em;
|
||||
color: var(--bs-code-color);
|
||||
word-wrap: break-word;
|
||||
}
|
||||
a > code {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
kbd {
|
||||
padding: 0.1875rem 0.375rem;
|
||||
font-size: 0.875em;
|
||||
color: var(--bs-body-bg);
|
||||
background-color: var(--bs-body-color);
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
kbd kbd {
|
||||
padding: 0;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
figure {
|
||||
margin: 0 0 1rem;
|
||||
}
|
||||
|
||||
img,
|
||||
svg {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
table {
|
||||
caption-side: bottom;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
caption {
|
||||
padding-top: 0.5rem;
|
||||
padding-bottom: 0.5rem;
|
||||
color: var(--bs-secondary-color);
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
th {
|
||||
text-align: inherit;
|
||||
text-align: -webkit-match-parent;
|
||||
}
|
||||
|
||||
thead,
|
||||
tbody,
|
||||
tfoot,
|
||||
tr,
|
||||
td,
|
||||
th {
|
||||
border-color: inherit;
|
||||
border-style: solid;
|
||||
border-width: 0;
|
||||
}
|
||||
|
||||
label {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
button {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
button:focus:not(:focus-visible) {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
input,
|
||||
button,
|
||||
select,
|
||||
optgroup,
|
||||
textarea {
|
||||
margin: 0;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
button,
|
||||
select {
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
[role=button] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
select {
|
||||
word-wrap: normal;
|
||||
}
|
||||
select:disabled {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
[list]:not([type=date]):not([type=datetime-local]):not([type=month]):not([type=week]):not([type=time])::-webkit-calendar-picker-indicator {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
button,
|
||||
[type=button],
|
||||
[type=reset],
|
||||
[type=submit] {
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
button:not(:disabled),
|
||||
[type=button]:not(:disabled),
|
||||
[type=reset]:not(:disabled),
|
||||
[type=submit]:not(:disabled) {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
::-moz-focus-inner {
|
||||
padding: 0;
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
textarea {
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
fieldset {
|
||||
min-width: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
legend {
|
||||
float: left;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
margin-bottom: 0.5rem;
|
||||
font-size: calc(1.275rem + 0.3vw);
|
||||
line-height: inherit;
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
legend {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
}
|
||||
legend + * {
|
||||
clear: left;
|
||||
}
|
||||
|
||||
::-webkit-datetime-edit-fields-wrapper,
|
||||
::-webkit-datetime-edit-text,
|
||||
::-webkit-datetime-edit-minute,
|
||||
::-webkit-datetime-edit-hour-field,
|
||||
::-webkit-datetime-edit-day-field,
|
||||
::-webkit-datetime-edit-month-field,
|
||||
::-webkit-datetime-edit-year-field {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
::-webkit-inner-spin-button {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
[type=search] {
|
||||
-webkit-appearance: textfield;
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
||||
/* rtl:raw:
|
||||
[type="tel"],
|
||||
[type="url"],
|
||||
[type="email"],
|
||||
[type="number"] {
|
||||
direction: ltr;
|
||||
}
|
||||
*/
|
||||
::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
::-webkit-color-swatch-wrapper {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
::-webkit-file-upload-button {
|
||||
font: inherit;
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
|
||||
::file-selector-button {
|
||||
font: inherit;
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
|
||||
output {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
iframe {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
summary {
|
||||
display: list-item;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
progress {
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/*# sourceMappingURL=bootstrap-reboot.css.map */
|
||||
+1
File diff suppressed because one or more lines are too long
+6
File diff suppressed because one or more lines are too long
Vendored
+1
File diff suppressed because one or more lines are too long
+594
@@ -0,0 +1,594 @@
|
||||
/*!
|
||||
* Bootstrap Reboot v5.3.3 (https://getbootstrap.com/)
|
||||
* Copyright 2011-2024 The Bootstrap Authors
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
*/
|
||||
:root,
|
||||
[data-bs-theme=light] {
|
||||
--bs-blue: #0d6efd;
|
||||
--bs-indigo: #6610f2;
|
||||
--bs-purple: #6f42c1;
|
||||
--bs-pink: #d63384;
|
||||
--bs-red: #dc3545;
|
||||
--bs-orange: #fd7e14;
|
||||
--bs-yellow: #ffc107;
|
||||
--bs-green: #198754;
|
||||
--bs-teal: #20c997;
|
||||
--bs-cyan: #0dcaf0;
|
||||
--bs-black: #000;
|
||||
--bs-white: #fff;
|
||||
--bs-gray: #6c757d;
|
||||
--bs-gray-dark: #343a40;
|
||||
--bs-gray-100: #f8f9fa;
|
||||
--bs-gray-200: #e9ecef;
|
||||
--bs-gray-300: #dee2e6;
|
||||
--bs-gray-400: #ced4da;
|
||||
--bs-gray-500: #adb5bd;
|
||||
--bs-gray-600: #6c757d;
|
||||
--bs-gray-700: #495057;
|
||||
--bs-gray-800: #343a40;
|
||||
--bs-gray-900: #212529;
|
||||
--bs-primary: #0d6efd;
|
||||
--bs-secondary: #6c757d;
|
||||
--bs-success: #198754;
|
||||
--bs-info: #0dcaf0;
|
||||
--bs-warning: #ffc107;
|
||||
--bs-danger: #dc3545;
|
||||
--bs-light: #f8f9fa;
|
||||
--bs-dark: #212529;
|
||||
--bs-primary-rgb: 13, 110, 253;
|
||||
--bs-secondary-rgb: 108, 117, 125;
|
||||
--bs-success-rgb: 25, 135, 84;
|
||||
--bs-info-rgb: 13, 202, 240;
|
||||
--bs-warning-rgb: 255, 193, 7;
|
||||
--bs-danger-rgb: 220, 53, 69;
|
||||
--bs-light-rgb: 248, 249, 250;
|
||||
--bs-dark-rgb: 33, 37, 41;
|
||||
--bs-primary-text-emphasis: #052c65;
|
||||
--bs-secondary-text-emphasis: #2b2f32;
|
||||
--bs-success-text-emphasis: #0a3622;
|
||||
--bs-info-text-emphasis: #055160;
|
||||
--bs-warning-text-emphasis: #664d03;
|
||||
--bs-danger-text-emphasis: #58151c;
|
||||
--bs-light-text-emphasis: #495057;
|
||||
--bs-dark-text-emphasis: #495057;
|
||||
--bs-primary-bg-subtle: #cfe2ff;
|
||||
--bs-secondary-bg-subtle: #e2e3e5;
|
||||
--bs-success-bg-subtle: #d1e7dd;
|
||||
--bs-info-bg-subtle: #cff4fc;
|
||||
--bs-warning-bg-subtle: #fff3cd;
|
||||
--bs-danger-bg-subtle: #f8d7da;
|
||||
--bs-light-bg-subtle: #fcfcfd;
|
||||
--bs-dark-bg-subtle: #ced4da;
|
||||
--bs-primary-border-subtle: #9ec5fe;
|
||||
--bs-secondary-border-subtle: #c4c8cb;
|
||||
--bs-success-border-subtle: #a3cfbb;
|
||||
--bs-info-border-subtle: #9eeaf9;
|
||||
--bs-warning-border-subtle: #ffe69c;
|
||||
--bs-danger-border-subtle: #f1aeb5;
|
||||
--bs-light-border-subtle: #e9ecef;
|
||||
--bs-dark-border-subtle: #adb5bd;
|
||||
--bs-white-rgb: 255, 255, 255;
|
||||
--bs-black-rgb: 0, 0, 0;
|
||||
--bs-font-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", "Liberation Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||
--bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||
--bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));
|
||||
--bs-body-font-family: var(--bs-font-sans-serif);
|
||||
--bs-body-font-size: 1rem;
|
||||
--bs-body-font-weight: 400;
|
||||
--bs-body-line-height: 1.5;
|
||||
--bs-body-color: #212529;
|
||||
--bs-body-color-rgb: 33, 37, 41;
|
||||
--bs-body-bg: #fff;
|
||||
--bs-body-bg-rgb: 255, 255, 255;
|
||||
--bs-emphasis-color: #000;
|
||||
--bs-emphasis-color-rgb: 0, 0, 0;
|
||||
--bs-secondary-color: rgba(33, 37, 41, 0.75);
|
||||
--bs-secondary-color-rgb: 33, 37, 41;
|
||||
--bs-secondary-bg: #e9ecef;
|
||||
--bs-secondary-bg-rgb: 233, 236, 239;
|
||||
--bs-tertiary-color: rgba(33, 37, 41, 0.5);
|
||||
--bs-tertiary-color-rgb: 33, 37, 41;
|
||||
--bs-tertiary-bg: #f8f9fa;
|
||||
--bs-tertiary-bg-rgb: 248, 249, 250;
|
||||
--bs-heading-color: inherit;
|
||||
--bs-link-color: #0d6efd;
|
||||
--bs-link-color-rgb: 13, 110, 253;
|
||||
--bs-link-decoration: underline;
|
||||
--bs-link-hover-color: #0a58ca;
|
||||
--bs-link-hover-color-rgb: 10, 88, 202;
|
||||
--bs-code-color: #d63384;
|
||||
--bs-highlight-color: #212529;
|
||||
--bs-highlight-bg: #fff3cd;
|
||||
--bs-border-width: 1px;
|
||||
--bs-border-style: solid;
|
||||
--bs-border-color: #dee2e6;
|
||||
--bs-border-color-translucent: rgba(0, 0, 0, 0.175);
|
||||
--bs-border-radius: 0.375rem;
|
||||
--bs-border-radius-sm: 0.25rem;
|
||||
--bs-border-radius-lg: 0.5rem;
|
||||
--bs-border-radius-xl: 1rem;
|
||||
--bs-border-radius-xxl: 2rem;
|
||||
--bs-border-radius-2xl: var(--bs-border-radius-xxl);
|
||||
--bs-border-radius-pill: 50rem;
|
||||
--bs-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
|
||||
--bs-box-shadow-sm: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
|
||||
--bs-box-shadow-lg: 0 1rem 3rem rgba(0, 0, 0, 0.175);
|
||||
--bs-box-shadow-inset: inset 0 1px 2px rgba(0, 0, 0, 0.075);
|
||||
--bs-focus-ring-width: 0.25rem;
|
||||
--bs-focus-ring-opacity: 0.25;
|
||||
--bs-focus-ring-color: rgba(13, 110, 253, 0.25);
|
||||
--bs-form-valid-color: #198754;
|
||||
--bs-form-valid-border-color: #198754;
|
||||
--bs-form-invalid-color: #dc3545;
|
||||
--bs-form-invalid-border-color: #dc3545;
|
||||
}
|
||||
|
||||
[data-bs-theme=dark] {
|
||||
color-scheme: dark;
|
||||
--bs-body-color: #dee2e6;
|
||||
--bs-body-color-rgb: 222, 226, 230;
|
||||
--bs-body-bg: #212529;
|
||||
--bs-body-bg-rgb: 33, 37, 41;
|
||||
--bs-emphasis-color: #fff;
|
||||
--bs-emphasis-color-rgb: 255, 255, 255;
|
||||
--bs-secondary-color: rgba(222, 226, 230, 0.75);
|
||||
--bs-secondary-color-rgb: 222, 226, 230;
|
||||
--bs-secondary-bg: #343a40;
|
||||
--bs-secondary-bg-rgb: 52, 58, 64;
|
||||
--bs-tertiary-color: rgba(222, 226, 230, 0.5);
|
||||
--bs-tertiary-color-rgb: 222, 226, 230;
|
||||
--bs-tertiary-bg: #2b3035;
|
||||
--bs-tertiary-bg-rgb: 43, 48, 53;
|
||||
--bs-primary-text-emphasis: #6ea8fe;
|
||||
--bs-secondary-text-emphasis: #a7acb1;
|
||||
--bs-success-text-emphasis: #75b798;
|
||||
--bs-info-text-emphasis: #6edff6;
|
||||
--bs-warning-text-emphasis: #ffda6a;
|
||||
--bs-danger-text-emphasis: #ea868f;
|
||||
--bs-light-text-emphasis: #f8f9fa;
|
||||
--bs-dark-text-emphasis: #dee2e6;
|
||||
--bs-primary-bg-subtle: #031633;
|
||||
--bs-secondary-bg-subtle: #161719;
|
||||
--bs-success-bg-subtle: #051b11;
|
||||
--bs-info-bg-subtle: #032830;
|
||||
--bs-warning-bg-subtle: #332701;
|
||||
--bs-danger-bg-subtle: #2c0b0e;
|
||||
--bs-light-bg-subtle: #343a40;
|
||||
--bs-dark-bg-subtle: #1a1d20;
|
||||
--bs-primary-border-subtle: #084298;
|
||||
--bs-secondary-border-subtle: #41464b;
|
||||
--bs-success-border-subtle: #0f5132;
|
||||
--bs-info-border-subtle: #087990;
|
||||
--bs-warning-border-subtle: #997404;
|
||||
--bs-danger-border-subtle: #842029;
|
||||
--bs-light-border-subtle: #495057;
|
||||
--bs-dark-border-subtle: #343a40;
|
||||
--bs-heading-color: inherit;
|
||||
--bs-link-color: #6ea8fe;
|
||||
--bs-link-hover-color: #8bb9fe;
|
||||
--bs-link-color-rgb: 110, 168, 254;
|
||||
--bs-link-hover-color-rgb: 139, 185, 254;
|
||||
--bs-code-color: #e685b5;
|
||||
--bs-highlight-color: #dee2e6;
|
||||
--bs-highlight-bg: #664d03;
|
||||
--bs-border-color: #495057;
|
||||
--bs-border-color-translucent: rgba(255, 255, 255, 0.15);
|
||||
--bs-form-valid-color: #75b798;
|
||||
--bs-form-valid-border-color: #75b798;
|
||||
--bs-form-invalid-color: #ea868f;
|
||||
--bs-form-invalid-border-color: #ea868f;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
:root {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: var(--bs-body-font-family);
|
||||
font-size: var(--bs-body-font-size);
|
||||
font-weight: var(--bs-body-font-weight);
|
||||
line-height: var(--bs-body-line-height);
|
||||
color: var(--bs-body-color);
|
||||
text-align: var(--bs-body-text-align);
|
||||
background-color: var(--bs-body-bg);
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
hr {
|
||||
margin: 1rem 0;
|
||||
color: inherit;
|
||||
border: 0;
|
||||
border-top: var(--bs-border-width) solid;
|
||||
opacity: 0.25;
|
||||
}
|
||||
|
||||
h6, h5, h4, h3, h2, h1 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: 500;
|
||||
line-height: 1.2;
|
||||
color: var(--bs-heading-color);
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: calc(1.375rem + 1.5vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h1 {
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: calc(1.325rem + 0.9vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h2 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: calc(1.3rem + 0.6vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h3 {
|
||||
font-size: 1.75rem;
|
||||
}
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: calc(1.275rem + 0.3vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h4 {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
h6 {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
abbr[title] {
|
||||
-webkit-text-decoration: underline dotted;
|
||||
text-decoration: underline dotted;
|
||||
cursor: help;
|
||||
-webkit-text-decoration-skip-ink: none;
|
||||
text-decoration-skip-ink: none;
|
||||
}
|
||||
|
||||
address {
|
||||
margin-bottom: 1rem;
|
||||
font-style: normal;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
ol,
|
||||
ul {
|
||||
padding-right: 2rem;
|
||||
}
|
||||
|
||||
ol,
|
||||
ul,
|
||||
dl {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
ol ol,
|
||||
ul ul,
|
||||
ol ul,
|
||||
ul ol {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
dt {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
dd {
|
||||
margin-bottom: 0.5rem;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
margin: 0 0 1rem;
|
||||
}
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
small {
|
||||
font-size: 0.875em;
|
||||
}
|
||||
|
||||
mark {
|
||||
padding: 0.1875em;
|
||||
color: var(--bs-highlight-color);
|
||||
background-color: var(--bs-highlight-bg);
|
||||
}
|
||||
|
||||
sub,
|
||||
sup {
|
||||
position: relative;
|
||||
font-size: 0.75em;
|
||||
line-height: 0;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -0.25em;
|
||||
}
|
||||
|
||||
sup {
|
||||
top: -0.5em;
|
||||
}
|
||||
|
||||
a {
|
||||
color: rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 1));
|
||||
text-decoration: underline;
|
||||
}
|
||||
a:hover {
|
||||
--bs-link-color-rgb: var(--bs-link-hover-color-rgb);
|
||||
}
|
||||
|
||||
a:not([href]):not([class]), a:not([href]):not([class]):hover {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
pre,
|
||||
code,
|
||||
kbd,
|
||||
samp {
|
||||
font-family: var(--bs-font-monospace);
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
pre {
|
||||
display: block;
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
overflow: auto;
|
||||
font-size: 0.875em;
|
||||
}
|
||||
pre code {
|
||||
font-size: inherit;
|
||||
color: inherit;
|
||||
word-break: normal;
|
||||
}
|
||||
|
||||
code {
|
||||
font-size: 0.875em;
|
||||
color: var(--bs-code-color);
|
||||
word-wrap: break-word;
|
||||
}
|
||||
a > code {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
kbd {
|
||||
padding: 0.1875rem 0.375rem;
|
||||
font-size: 0.875em;
|
||||
color: var(--bs-body-bg);
|
||||
background-color: var(--bs-body-color);
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
kbd kbd {
|
||||
padding: 0;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
figure {
|
||||
margin: 0 0 1rem;
|
||||
}
|
||||
|
||||
img,
|
||||
svg {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
table {
|
||||
caption-side: bottom;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
caption {
|
||||
padding-top: 0.5rem;
|
||||
padding-bottom: 0.5rem;
|
||||
color: var(--bs-secondary-color);
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
th {
|
||||
text-align: inherit;
|
||||
text-align: -webkit-match-parent;
|
||||
}
|
||||
|
||||
thead,
|
||||
tbody,
|
||||
tfoot,
|
||||
tr,
|
||||
td,
|
||||
th {
|
||||
border-color: inherit;
|
||||
border-style: solid;
|
||||
border-width: 0;
|
||||
}
|
||||
|
||||
label {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
button {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
button:focus:not(:focus-visible) {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
input,
|
||||
button,
|
||||
select,
|
||||
optgroup,
|
||||
textarea {
|
||||
margin: 0;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
button,
|
||||
select {
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
[role=button] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
select {
|
||||
word-wrap: normal;
|
||||
}
|
||||
select:disabled {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
[list]:not([type=date]):not([type=datetime-local]):not([type=month]):not([type=week]):not([type=time])::-webkit-calendar-picker-indicator {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
button,
|
||||
[type=button],
|
||||
[type=reset],
|
||||
[type=submit] {
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
button:not(:disabled),
|
||||
[type=button]:not(:disabled),
|
||||
[type=reset]:not(:disabled),
|
||||
[type=submit]:not(:disabled) {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
::-moz-focus-inner {
|
||||
padding: 0;
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
textarea {
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
fieldset {
|
||||
min-width: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
legend {
|
||||
float: right;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
margin-bottom: 0.5rem;
|
||||
font-size: calc(1.275rem + 0.3vw);
|
||||
line-height: inherit;
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
legend {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
}
|
||||
legend + * {
|
||||
clear: right;
|
||||
}
|
||||
|
||||
::-webkit-datetime-edit-fields-wrapper,
|
||||
::-webkit-datetime-edit-text,
|
||||
::-webkit-datetime-edit-minute,
|
||||
::-webkit-datetime-edit-hour-field,
|
||||
::-webkit-datetime-edit-day-field,
|
||||
::-webkit-datetime-edit-month-field,
|
||||
::-webkit-datetime-edit-year-field {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
::-webkit-inner-spin-button {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
[type=search] {
|
||||
-webkit-appearance: textfield;
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
||||
[type="tel"],
|
||||
[type="url"],
|
||||
[type="email"],
|
||||
[type="number"] {
|
||||
direction: ltr;
|
||||
}
|
||||
::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
::-webkit-color-swatch-wrapper {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
::-webkit-file-upload-button {
|
||||
font: inherit;
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
|
||||
::file-selector-button {
|
||||
font: inherit;
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
|
||||
output {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
iframe {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
summary {
|
||||
display: list-item;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
progress {
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
/*# sourceMappingURL=bootstrap-reboot.rtl.css.map */
|
||||
Vendored
+1
File diff suppressed because one or more lines are too long
Vendored
+6
File diff suppressed because one or more lines are too long
Vendored
+1
File diff suppressed because one or more lines are too long
+5402
File diff suppressed because it is too large
Load Diff
+1
File diff suppressed because one or more lines are too long
+6
File diff suppressed because one or more lines are too long
Vendored
+1
File diff suppressed because one or more lines are too long
+5393
File diff suppressed because it is too large
Load Diff
Vendored
+1
File diff suppressed because one or more lines are too long
Vendored
+6
File diff suppressed because one or more lines are too long
Vendored
+1
File diff suppressed because one or more lines are too long
+12057
File diff suppressed because it is too large
Load Diff
+1
File diff suppressed because one or more lines are too long
+6
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
+12030
File diff suppressed because it is too large
Load Diff
+1
File diff suppressed because one or more lines are too long
+6
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
+6314
File diff suppressed because it is too large
Load Diff
+1
File diff suppressed because one or more lines are too long
+7
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
+4447
File diff suppressed because it is too large
Load Diff
+1
File diff suppressed because one or more lines are too long
+7
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
+1
File diff suppressed because one or more lines are too long
+7
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user