roles and docs update
This commit is contained in:
parent
1922ee58ec
commit
1cff66b0d9
12 changed files with 1258 additions and 299 deletions
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
|
|
@ -1,3 +1,4 @@
|
|||
{
|
||||
"svelte.enable-ts-plugin": true
|
||||
"svelte.enable-ts-plugin": true,
|
||||
"typescript.experimental.useTsgo": true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
<script lang="ts">
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import { TAClient } from "moons-ta-client";
|
||||
import { TAClient, type Role } from "moons-ta-client";
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
export let isOpen: boolean = false;
|
||||
export let taClient: TAClient;
|
||||
export let tournamentGuid: string;
|
||||
export let tournamentRoles: Role[] = [];
|
||||
|
||||
// User states
|
||||
let discordUserId: string = "";
|
||||
|
|
@ -21,9 +22,8 @@
|
|||
discordAvatarUrl: string;
|
||||
} | null = null;
|
||||
|
||||
// Permission settings
|
||||
let hasAdminPermission: boolean = false;
|
||||
let hasViewPermission: boolean = true;
|
||||
// Role selection
|
||||
let selectedRoles: Set<string> = new Set();
|
||||
|
||||
// This will be implemented by the parent component
|
||||
async function fetchDiscordUser() {
|
||||
|
|
@ -74,8 +74,7 @@
|
|||
discordUser = null;
|
||||
userFound = false;
|
||||
error = null;
|
||||
hasAdminPermission = false;
|
||||
hasViewPermission = true;
|
||||
selectedRoles = new Set();
|
||||
}
|
||||
|
||||
function closePopup() {
|
||||
|
|
@ -83,32 +82,22 @@
|
|||
dispatch("close");
|
||||
}
|
||||
|
||||
function handleRoleToggle(roleId: string) {
|
||||
if (selectedRoles.has(roleId)) {
|
||||
selectedRoles.delete(roleId);
|
||||
} else {
|
||||
selectedRoles.add(roleId);
|
||||
}
|
||||
selectedRoles = selectedRoles; // Trigger reactivity
|
||||
}
|
||||
|
||||
function addUser() {
|
||||
if (discordUser) {
|
||||
let permission;
|
||||
|
||||
enum Permissions {
|
||||
"None" = 0,
|
||||
"View Only" = 1,
|
||||
"Administrator" = 2,
|
||||
"View and Administrator" = 3
|
||||
}
|
||||
|
||||
if(hasAdminPermission && hasViewPermission) {
|
||||
permission = Permissions["View and Administrator"];
|
||||
} else if(hasViewPermission) {
|
||||
permission = Permissions["View Only"];
|
||||
} else if(hasAdminPermission) {
|
||||
permission = Permissions.Administrator;
|
||||
} else {
|
||||
permission = Permissions.None;
|
||||
}
|
||||
|
||||
dispatch("addUser", {
|
||||
discordId: discordUser.discordId,
|
||||
discordUsername: discordUser.discordUsername,
|
||||
discordAvatarUrl: discordUser.discordAvatarUrl,
|
||||
permission
|
||||
roleIds: Array.from(selectedRoles)
|
||||
});
|
||||
|
||||
closePopup();
|
||||
|
|
@ -176,30 +165,27 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="permissions-section">
|
||||
<h4>Permissions</h4>
|
||||
<div class="roles-section">
|
||||
<h4>Tournament Roles</h4>
|
||||
|
||||
<div class="permission-option">
|
||||
<div class="permission-label">
|
||||
<label for="view-permission">View Permission</label>
|
||||
<p class="permission-description">Can view tournament data (generally players)</p>
|
||||
</div>
|
||||
<label class="toggle">
|
||||
<input type="checkbox" id="view-permission" bind:checked={hasViewPermission}>
|
||||
<span class="slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="permission-option">
|
||||
<div class="permission-label">
|
||||
<label for="admin-permission">Admin Permission</label>
|
||||
<p class="permission-description">Can manage tournament settings (generally staff))</p>
|
||||
</div>
|
||||
<label class="toggle">
|
||||
<input type="checkbox" id="admin-permission" bind:checked={hasAdminPermission}>
|
||||
<span class="slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
{#if tournamentRoles.length === 0}
|
||||
<p class="no-roles-message">No tournament roles available</p>
|
||||
{:else}
|
||||
{#each tournamentRoles as role (role.guid)}
|
||||
<div class="role-option">
|
||||
<div class="role-label">
|
||||
<label for="role-{role.guid}">{role.name}</label>
|
||||
</div>
|
||||
<input
|
||||
type="checkbox"
|
||||
id="role-{role.guid}"
|
||||
checked={selectedRoles.has(role.roleId)}
|
||||
on:change={() => handleRoleToggle(role.roleId)}
|
||||
class="role-checkbox"
|
||||
/>
|
||||
</div>
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
|
@ -210,7 +196,7 @@
|
|||
<button
|
||||
class="add-button"
|
||||
on:click={addUser}
|
||||
disabled={!userFound || (!hasViewPermission && !hasAdminPermission)}
|
||||
disabled={!userFound || selectedRoles.size === 0}
|
||||
>
|
||||
Add User
|
||||
</button>
|
||||
|
|
@ -381,19 +367,29 @@
|
|||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.permissions-section {
|
||||
.roles-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.permissions-section h4 {
|
||||
.roles-section h4 {
|
||||
margin: 0 0 0.5rem 0;
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.permission-option {
|
||||
.no-roles-message {
|
||||
margin: 0;
|
||||
font-size: 0.875rem;
|
||||
color: var(--text-secondary);
|
||||
text-align: center;
|
||||
padding: 1rem;
|
||||
background-color: var(--bg-tertiary);
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
|
||||
.role-option {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
|
@ -402,69 +398,22 @@
|
|||
border-radius: 0.5rem;
|
||||
}
|
||||
|
||||
.permission-label {
|
||||
.role-label {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.permission-label label {
|
||||
.role-label label {
|
||||
font-weight: 500;
|
||||
font-size: 0.9375rem;
|
||||
display: block;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.permission-description {
|
||||
margin: 0;
|
||||
font-size: 0.8125rem;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
/* Toggle styles (reusing from existing code) */
|
||||
.toggle {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 3rem;
|
||||
height: 1.5rem;
|
||||
color: grey;
|
||||
}
|
||||
|
||||
.toggle input {
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.slider {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: var(--bg-secondary);
|
||||
transition: .4s;
|
||||
border-radius: 1.5rem;
|
||||
}
|
||||
|
||||
.slider:before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
height: 1.125rem;
|
||||
.role-checkbox {
|
||||
width: 1.125rem;
|
||||
left: 0.1875rem;
|
||||
bottom: 0.1875rem;
|
||||
background-color: var(--text-secondary);
|
||||
transition: .4s;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
input:checked + .slider {
|
||||
background-color: var(--accent-color);
|
||||
}
|
||||
|
||||
input:checked + .slider:before {
|
||||
transform: translateX(1.5rem);
|
||||
background-color: white;
|
||||
height: 1.125rem;
|
||||
margin-left: 1rem;
|
||||
cursor: pointer;
|
||||
accent-color: var(--accent-color);
|
||||
}
|
||||
|
||||
.popup-actions {
|
||||
|
|
|
|||
557
src/lib/components/popups/AddOrModifyRole.svelte
Normal file
557
src/lib/components/popups/AddOrModifyRole.svelte
Normal file
|
|
@ -0,0 +1,557 @@
|
|||
<script lang="ts">
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import { type Role } from 'moons-ta-client'
|
||||
|
||||
interface InternalPermission {
|
||||
displayName: string,
|
||||
value: string,
|
||||
description: string
|
||||
}
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
export let isOpen: boolean = false;
|
||||
export let role: Role | null = null;
|
||||
export let tournamentRoles: Role[] = []
|
||||
let availablePermissions: InternalPermission[] = [
|
||||
{ displayName: "View Tournament In List", value: "tournament:view_tournament_in_list", description: "" },
|
||||
{ displayName: "Join Tournament", value: "tournament:join", description: "" },
|
||||
|
||||
{ displayName: "Add Authorized Users", value: "tournament:settings:add_authorized_users", description: "" },
|
||||
{ displayName: "Update Authorized User Roles", value: "tournament:settings:update_authorized_user_roles", description: "" },
|
||||
{ displayName: "Remove Authorized Users", value: "tournament:settings:remove_authorized_users", description: "" },
|
||||
{ displayName: "Get Authorized Users", value: "tournament:settings:get_authorized_users", description: "" },
|
||||
{ displayName: "Get Discord Info", value: "tournament:settings:get_discord_info", description: "" },
|
||||
|
||||
{ displayName: "Get Qualifier Scores", value: "tournament:qualifier:get_qualifier_scores", description: "" },
|
||||
{ displayName: "Submit Qualifier Scores", value: "tournament:qualifier:submit_qualifier_scores", description: "" },
|
||||
{ displayName: "See Hidden Qualifier Scores", value: "tournament:qualifier:see_hidden_qualifier_scores", description: "" },
|
||||
{ displayName: "Get Remaining Attempts", value: "tournament:qualifier:get_remaining_attempts", description: "" },
|
||||
{ displayName: "Refund Attempts", value: "tournament:qualifier:refund_attempts", description: "" },
|
||||
|
||||
{ displayName: "Return To Menu", value: "tournament:player:return_to_menu", description: "" },
|
||||
{ displayName: "Play Song", value: "tournament:player:play_song", description: "" },
|
||||
{ displayName: "Play With Stream Sync", value: "tournament:player:play_with_stream_sync", description: "" },
|
||||
{ displayName: "Modify Gameplay", value: "tournament:player:modify_gameplay", description: "" },
|
||||
{ displayName: "Load Song", value: "tournament:player:load_song", description: "" },
|
||||
|
||||
{ displayName: "Create Match", value: "tournament:match:create_match", description: "" },
|
||||
{ displayName: "Add User To Match", value: "tournament:match:add_user_to_match", description: "" },
|
||||
{ displayName: "Remove User From Match", value: "tournament:match:remove_user_from_match", description: "" },
|
||||
{ displayName: "Set Match Leader", value: "tournament:match:set_match_leader", description: "" },
|
||||
{ displayName: "Set Match Map", value: "tournament:match:set_match_map", description: "" },
|
||||
{ displayName: "Delete Match", value: "tournament:match:delete_match", description: "" },
|
||||
|
||||
{ displayName: "Create Qualifier", value: "tournament:qualifier:create", description: "" },
|
||||
{ displayName: "Set Qualifier Name", value: "tournament:qualifier:set_name", description: "" },
|
||||
{ displayName: "Set Qualifier Image", value: "tournament:qualifier:set_image", description: "" },
|
||||
{ displayName: "Set Qualifier Info Channel", value: "tournament:qualifier:set_info_channel", description: "" },
|
||||
{ displayName: "Set Qualifier Flags", value: "tournament:qualifier:set_flags", description: "" },
|
||||
{ displayName: "Set Qualifier Leaderboard Sort", value: "tournament:qualifier:set_leaderboard_sort", description: "" },
|
||||
{ displayName: "Add Qualifier Maps", value: "tournament:qualifier:add_maps", description: "" },
|
||||
{ displayName: "Update Qualifier Map", value: "tournament:qualifier:update_map", description: "" },
|
||||
{ displayName: "Remove Qualifier Map", value: "tournament:qualifier:remove_map", description: "" },
|
||||
{ displayName: "Delete Qualifier", value: "tournament:qualifier:delete", description: "" },
|
||||
|
||||
{ displayName: "Set Tournament Name", value: "tournament:settings:set_name", description: "" },
|
||||
{ displayName: "Set Tournament Image", value: "tournament:settings:set_image", description: "" },
|
||||
{ displayName: "Set Tournament Enable Teams", value: "tournament:settings:set_enable_teams", description: "" },
|
||||
{ displayName: "Set Tournament Enable Pools", value: "tournament:settings:set_enable_pools", description: "" },
|
||||
{ displayName: "Set Tournament Show Tournament Button", value: "tournament:settings:set_show_tournament_button", description: "" },
|
||||
{ displayName: "Set Tournament Show Qualifier Button", value: "tournament:settings:set_show_qualifier_button", description: "" },
|
||||
{ displayName: "Set Tournament Allow Unauthorized View", value: "tournament:settings:set_allow_unauthorized_view", description: "" },
|
||||
{ displayName: "Set Tournament Score Update Frequency", value: "tournament:settings:set_score_update_frequency", description: "" },
|
||||
{ displayName: "Set Tournament Banned Mods", value: "tournament:settings:set_banned_mods", description: "" },
|
||||
{ displayName: "Add Tournament Team", value: "tournament:settings:add_team", description: "" },
|
||||
{ displayName: "Set Tournament Team Name", value: "tournament:settings:set_team_name", description: "" },
|
||||
{ displayName: "Set Tournament Team Image", value: "tournament:settings:set_team_image", description: "" },
|
||||
{ displayName: "Remove Tournament Team", value: "tournament:settings:remove_team", description: "" },
|
||||
{ displayName: "Add Tournament Pool", value: "tournament:settings:add_pool", description: "" },
|
||||
{ displayName: "Set Tournament Pool Name", value: "tournament:settings:set_pool_name", description: "" },
|
||||
{ displayName: "Set Tournament Pool Image", value: "tournament:settings:set_pool_image", description: "" },
|
||||
{ displayName: "Add Tournament Pool Maps", value: "tournament:settings:add_pool_maps", description: "" },
|
||||
{ displayName: "Update Tournament Pool Maps", value: "tournament:settings:update_pool_maps", description: "" },
|
||||
{ displayName: "Remove Tournament Pool Maps", value: "tournament:settings:remove_pool_maps", description: "" },
|
||||
{ displayName: "Remove Tournament Pools", value: "tournament:settings:remove_pools", description: "" },
|
||||
{ displayName: "Add Tournament Role", value: "tournament:settings:add_role", description: "" },
|
||||
{ displayName: "Set Tournament Role Name", value: "tournament:settings:set_role_name", description: "" },
|
||||
{ displayName: "Set Tournament Role Permissions", value: "tournament:settings:set_role_permissions", description: "" },
|
||||
{ displayName: "Remove Tournament Role", value: "tournament:settings:remove_role", description: "" },
|
||||
{ displayName: "Delete Tournament", value: "tournament:settings:delete", description: "" },
|
||||
];
|
||||
|
||||
// Form states
|
||||
let roleName: string = "";
|
||||
let roleId: string = "";
|
||||
let selectedPermissions: Set<string> = new Set();
|
||||
let error: string | null = null;
|
||||
|
||||
// Reactive statement to initialize form when role or popup opens
|
||||
$: if (isOpen) {
|
||||
initializeForm();
|
||||
}
|
||||
|
||||
function initializeForm() {
|
||||
if (role) {
|
||||
// Editing existing role
|
||||
roleName = role.name;
|
||||
roleId = role.roleId;
|
||||
selectedPermissions = new Set(role.permissions);
|
||||
} else {
|
||||
// Creating new role
|
||||
roleName = "";
|
||||
selectedPermissions = new Set();
|
||||
}
|
||||
error = null;
|
||||
}
|
||||
|
||||
function resetForm() {
|
||||
roleName = "";
|
||||
roleId = "";
|
||||
selectedPermissions = new Set();
|
||||
error = null;
|
||||
}
|
||||
|
||||
function closePopup() {
|
||||
resetForm();
|
||||
isOpen = false;
|
||||
}
|
||||
|
||||
function handlePermissionToggle(permissionValue: string) {
|
||||
if (selectedPermissions.has(permissionValue)) {
|
||||
selectedPermissions.delete(permissionValue);
|
||||
} else {
|
||||
selectedPermissions.add(permissionValue);
|
||||
}
|
||||
selectedPermissions = selectedPermissions; // Trigger reactivity
|
||||
}
|
||||
|
||||
function selectAllPermissions() {
|
||||
selectedPermissions = new Set(availablePermissions.map(p => p.value));
|
||||
}
|
||||
|
||||
function deselectAllPermissions() {
|
||||
selectedPermissions = new Set();
|
||||
}
|
||||
|
||||
function validateForm(): boolean {
|
||||
if (roleName.trim() === "") {
|
||||
error = "Role name is required";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (selectedPermissions.size === 0) {
|
||||
error = "At least one permission must be selected";
|
||||
return false;
|
||||
}
|
||||
|
||||
error = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
function handleSave() {
|
||||
if (!validateForm()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const permissions = Array.from(selectedPermissions);
|
||||
|
||||
if (role) {
|
||||
// Modifying existing role
|
||||
dispatch("modifyRole", {
|
||||
roleId: role.roleId,
|
||||
name: roleName.trim(),
|
||||
permissions: permissions
|
||||
});
|
||||
} else {
|
||||
// Creating new role
|
||||
dispatch("createRole", {
|
||||
name: roleName.trim(),
|
||||
roleId: roleId,
|
||||
permissions: permissions
|
||||
});
|
||||
}
|
||||
|
||||
closePopup();
|
||||
}
|
||||
|
||||
function handleKeyDown(event: KeyboardEvent) {
|
||||
if (event.key === "Escape") {
|
||||
closePopup();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:window on:keydown={handleKeyDown} />
|
||||
|
||||
{#if isOpen}
|
||||
<div class="popup-overlay">
|
||||
<div class="popup-container">
|
||||
<div class="popup-header">
|
||||
<h3>{role ? 'Modify Role' : 'Create Role'}</h3>
|
||||
<button class="close-button" on:click={closePopup}>
|
||||
<span class="material-icons">close</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="popup-content">
|
||||
<div class="role-name-section">
|
||||
<label for="role-name">Role Name</label>
|
||||
<input
|
||||
type="text"
|
||||
id="role-name"
|
||||
bind:value={roleName}
|
||||
placeholder="Enter role name"
|
||||
class="role-name-input"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{#if !role}
|
||||
<div class="role-name-section">
|
||||
<label for="role-id">Role ID</label>
|
||||
<input
|
||||
type="text"
|
||||
id="role-id"
|
||||
bind:value={roleId}
|
||||
placeholder="Enter role id"
|
||||
class="role-name-input"
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if error}
|
||||
<p class="error-message">{error}</p>
|
||||
{/if}
|
||||
|
||||
<div class="permissions-section">
|
||||
<div class="permissions-header">
|
||||
<h4>Permissions ({selectedPermissions.size} of {availablePermissions && availablePermissions.length ? availablePermissions.length : 0} selected)</h4>
|
||||
<div class="permissions-actions">
|
||||
<button class="select-all-button" on:click={selectAllPermissions}>
|
||||
Select All
|
||||
</button>
|
||||
<button class="deselect-all-button" on:click={deselectAllPermissions}>
|
||||
Deselect All
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="permissions-grid">
|
||||
{#each availablePermissions as permission (permission.value)}
|
||||
<div class="permission-option">
|
||||
<div class="permission-label">
|
||||
<label for="permission-{permission.value}">
|
||||
{permission.displayName}
|
||||
</label>
|
||||
</div>
|
||||
<input
|
||||
type="checkbox"
|
||||
id="permission-{permission.value}"
|
||||
checked={selectedPermissions.has(permission.value)}
|
||||
on:change={() => handlePermissionToggle(permission.value)}
|
||||
class="permission-checkbox"
|
||||
/>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="popup-actions">
|
||||
<button class="cancel-button" on:click={closePopup}>Cancel</button>
|
||||
<button
|
||||
class="save-button"
|
||||
on:click={handleSave}
|
||||
disabled={roleName.trim() === "" || selectedPermissions.size === 0}
|
||||
>
|
||||
{role ? 'Save Changes' : 'Create Role'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.popup-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 1000;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.popup-container {
|
||||
background-color: var(--bg-primary);
|
||||
border-radius: 0.75rem;
|
||||
width: 100%;
|
||||
max-width: 56rem;
|
||||
max-height: 90vh;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.popup-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 1.5rem;
|
||||
border-bottom: 1px solid var(--bg-tertiary);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.popup-header h3 {
|
||||
margin: 0;
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.close-button {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--text-secondary);
|
||||
padding: 0.5rem;
|
||||
border-radius: 0.375rem;
|
||||
}
|
||||
|
||||
.close-button:hover {
|
||||
background-color: var(--bg-tertiary);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.popup-content {
|
||||
padding: 1.5rem;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.5rem;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.role-name-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.role-name-section label {
|
||||
font-weight: 500;
|
||||
font-size: 0.9375rem;
|
||||
}
|
||||
|
||||
.role-name-input {
|
||||
background-color: var(--bg-tertiary);
|
||||
border: none;
|
||||
border-radius: 0.375rem;
|
||||
padding: 0.625rem 0.75rem;
|
||||
color: var(--text-primary);
|
||||
font-size: 0.9375rem;
|
||||
}
|
||||
|
||||
.role-name-input:focus {
|
||||
outline: 2px solid var(--accent-color);
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: #ff5555;
|
||||
font-size: 0.875rem;
|
||||
margin: 0;
|
||||
padding: 0.5rem;
|
||||
background-color: rgba(255, 85, 85, 0.1);
|
||||
border-radius: 0.375rem;
|
||||
border: 1px solid rgba(255, 85, 85, 0.3);
|
||||
}
|
||||
|
||||
.permissions-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.permissions-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.permissions-header h4 {
|
||||
margin: 0;
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.permissions-actions {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.select-all-button,
|
||||
.deselect-all-button {
|
||||
background-color: var(--bg-tertiary);
|
||||
border: none;
|
||||
border-radius: 0.375rem;
|
||||
padding: 0.5rem 0.75rem;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
color: var(--text-secondary);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.select-all-button:hover {
|
||||
background-color: var(--accent-color);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.deselect-all-button:hover {
|
||||
background-color: #ff5555;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.permissions-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
||||
gap: 0.75rem;
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
padding: 0.5rem;
|
||||
border-radius: 0.5rem;
|
||||
background-color: var(--bg-secondary);
|
||||
}
|
||||
|
||||
.permission-option {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0.75rem;
|
||||
background-color: var(--bg-tertiary);
|
||||
border-radius: 0.5rem;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.permission-option:hover {
|
||||
background-color: var(--bg-primary);
|
||||
}
|
||||
|
||||
.permission-label {
|
||||
flex: 1;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.permission-label label {
|
||||
font-weight: 400;
|
||||
font-size: 0.875rem;
|
||||
cursor: pointer;
|
||||
word-wrap: break-word;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.permission-checkbox {
|
||||
width: 1.125rem;
|
||||
height: 1.125rem;
|
||||
cursor: pointer;
|
||||
accent-color: var(--accent-color);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.popup-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 1rem;
|
||||
padding: 1.25rem 1.5rem;
|
||||
border-top: 1px solid var(--bg-tertiary);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.cancel-button {
|
||||
background-color: var(--bg-tertiary);
|
||||
border: none;
|
||||
border-radius: 0.375rem;
|
||||
padding: 0.625rem 1.25rem;
|
||||
font-size: 0.9375rem;
|
||||
font-weight: 500;
|
||||
color: var(--text-secondary);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.cancel-button:hover {
|
||||
background-color: var(--bg-secondary);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.save-button {
|
||||
background-color: var(--accent-color);
|
||||
border: none;
|
||||
border-radius: 0.375rem;
|
||||
padding: 0.625rem 1.25rem;
|
||||
font-size: 0.9375rem;
|
||||
font-weight: 500;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.save-button:hover {
|
||||
background-color: var(--accent-hover);
|
||||
}
|
||||
|
||||
.save-button:disabled {
|
||||
background-color: var(--bg-tertiary);
|
||||
color: var(--text-secondary);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* Scrollbar styling */
|
||||
.permissions-grid::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
.permissions-grid::-webkit-scrollbar-track {
|
||||
background: var(--bg-tertiary);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.permissions-grid::-webkit-scrollbar-thumb {
|
||||
background: var(--text-secondary);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.permissions-grid::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--text-primary);
|
||||
}
|
||||
|
||||
/* Responsive design */
|
||||
@media (max-width: 768px) {
|
||||
.popup-container {
|
||||
margin: 0.5rem;
|
||||
max-width: calc(100% - 1rem);
|
||||
}
|
||||
|
||||
.permissions-grid {
|
||||
grid-template-columns: 1fr;
|
||||
max-height: 300px;
|
||||
}
|
||||
|
||||
.permissions-header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.permissions-actions {
|
||||
width: 100%;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
# Introduction
|
||||
TournamentAssistant is a piece of woftware which has a backend(TournamentAssistantServer), frontend(TournamentAssistantUI), and in-game plugin(TournamentAssistant). It is important to note before proceeding that TA uses a websocket for connections and DOES NOT have a RESTful API. The websocket uses protobuf, so the packets themseves are not readable by humans naturally. That is why the TA client is important. It includes the proto models and deserialises them.
|
||||
TournamentAssistant is a piece of software which has a backend(TournamentAssistantServer), frontend(TournamentAssistantUI), and in-game plugin(TournamentAssistant). It is important to note before proceeding that TA uses a websocket for connections and DOES NOT have a RESTful API. The websocket uses protobuf, so the packets themseves are not readable by humans naturally. That is why the TA client is important. It includes the proto models and deserialises them.
|
||||
|
||||
## Prerequisites
|
||||
You should probably get familiar with:
|
||||
- Serialisation
|
||||
- Serialisation - optional, but recommended
|
||||
- Typescript
|
||||
- Websocket connections
|
||||
|
||||
|
|
|
|||
46
src/lib/taDocs/3-client-addAuthorizedUser.md
Normal file
46
src/lib/taDocs/3-client-addAuthorizedUser.md
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
# The taClient.addAuthorizedUser() method
|
||||
This documents the `taClient.addAuthorizedUser()` method.
|
||||
|
||||
This method has input parameters. The function could easily be defined as:
|
||||
```ts
|
||||
async function addAuthorizedUser(tournamentId: string, discordId: string, roleIds: string[]): Promise<Response> {
|
||||
// backend logic
|
||||
}
|
||||
```
|
||||
## General usage:
|
||||
```ts
|
||||
const response: Response = taClient.addAuthorizedUser(tournamentGuid, userDiscordId, ['roleId1', 'roleId2'])
|
||||
```
|
||||
|
||||
The `response` variable from above will actually be a 'mashup' of two types. Firstly, the standard `Response` type. Secondly the `Response_AddAuthorizedUser`. Find the custom response object below.
|
||||
|
||||
- For `Response` please find documentation at [Modals -> Response](/documentation#Modals/4-Response)
|
||||
- For `Response_AddAuthorizedUser` please find documentation at [Modals -> Response_AddAuthorizedUser](/documentation#Modals/4-Response_AddAuthorizedUser).
|
||||
|
||||
```js
|
||||
{
|
||||
details: {
|
||||
addAuthorizedUser: Response_AddAuthorizedUser,
|
||||
oneofKind: 'addAuthorizedUser'
|
||||
},
|
||||
respondingToPacketId: 'packet-guid-uuidv4',
|
||||
type: Response_ResponseType
|
||||
}
|
||||
```
|
||||
|
||||
<div class="info-box">
|
||||
<span class="material-icons">info</span>
|
||||
<div>
|
||||
<strong>Note:</strong> The method uses role.roleId, not role.guid.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
```ts
|
||||
ts example here
|
||||
```
|
||||
<div class="info-box">
|
||||
<span class="material-icons">info</span>
|
||||
<div>
|
||||
<strong>Example:</strong> A direct example is not available, as this function is not used in ShyyTAUI.
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -10,6 +10,7 @@ The table below shows all of the functions of the taClient. From this point onwa
|
|||
|addServer()|Add a CoreServer. You will probably never use this.|True|
|
||||
|addTournamentPool()|Add a map pool to a tournament. This is the way a map pool can be created.|True|
|
||||
|addTournamentPoolMaps()|Add maps to an already existing map pool.|True|
|
||||
|addTournamentRole()|Add a role to a tournament.|True|
|
||||
|addTournamentTeam()|Add a team to a tournament. This will create a new team|True|
|
||||
|addUserToMatch()|Add a user to a match. This is how a coordinator joins a match.|True|
|
||||
|connect()|Connect to a TournamentAssistant Core Server. This is the main server, not the tournament.|True|
|
||||
|
|
@ -42,6 +43,7 @@ The table below shows all of the functions of the taClient. From this point onwa
|
|||
|removeQualifierMap()|Remove a qualifier map from an already existing qualifier.|True|
|
||||
|removeTournamentPool()|Remove a map pool from a tournament.|True|
|
||||
|removeTournamentPoolMap()|Remove a map from an already existing map pool.|True|
|
||||
|removeTournamentRole()|Remove a role from a tournament.|True|
|
||||
|removeTournamentTeam()|Remove a team from a tournament.|True|
|
||||
|removeUserFromMatch()|Remove a user from a match.|True|
|
||||
|returnToMenu()|Return the speciied players to the menu.|False|
|
||||
|
|
|
|||
|
|
@ -3,16 +3,40 @@ The `taClient`(client for short) is how most of the actions will be carried out.
|
|||
|
||||
The `stateManager` is the way you would fetch tournaments, get matches, or do anything related to getting specific data that does not require a database query. It is best if you are familiar with what methods it has and how you can use them.
|
||||
|
||||
For the ease of this explanation let me provide an example:
|
||||
## Standard Response Object
|
||||
There are a couple of important notes for client responses. Since most client responses are async, they will return a promise. This will be shown as `Promise<Response>` in IntelliSense. In reality, what happens under the hood is that ProtoBuf sends back a packet of the form:
|
||||
```ts
|
||||
{
|
||||
details: {
|
||||
packetType: data,
|
||||
oneofKind: 'packetType'
|
||||
},
|
||||
respondingToPacketId: 'packet-guid-uuidv4',
|
||||
type: Response_ResponseType
|
||||
}
|
||||
```
|
||||
For the definition of the `Response_ResponseType` look at the [Modals -> Response_ResponseType](/documentation#Modals/4-Response_ResponseType)
|
||||
|
||||
|
||||
<div class="warning-box">
|
||||
<span class="material-icons">question_mark</span>
|
||||
<div>
|
||||
<strong>Why is this important?</strong> TypeScript <strong>DOES NOT</strong> recognise these `packetType` objects, hence when dealing with responses we often use `as any` to get rid of type errors.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Let me provide an example of how the client is used in ShyyTAUI:
|
||||
```ts
|
||||
onMount(async() => {
|
||||
// Check if the user is logged in
|
||||
if ($authTokenStore) {
|
||||
// Using the client listen to events that will not be stored in the state, such as scores and song finish events
|
||||
// Using the client listen for events that will not be stored in the state,
|
||||
// such as scores and song finish events
|
||||
client.on('realtimeScore', handleRealtimeScoreUpdate);
|
||||
client.on('songFinished', handleSongFinished);
|
||||
} else {
|
||||
// If the user is not authenticated, they will be thrown to the discord authentication page
|
||||
// If the user is not authenticated,
|
||||
// they will be thrown to the discord authentication page
|
||||
window.location.href = "/discordAuth"
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -13,6 +13,20 @@ Some more normal text...
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="warning-box">
|
||||
<span class="material-icons">question_mark</span>
|
||||
<div>
|
||||
<strong>Will the stateManager be out-of-date?</strong> The stateManager automatically updates its state when a packet is received or sent, so no, you do not have to worry about out-of-date information being stored.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="warning-box">
|
||||
<span class="material-icons">warning</span>
|
||||
<div>
|
||||
<strong>Note:</strong> This will be explained further in the 'State Manager' section, along with further behaviour descriptors. (most info will even be repeated)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
## Support
|
||||
|
||||
If you have any issues, please contact us through Discord or email
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
let isAuthenticated = false;
|
||||
let profileDropdownOpen = false;
|
||||
let mobileMenuOpen = false;
|
||||
let userProfile: { name: string; avatar?: string } | null = null;
|
||||
|
||||
onMount(() => {
|
||||
|
|
@ -25,6 +26,10 @@
|
|||
profileDropdownOpen = !profileDropdownOpen;
|
||||
}
|
||||
|
||||
function toggleMobileMenu() {
|
||||
mobileMenuOpen = !mobileMenuOpen;
|
||||
}
|
||||
|
||||
function logout() {
|
||||
authTokenStore.set(null);
|
||||
discordDataStore.set(null);
|
||||
|
|
@ -32,14 +37,27 @@
|
|||
isAuthenticated = false;
|
||||
userProfile = null;
|
||||
profileDropdownOpen = false;
|
||||
mobileMenuOpen = false;
|
||||
goto('/');
|
||||
}
|
||||
|
||||
function handleClickOutside(event: MouseEvent) {
|
||||
const dropdown = document.querySelector('.profile-container');
|
||||
const mobileMenu = document.querySelector('.mobile-menu');
|
||||
const hamburger = document.querySelector('.hamburger-button');
|
||||
|
||||
if (dropdown && !dropdown.contains(event.target as Node) && profileDropdownOpen) {
|
||||
profileDropdownOpen = false;
|
||||
}
|
||||
|
||||
if (mobileMenu && !mobileMenu.contains(event.target as Node) &&
|
||||
hamburger && !hamburger.contains(event.target as Node) && mobileMenuOpen) {
|
||||
mobileMenuOpen = false;
|
||||
}
|
||||
}
|
||||
|
||||
function handleNavClick() {
|
||||
mobileMenuOpen = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
@ -52,7 +70,8 @@
|
|||
<img class="logo-svg" src="/assets/LogoTextC_DevW.svg" alt="">
|
||||
</div>
|
||||
|
||||
<nav class="navbar-section nav-links">
|
||||
<!-- Desktop Navigation -->
|
||||
<nav class="navbar-section nav-links desktop-nav">
|
||||
<a href="/" class:active={$page.url.pathname === '/'}>
|
||||
<span>Home</span>
|
||||
<div class="hover-indicator"></div>
|
||||
|
|
@ -67,7 +86,8 @@
|
|||
</a>
|
||||
</nav>
|
||||
|
||||
<div class="navbar-section profile-container">
|
||||
<!-- Desktop Profile -->
|
||||
<div class="navbar-section profile-container desktop-profile">
|
||||
<button class="profile-button" on:click={toggleProfileDropdown}>
|
||||
{#if isAuthenticated && userProfile?.avatar}
|
||||
<img src={userProfile.avatar} alt="Profile" class="profile-image" />
|
||||
|
|
@ -80,7 +100,7 @@
|
|||
{#if profileDropdownOpen}
|
||||
<div class="profile-dropdown" transition:slide={{duration: 200}}>
|
||||
{#if isAuthenticated}
|
||||
<a href="/authTokens" class="dropdown-item">
|
||||
<a href="/authTokens" class="dropdown-item" on:click={handleNavClick}>
|
||||
<span class="material-icons">vpn_key</span>
|
||||
<span>Manage Auth (MAT)</span>
|
||||
</a>
|
||||
|
|
@ -90,7 +110,7 @@
|
|||
<span>Logout</span>
|
||||
</button>
|
||||
{:else}
|
||||
<a href="/discordAuth" class="dropdown-item">
|
||||
<a href="/discordAuth" class="dropdown-item" on:click={handleNavClick}>
|
||||
<span class="material-icons">login</span>
|
||||
<span>Login</span>
|
||||
</a>
|
||||
|
|
@ -98,7 +118,55 @@
|
|||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Mobile Hamburger Menu -->
|
||||
<button class="hamburger-button mobile-only" on:click={toggleMobileMenu}>
|
||||
<span class="material-icons">{mobileMenuOpen ? 'close' : 'menu'}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Mobile Menu -->
|
||||
{#if mobileMenuOpen}
|
||||
<div class="mobile-menu" transition:slide={{duration: 300}}>
|
||||
<nav class="mobile-nav-links">
|
||||
<a href="/" class:active={$page.url.pathname === '/'} on:click={handleNavClick}>
|
||||
<span class="material-icons">home</span>
|
||||
<span>Home</span>
|
||||
</a>
|
||||
<a href="/tournaments" class:active={$page.url.pathname === '/tournaments'} on:click={handleNavClick}>
|
||||
<span class="material-icons">sports_esports</span>
|
||||
<span>View Tournaments</span>
|
||||
</a>
|
||||
<a href="/authTokens" class:active={$page.url.pathname === '/authTokens'} on:click={handleNavClick}>
|
||||
<span class="material-icons">vpn_key</span>
|
||||
<span>My Tokens</span>
|
||||
</a>
|
||||
</nav>
|
||||
|
||||
<div class="mobile-profile-section">
|
||||
{#if isAuthenticated}
|
||||
<div class="mobile-user-info">
|
||||
{#if userProfile?.avatar}
|
||||
<img src={userProfile.avatar} alt="Profile" class="mobile-profile-image" />
|
||||
{:else}
|
||||
<span class="material-icons">account_circle</span>
|
||||
{/if}
|
||||
<span class="mobile-user-name">{userProfile?.name}</span>
|
||||
</div>
|
||||
<div class="mobile-profile-divider"></div>
|
||||
<button class="mobile-logout-button" on:click={logout}>
|
||||
<span class="material-icons">exit_to_app</span>
|
||||
<span>Logout</span>
|
||||
</button>
|
||||
{:else}
|
||||
<a href="/discordAuth" class="mobile-login-button" on:click={handleNavClick}>
|
||||
<span class="material-icons">login</span>
|
||||
<span>Login</span>
|
||||
</a>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</header>
|
||||
|
||||
<main>
|
||||
|
|
@ -195,7 +263,6 @@
|
|||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
height: var(--navbar-height);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
|
|
@ -203,7 +270,7 @@
|
|||
display: grid;
|
||||
grid-template-columns: 1fr auto 1fr;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
height: var(--navbar-height);
|
||||
padding: 0 1.5rem;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
|
|
@ -217,17 +284,16 @@
|
|||
|
||||
.logo-section {
|
||||
justify-content: flex-start;
|
||||
right: 5rem;
|
||||
padding-right: 10rem;
|
||||
}
|
||||
|
||||
.logo-svg {
|
||||
height: 40px;
|
||||
width: auto;
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
/* Navigation links - centered */
|
||||
.nav-links {
|
||||
/* Desktop Navigation */
|
||||
.desktop-nav {
|
||||
justify-content: center;
|
||||
gap: 2rem;
|
||||
height: 100%;
|
||||
|
|
@ -277,8 +343,8 @@
|
|||
background: linear-gradient(90deg, transparent, var(--accent-hover), transparent);
|
||||
}
|
||||
|
||||
/* Profile section */
|
||||
.profile-container {
|
||||
/* Desktop Profile */
|
||||
.desktop-profile {
|
||||
position: relative;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
|
@ -364,6 +430,132 @@
|
|||
margin: 0.5rem 0;
|
||||
}
|
||||
|
||||
/* Mobile-specific styles */
|
||||
.mobile-only {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 769px) {
|
||||
.mobile-only {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
.hamburger-button {
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--text-primary);
|
||||
cursor: pointer;
|
||||
padding: 0.5rem;
|
||||
border-radius: 0.375rem;
|
||||
transition: background-color 0.2s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.hamburger-button:hover {
|
||||
background-color: var(--bg-tertiary);
|
||||
}
|
||||
|
||||
.mobile-menu {
|
||||
background-color: var(--bg-secondary);
|
||||
border-top: 1px solid var(--border-color);
|
||||
padding: 1rem 0;
|
||||
}
|
||||
|
||||
.mobile-nav-links {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 0 1.5rem;
|
||||
}
|
||||
|
||||
.mobile-nav-links a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
padding: 1rem 0;
|
||||
color: var(--text-secondary);
|
||||
text-decoration: none;
|
||||
font-weight: 500;
|
||||
transition: color 0.2s ease;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.mobile-nav-links a:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.mobile-nav-links a:hover,
|
||||
.mobile-nav-links a.active {
|
||||
color: var(--accent-color);
|
||||
}
|
||||
|
||||
.mobile-nav-links .material-icons {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.mobile-profile-section {
|
||||
margin-top: 1rem;
|
||||
padding: 1rem 1.5rem 0;
|
||||
border-top: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.mobile-user-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.mobile-profile-image {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
border: 2px solid var(--accent-color);
|
||||
}
|
||||
|
||||
.mobile-user-name {
|
||||
color: var(--text-primary);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.mobile-profile-divider {
|
||||
height: 1px;
|
||||
background-color: var(--border-color);
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.mobile-logout-button,
|
||||
.mobile-login-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
padding: 0.75rem 0;
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--danger-color);
|
||||
text-decoration: none;
|
||||
font-size: 1rem;
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
transition: color 0.2s ease;
|
||||
}
|
||||
|
||||
.mobile-login-button {
|
||||
color: var(--accent-color);
|
||||
}
|
||||
|
||||
.mobile-logout-button:hover {
|
||||
color: var(--danger-hover);
|
||||
}
|
||||
|
||||
.mobile-login-button:hover {
|
||||
color: var(--accent-hover);
|
||||
}
|
||||
|
||||
/* Main content */
|
||||
main {
|
||||
flex: 1;
|
||||
|
|
@ -371,9 +563,10 @@
|
|||
}
|
||||
|
||||
.content-container {
|
||||
max-width: 1200px;
|
||||
max-width: 1250px;
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
overflow-y: visible;
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
|
|
@ -455,60 +648,84 @@
|
|||
|
||||
/* Media queries */
|
||||
@media (max-width: 768px) {
|
||||
.navbar {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
padding: 1rem;
|
||||
height: auto;
|
||||
/* Hide desktop navigation and profile */
|
||||
.desktop-nav,
|
||||
.desktop-profile {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.navbar-section {
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
/* Show mobile hamburger */
|
||||
.mobile-only {
|
||||
display: flex !important;
|
||||
}
|
||||
|
||||
/* Adjust navbar layout for mobile */
|
||||
.navbar {
|
||||
grid-template-columns: auto 1fr auto;
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
.logo-section {
|
||||
padding-right: 0;
|
||||
justify-content: flex-start;
|
||||
overflow: hidden;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.nav-links {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
gap: 1rem;
|
||||
.logo-svg {
|
||||
height: 32px;
|
||||
max-width: calc(100vw - 8rem);
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.nav-links a {
|
||||
height: auto;
|
||||
padding: 0.5rem 0.75rem;
|
||||
.hamburger-button {
|
||||
flex-shrink: 0;
|
||||
justify-self: end;
|
||||
}
|
||||
|
||||
.nav-links a .hover-indicator {
|
||||
bottom: -4px;
|
||||
/* Additional breakpoint for very narrow screens like folded phones */
|
||||
@media (max-width: 480px) {
|
||||
.logo-svg {
|
||||
height: 28px;
|
||||
max-width: calc(100vw - 6rem);
|
||||
}
|
||||
|
||||
.navbar {
|
||||
padding: 0 0.75rem;
|
||||
}
|
||||
}
|
||||
|
||||
.profile-container {
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.profile-dropdown {
|
||||
position: absolute;
|
||||
right: auto;
|
||||
@media (max-width: 360px) {
|
||||
.logo-svg {
|
||||
height: 24px;
|
||||
max-width: calc(100vw - 5rem);
|
||||
}
|
||||
|
||||
.navbar {
|
||||
padding: 0 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* Footer adjustments */
|
||||
.footer-content {
|
||||
grid-template-columns: 1fr;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.footer-links, .contact-link {
|
||||
.footer-links {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.contact-link {
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 769px) and (max-width: 1024px) {
|
||||
.navbar {
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
.footer-content {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ export async function GET({ params }) {
|
|||
headers: {
|
||||
'Content-Type': 'text/markdown; charset=utf-8',
|
||||
'Cache-Control': 'max-age=600', // Cache for 10 minutes
|
||||
'Access-Control-Allow-Origin': '*' // Add CORS if needed
|
||||
'Access-Control-Allow-Origin': '*' // Add CORS
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -17,145 +17,149 @@
|
|||
category: "Getting Started",
|
||||
icon: "rocket_launch",
|
||||
items: [
|
||||
{ title: "Introduction", file: "1-intro", order: 1 },
|
||||
{ title: "Installation", file: "1-installation", order: 2 },
|
||||
{ title: "Familiarisation, States", file: "1-fam-states-intro", order: 3 },
|
||||
{ title: "How TA Works, Info", file: "1-taInfo", order: 4 },
|
||||
{ title: "Introduction", file: "1-intro" },
|
||||
{ title: "Installation", file: "1-installation" },
|
||||
{ title: "Familiarisation, States", file: "1-fam-states-intro" },
|
||||
{ title: "How TA Works, Info", file: "1-taInfo" },
|
||||
]
|
||||
},
|
||||
{
|
||||
category: "State Manager",
|
||||
icon: "account_tree",
|
||||
items: [
|
||||
{ title: "Introduction to the StateManager", file: "2-stateManager-intro", order: 1 },
|
||||
{ title: "All Functions", file: "2-stateManager-allFunctions", order: 2 },
|
||||
{ title: "Emit", file: "2-stateManager-emit", order: 3 },
|
||||
{ title: "GetKnownServers", file: "2-stateManager-getKnownServers", order: 4 },
|
||||
{ title: "GetMatch", file: "2-stateManager-getMatch", order: 6 },
|
||||
{ title: "GetMatches", file: "2-stateManager-getMatches", order: 7 },
|
||||
{ title: "GetQualifier", file: "2-stateManager-getQualifier", order: 7 },
|
||||
{ title: "GetQualifiers", file: "2-stateManager-getQualifiers", order: 8 },
|
||||
{ title: "GetSelfGuid", file: "2-stateManager-getSelfGuid", order: 9 },
|
||||
{ title: "GetTournament", file: "2-stateManager-getTournament", order: 10 },
|
||||
{ title: "GetTournaments", file: "2-stateManager-getTournaments", order: 11 },
|
||||
{ title: "GetUser", file: "2-stateManager-getUser", order: 12 },
|
||||
{ title: "GetUsers", file: "2-stateManager-getUsers", order: 13 },
|
||||
{ title: "HandlePacket", file: "2-stateManager-handlePacket", order: 14 },
|
||||
{ title: "On", file: "2-stateManager-on", order: 15 },
|
||||
{ title: "Once", file: "2-stateManager-once", order: 16 },
|
||||
{ title: "RemoveListener", file: "2-stateManager-removeListener", order: 17 },
|
||||
{ title: "Introduction to the StateManager", file: "2-stateManager-intro" },
|
||||
{ title: "All Functions", file: "2-stateManager-allFunctions" },
|
||||
{ title: "Emit", file: "2-stateManager-emit" },
|
||||
{ title: "GetKnownServers", file: "2-stateManager-getKnownServers" },
|
||||
{ title: "GetMatch", file: "2-stateManager-getMatch" },
|
||||
{ title: "GetMatches", file: "2-stateManager-getMatches" },
|
||||
{ title: "GetQualifier", file: "2-stateManager-getQualifier" },
|
||||
{ title: "GetQualifiers", file: "2-stateManager-getQualifiers" },
|
||||
{ title: "GetSelfGuid", file: "2-stateManager-getSelfGuid" },
|
||||
{ title: "GetTournament", file: "2-stateManager-getTournament" },
|
||||
{ title: "GetTournaments", file: "2-stateManager-getTournaments" },
|
||||
{ title: "GetUser", file: "2-stateManager-getUser" },
|
||||
{ title: "GetUsers", file: "2-stateManager-getUsers" },
|
||||
{ title: "HandlePacket", file: "2-stateManager-handlePacket" },
|
||||
{ title: "On", file: "2-stateManager-on" },
|
||||
{ title: "Once", file: "2-stateManager-once" },
|
||||
{ title: "RemoveListener", file: "2-stateManager-removeListener" },
|
||||
]
|
||||
},
|
||||
{
|
||||
category: "Client",
|
||||
icon: "api",
|
||||
items: [
|
||||
{ title: "Introduction to the Client", file: "3-client-intro", order: 1 },
|
||||
{ title: "All Functions", file: "3-client-allFunctions", order: 2 },
|
||||
{ title: "AddAuthorizedUser", file: "3-client-addAuthorizedUser", order: 3 },
|
||||
{ title: "AddQualifierMaps", file: "3-client-addQualifierMaps", order: 4 },
|
||||
{ title: "AddServer", file: "3-client-addServer", order: 5 },
|
||||
{ title: "AddTournamentPool", file: "3-client-addTournamentPool", order: 6 },
|
||||
{ title: "AddTournamentPoolMaps", file: "3-client-addTournamentPoolMaps", order: 7 },
|
||||
{ title: "AddTournamentTeam", file: "3-client-addTournamentTeam", order: 8 },
|
||||
{ title: "AddUserToMatch", file: "3-client-addUserToMatch", order: 9 },
|
||||
{ title: "Connect", file: "3-client-connect", order: 10 },
|
||||
{ title: "CreateMatch", file: "3-client-createMatch", order: 11 },
|
||||
{ title: "CreateQualifierEvent", file: "3-client-createQualifierEvent", order: 12 },
|
||||
{ title: "CreateTournament", file: "3-client-createTournament", order: 13 },
|
||||
{ title: "DelayTestFinished", file: "3-client-delayTestFinished", order: 14 },
|
||||
{ title: "DeleteMatch", file: "3-client-deleteMatch", order: 15 },
|
||||
{ title: "DeleteQualifierEvent", file: "3-client-deleteQualifierEvent", order: 16 },
|
||||
{ title: "DeleteTournament", file: "3-client-deleteTournament", order: 17 },
|
||||
{ title: "Disconnect", file: "3-client-disconnect", order: 18 },
|
||||
{ title: "Emit", file: "3-client-emit", order: 19 },
|
||||
{ title: "FlipColors", file: "3-client-flipColors", order: 20 },
|
||||
{ title: "FlipHands", file: "3-client-flipHands", order: 21 },
|
||||
{ title: "GenerateBotToken", file: "3-client-generateBotToken", order: 22 },
|
||||
{ title: "GetAuthorizedUsers", file: "3-client-getAuthorizedUsers", order: 23 },
|
||||
{ title: "GetBotTokensForUser", file: "3-client-getBotTokensForUser", order: 24 },
|
||||
{ title: "GetDiscordInfo", file: "3-client-getDiscordInfo", order: 25 },
|
||||
{ title: "GetLeaderboard", file: "3-client-getLeaderboard", order: 26 },
|
||||
{ title: "IsConnected", file: "3-client-isConnected", order: 27 },
|
||||
{ title: "IsConnecting", file: "3-client-isConnecting", order: 28 },
|
||||
{ title: "JoinTournament", file: "3-client-joinTournament", order: 29 },
|
||||
{ title: "LoadImage", file: "3-client-loadImage", order: 30 },
|
||||
{ title: "LoadSong", file: "3-client-loadSong", order: 31 },
|
||||
{ title: "On", file: "3-client-on", order: 32 },
|
||||
{ title: "Once", file: "3-client-once", order: 33 },
|
||||
{ title: "PlaySong", file: "3-client-playSong", order: 34 },
|
||||
{ title: "RemoveAuthorizedUser", file: "3-client-removeAuthorizedUser", order: 35 },
|
||||
{ title: "RemoveListener", file: "3-client-removeListener", order: 36 },
|
||||
{ title: "RemoveQualifierMap", file: "3-client-removeQualifierMap", order: 37 },
|
||||
{ title: "RemoveTournamentPool", file: "3-client-removeTournamentPool", order: 38 },
|
||||
{ title: "RemoveTournamentPoolMap", file: "3-client-removeTournamentPoolMap", order: 39 },
|
||||
{ title: "RemoveTournamentTeam", file: "3-client-removeTournamentTeam", order: 40 },
|
||||
{ title: "RemoveUserFromMatch", file: "3-client-removeUserFromMatch", order: 41 },
|
||||
{ title: "ReturnToMenu", file: "3-client-returnToMenu", order: 42 },
|
||||
{ title: "RevokeBotToken", file: "3-client-revokeBotToken", order: 43 },
|
||||
{ title: "SendResponse", file: "3-client-sendResponse", order: 44 },
|
||||
{ title: "SetAuthToken", file: "3-client-setAuthToken", order: 45 },
|
||||
{ title: "SetMatchLeader", file: "3-client-setMatchLeader", order: 46 },
|
||||
{ title: "SetMatchMap", file: "3-client-setMatchMap", order: 47 },
|
||||
{ title: "SetQualifierFlags", file: "3-client-setQualifierFlags", order: 48 },
|
||||
{ title: "SetQualifierImage", file: "3-client-setQualifierImage", order: 49 },
|
||||
{ title: "SetQualifierInfoChannel", file: "3-client-setQualifierInfoChannel", order: 50 },
|
||||
{ title: "SetQualifierLeaderboardSort", file: "3-client-setQualifierLeaderboardSort", order: 51 },
|
||||
{ title: "SetQualifierName", file: "3-client-setQualifierName", order: 52 },
|
||||
{ title: "SetTournamentAllowUnauthorizedView", file: "3-client-setTournamentAllowUnauthorizedView", order: 53 },
|
||||
{ title: "SetTournamentBannedMods", file: "3-client-setTournamentBannedMods", order: 54 },
|
||||
{ title: "SetTournamentEnablePools", file: "3-client-setTournamentEnablePools", order: 55 },
|
||||
{ title: "SetTournamentEnableTeams", file: "3-client-setTournamentEnableTeams", order: 56 },
|
||||
{ title: "SetTournamentImage", file: "3-client-setTournamentImage", order: 57 },
|
||||
{ title: "SetTournamentName", file: "3-client-setTournamentName", order: 58 },
|
||||
{ title: "SetTournamentPoolName", file: "3-client-setTournamentPoolName", order: 59 },
|
||||
{ title: "SetTournamentScoreUpdateFrequency", file: "3-client-setTournamentScoreUpdateFrequency", order: 60 },
|
||||
{ title: "SetTournamentShowQualifierButton", file: "3-client-setTournamentShowQualifierButton", order: 61 },
|
||||
{ title: "SetTournamentShowTournamentButton", file: "3-client-setTournamentShowTournamentButton", order: 62 },
|
||||
{ title: "SetTournamentTeamImage", file: "3-client-setTournamentTeamImage", order: 63 },
|
||||
{ title: "SetTournamentTeamName", file: "3-client-setTournamentTeamName", order: 64 },
|
||||
{ title: "ShowLoadedImage", file: "3-client-showLoadedImage", order: 65 },
|
||||
{ title: "ShowPrompt", file: "3-client-showPrompt", order: 66 },
|
||||
{ title: "StateManager", file: "3-client-stateManager", order: 67 },
|
||||
{ title: "UpdateQualifierMap", file: "3-client-updateQualifierMap", order: 68 },
|
||||
{ title: "UpdateTournamentPoolMap", file: "3-client-updateTournamentPoolMap", order: 69 },
|
||||
{ title: "UpdateUser", file: "3-client-updateUser", order: 70 }
|
||||
{ title: "Introduction to the Client", file: "3-client-intro" },
|
||||
{ title: "All Functions", file: "3-client-allFunctions" },
|
||||
{ title: "AddAuthorizedUser", file: "3-client-addAuthorizedUser" },
|
||||
{ title: "AddQualifierMaps", file: "3-client-addQualifierMaps" },
|
||||
{ title: "AddServer", file: "3-client-addServer" },
|
||||
{ title: "AddTournamentPool", file: "3-client-addTournamentPool" },
|
||||
{ title: "AddTournamentPoolMaps", file: "3-client-addTournamentPoolMaps" },
|
||||
{ title: "AddTournamentRole", file: "3-client-addTournamentRole" },
|
||||
{ title: "AddTournamentTeam", file: "3-client-addTournamentTeam" },
|
||||
{ title: "AddUserToMatch", file: "3-client-addUserToMatch" },
|
||||
{ title: "Connect", file: "3-client-connect" },
|
||||
{ title: "CreateMatch", file: "3-client-createMatch" },
|
||||
{ title: "CreateQualifierEvent", file: "3-client-createQualifierEvent" },
|
||||
{ title: "CreateTournament", file: "3-client-createTournament" },
|
||||
{ title: "DelayTestFinished", file: "3-client-delayTestFinished" },
|
||||
{ title: "DeleteMatch", file: "3-client-deleteMatch" },
|
||||
{ title: "DeleteQualifierEvent", file: "3-client-deleteQualifierEvent" },
|
||||
{ title: "DeleteTournament", file: "3-client-deleteTournament" },
|
||||
{ title: "Disconnect", file: "3-client-disconnect" },
|
||||
{ title: "Emit", file: "3-client-emit" },
|
||||
{ title: "FlipColors", file: "3-client-flipColors" },
|
||||
{ title: "FlipHands", file: "3-client-flipHands" },
|
||||
{ title: "GenerateBotToken", file: "3-client-generateBotToken" },
|
||||
{ title: "GetAuthorizedUsers", file: "3-client-getAuthorizedUsers" },
|
||||
{ title: "GetBotTokensForUser", file: "3-client-getBotTokensForUser" },
|
||||
{ title: "GetDiscordInfo", file: "3-client-getDiscordInfo" },
|
||||
{ title: "GetLeaderboard", file: "3-client-getLeaderboard" },
|
||||
{ title: "IsConnected", file: "3-client-isConnected" },
|
||||
{ title: "IsConnecting", file: "3-client-isConnecting" },
|
||||
{ title: "JoinTournament", file: "3-client-joinTournament" },
|
||||
{ title: "LoadImage", file: "3-client-loadImage" },
|
||||
{ title: "LoadSong", file: "3-client-loadSong" },
|
||||
{ title: "On", file: "3-client-on" },
|
||||
{ title: "Once", file: "3-client-once" },
|
||||
{ title: "PlaySong", file: "3-client-playSong" },
|
||||
{ title: "RemoveAuthorizedUser", file: "3-client-removeAuthorizedUser" },
|
||||
{ title: "RemoveListener", file: "3-client-removeListener" },
|
||||
{ title: "RemoveQualifierMap", file: "3-client-removeQualifierMap" },
|
||||
{ title: "RemoveTournamentPool", file: "3-client-removeTournamentPool" },
|
||||
{ title: "RemoveTournamentPoolMap", file: "3-client-removeTournamentPoolMap" },
|
||||
{ title: "RemoveTournamentRole", file: "3-client-removeTournamentRole" },
|
||||
{ title: "RemoveTournamentTeam", file: "3-client-removeTournamentTeam" },
|
||||
{ title: "RemoveUserFromMatch", file: "3-client-removeUserFromMatch" },
|
||||
{ title: "ReturnToMenu", file: "3-client-returnToMenu" },
|
||||
{ title: "RevokeBotToken", file: "3-client-revokeBotToken" },
|
||||
{ title: "SendResponse", file: "3-client-sendResponse" },
|
||||
{ title: "SetAuthToken", file: "3-client-setAuthToken" },
|
||||
{ title: "SetMatchLeader", file: "3-client-setMatchLeader" },
|
||||
{ title: "SetMatchMap", file: "3-client-setMatchMap" },
|
||||
{ title: "SetQualifierFlags", file: "3-client-setQualifierFlags" },
|
||||
{ title: "SetQualifierImage", file: "3-client-setQualifierImage" },
|
||||
{ title: "SetQualifierInfoChannel", file: "3-client-setQualifierInfoChannel" },
|
||||
{ title: "SetQualifierLeaderboardSort", file: "3-client-setQualifierLeaderboardSort" },
|
||||
{ title: "SetQualifierName", file: "3-client-setQualifierName" },
|
||||
{ title: "SetTournamentAllowUnauthorizedView", file: "3-client-setTournamentAllowUnauthorizedView" },
|
||||
{ title: "SetTournamentBannedMods", file: "3-client-setTournamentBannedMods" },
|
||||
{ title: "SetTournamentEnablePools", file: "3-client-setTournamentEnablePools" },
|
||||
{ title: "SetTournamentEnableTeams", file: "3-client-setTournamentEnableTeams" },
|
||||
{ title: "SetTournamentImage", file: "3-client-setTournamentImage" },
|
||||
{ title: "SetTournamentName", file: "3-client-setTournamentName" },
|
||||
{ title: "SetTournamentPoolName", file: "3-client-setTournamentPoolName" },
|
||||
{ title: "SetTournamentRoleName", file: "3-client-setTournamentRoleName" },
|
||||
{ title: "SetTournamentRolePermissions", file: "3-client-setTournamentRolePermissions" },
|
||||
{ title: "SetTournamentScoreUpdateFrequency", file: "3-client-setTournamentScoreUpdateFrequency" },
|
||||
{ title: "SetTournamentShowQualifierButton", file: "3-client-setTournamentShowQualifierButton" },
|
||||
{ title: "SetTournamentShowTournamentButton", file: "3-client-setTournamentShowTournamentButton" },
|
||||
{ title: "SetTournamentTeamImage", file: "3-client-setTournamentTeamImage" },
|
||||
{ title: "SetTournamentTeamName", file: "3-client-setTournamentTeamName" },
|
||||
{ title: "ShowLoadedImage", file: "3-client-showLoadedImage" },
|
||||
{ title: "ShowPrompt", file: "3-client-showPrompt" },
|
||||
{ title: "StateManager", file: "3-client-stateManager" },
|
||||
{ title: "UpdateQualifierMap", file: "3-client-updateQualifierMap" },
|
||||
{ title: "UpdateTournamentPoolMap", file: "3-client-updateTournamentPoolMap" },
|
||||
{ title: "UpdateUser", file: "3-client-updateUser" }
|
||||
]
|
||||
},
|
||||
{
|
||||
category: "Models",
|
||||
icon: "view_in_ar",
|
||||
items: [
|
||||
{ title: "4-", file: "", order: 1 },
|
||||
{ title: "Endpoints", file: "", order: 2 },
|
||||
{ title: "Response Types", file: "", order: 3 }
|
||||
{ title: "4-", file: "" },
|
||||
{ title: "Endpoints", file: "" },
|
||||
{ title: "Response Types", file: "" }
|
||||
]
|
||||
},
|
||||
{
|
||||
category: "Enums",
|
||||
icon: "view_in_ar",
|
||||
items: [
|
||||
{ title: "5-", file: "", order: 1 },
|
||||
{ title: "Endpoints", file: "", order: 2 },
|
||||
{ title: "Response Types", file: "", order: 3 }
|
||||
{ title: "5-", file: "" },
|
||||
{ title: "Endpoints", file: "" },
|
||||
{ title: "Response Types", file: "" }
|
||||
]
|
||||
},
|
||||
{
|
||||
category: "Events",
|
||||
icon: "view_in_ar",
|
||||
items: [
|
||||
{ title: "6-", file: "", order: 1 },
|
||||
{ title: "Endpoints", file: "", order: 2 },
|
||||
{ title: "Response Types", file: "", order: 3 }
|
||||
{ title: "6-", file: "" },
|
||||
{ title: "Endpoints", file: "" },
|
||||
{ title: "Response Types", file: "" }
|
||||
]
|
||||
},
|
||||
{
|
||||
category: "Best Practices",
|
||||
icon: "view_in_ar",
|
||||
items: [
|
||||
{ title: "7-", file: "", order: 1 },
|
||||
{ title: "Endpoints", file: "", order: 2 },
|
||||
{ title: "Response Types", file: "", order: 3 }
|
||||
{ title: "7-", file: "" },
|
||||
{ title: "Endpoints", file: "" },
|
||||
{ title: "Response Types", file: "" }
|
||||
]
|
||||
}
|
||||
];
|
||||
|
|
@ -241,7 +245,7 @@
|
|||
// Find first doc in category and set it as current
|
||||
const categoryData = docStructure.find(c => c.category === category);
|
||||
if (categoryData && categoryData.items.length > 0) {
|
||||
categoryData.items.sort((a, b) => a.order - b.order);
|
||||
categoryData.items.sort((a, b) => categoryData.items.indexOf(a) - categoryData.items.indexOf(b));
|
||||
currentDoc = categoryData.items[0].file;
|
||||
window.location.hash = `${encodeURIComponent(category)}/${encodeURIComponent(currentDoc)}`;
|
||||
}
|
||||
|
|
@ -407,7 +411,7 @@
|
|||
{#if activeCategory === category.category}
|
||||
<!-- svelte-ignore missing-declaration -->
|
||||
<div class="category-items" transition:slide={{duration: 200}}>
|
||||
{#each category.items.sort((a, b) => a.order - b.order) as item}
|
||||
{#each category.items.sort((a, b) => category.items.indexOf(a) - category.items.indexOf(b)) as item}
|
||||
<button
|
||||
class="doc-link"
|
||||
class:active={currentDoc === item.file}
|
||||
|
|
@ -491,7 +495,7 @@
|
|||
|
||||
/* Sidebar styles */
|
||||
.sidebar {
|
||||
width: 280px;
|
||||
width: 330px;
|
||||
background-color: var(--bg-secondary);
|
||||
border-right: 1px solid var(--border-color);
|
||||
height: 100%;
|
||||
|
|
@ -923,6 +927,7 @@
|
|||
display: flex;
|
||||
gap: 1rem;
|
||||
align-items: flex-start;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.markdown-container :global(.info-box) {
|
||||
|
|
|
|||
|
|
@ -4,12 +4,13 @@
|
|||
import { bkAPIUrl } from '$lib/config.json';
|
||||
import AddNewAuthorisedUser from "$lib/components/popups/AddNewAuthorisedUser.svelte";
|
||||
//@ts-ignore
|
||||
import { Match, Tournament, TAClient, Response_ResponseType } from 'moons-ta-client';
|
||||
import { Match, Tournament, TAClient, Response_ResponseType, Role } from 'moons-ta-client';
|
||||
import { writable } from "svelte/store";
|
||||
import SideMenu from "$lib/components/menus/SideMenuTournaments.svelte";
|
||||
import InfoPopup from "$lib/components/notifications/InfoPopup.svelte";
|
||||
import { bufferToImageUrl, convertImageToUint8Array, linkToUint8Array } from "$lib/services/taImages.js";
|
||||
import Popup from "$lib/components/notifications/Popup.svelte";
|
||||
import AddOrModifyRole from "$lib/components/popups/AddOrModifyRole.svelte";
|
||||
import { goto } from "$app/navigation";
|
||||
|
||||
export let data;
|
||||
|
|
@ -37,6 +38,11 @@
|
|||
let popupContent: string = "";
|
||||
let showAddUserPopup = false;
|
||||
let showSuccessfullySaved = false;
|
||||
let tournamentRoles: Role[] = [];
|
||||
|
||||
// Role popup state
|
||||
let showRolePopup: boolean = false;
|
||||
let editingRole: Role | null = null;
|
||||
|
||||
// File upload
|
||||
let fileInput: HTMLInputElement;
|
||||
|
|
@ -54,13 +60,6 @@
|
|||
scoreUpdateFrequency: false,
|
||||
bannedMods: false
|
||||
}
|
||||
|
||||
enum Permissions {
|
||||
"None" = 0,
|
||||
"View Only" = 1,
|
||||
"Administrator" = 2,
|
||||
"View and Administrator" = 3
|
||||
}
|
||||
|
||||
// Remove 'Bearer ' from the token
|
||||
if($authTokenStore) {
|
||||
|
|
@ -125,7 +124,7 @@
|
|||
console.log('Adding user:', userData);
|
||||
|
||||
try {
|
||||
let addAuthorizedUserResult = await client.addAuthorizedUser(tournamentGuid, userData.discordId, userData.permission);
|
||||
let addAuthorizedUserResult = await client.addAuthorizedUser(tournamentGuid, userData.discordId, userData.roleIds);
|
||||
|
||||
// If successful, add to the authorizedUsers array
|
||||
if (addAuthorizedUserResult.type === Response_ResponseType.Success) {
|
||||
|
|
@ -137,6 +136,65 @@
|
|||
}
|
||||
}
|
||||
|
||||
async function handleAddRole(event: CustomEvent) {
|
||||
const roleData = event.detail;
|
||||
console.log('Adding role:', roleData);
|
||||
|
||||
let role: Role = {
|
||||
name: roleData.name,
|
||||
roleId: roleData.roleId,
|
||||
permissions: roleData.permisions,
|
||||
tournamentId: tournamentGuid,
|
||||
guid: ''
|
||||
}
|
||||
|
||||
try {
|
||||
let addRoleResult = await client.addTournamentRole(tournamentGuid, role);
|
||||
console.log("result", addRoleResult)
|
||||
if (addRoleResult.type === Response_ResponseType.Success) {
|
||||
tournamentRoles = [...tournamentRoles, role];
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error adding role:', err);
|
||||
error = "Failed to add role, please view console, screenshot it and send it to serverbp or matrikmoon on Discord.";
|
||||
}
|
||||
}
|
||||
|
||||
async function handleModifyRole(event: CustomEvent) {
|
||||
const roleData = event.detail;
|
||||
console.log('Adding role:', roleData);
|
||||
|
||||
const role: Role = {
|
||||
name: roleData.name,
|
||||
roleId: roleData.roleId,
|
||||
permissions: roleData.permisions,
|
||||
tournamentId: tournamentGuid,
|
||||
guid: ''
|
||||
}
|
||||
|
||||
try {
|
||||
let modifyRoleName = await client.setTournamentRoleName(tournamentGuid, roleData.roleId, roleData.name);
|
||||
let modifyRolePermissions = await client.setTournamentRolePermissions(tournamentGuid, roleData.roleId, roleData.permissions);
|
||||
|
||||
// If successful, add to the authorizedUsers array
|
||||
if (modifyRoleName.type === Response_ResponseType.Success && modifyRolePermissions.type === Response_ResponseType.Success) {
|
||||
tournamentRoles = [role, ...tournamentRoles.filter(x => x.roleId !== role.roleId)];
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error modifying role:', err);
|
||||
error = "Failed to modify role, please view console, screenshot it and send it to serverbp or matrikmoon on Discord.";
|
||||
}
|
||||
}
|
||||
|
||||
async function handleDeleteRole(roleId: string): Promise<any> {
|
||||
const removeRoleResponse = await client.removeTournamentRole(tournamentGuid, roleId);
|
||||
|
||||
if (removeRoleResponse.type === Response_ResponseType.Success) {
|
||||
console.log("Removed role", removeRoleResponse);
|
||||
tournamentRoles = [...tournamentRoles.filter(x => x.roleId !== roleId)];
|
||||
}
|
||||
}
|
||||
|
||||
function removeImage() {
|
||||
imagePreview = "/talogo.png";
|
||||
tournamentImage = "/talogo.png";
|
||||
|
|
@ -146,6 +204,15 @@
|
|||
function openAddUserPopup() {
|
||||
showAddUserPopup = true;
|
||||
}
|
||||
|
||||
function handleEditRole(roleGuid: string): any {
|
||||
editingRole = tournamentRoles.find(x => x.guid == roleGuid)!;
|
||||
showRolePopup = true;
|
||||
}
|
||||
|
||||
function openAddOrModifyRolePopup() {
|
||||
showRolePopup = true;
|
||||
}
|
||||
|
||||
function closeAddUserPopup() {
|
||||
showAddUserPopup = false;
|
||||
|
|
@ -251,31 +318,13 @@
|
|||
hideTournament = !tournament.settings?.allowUnauthorizedView || false;
|
||||
scoreUpdateFrequency = tournament.settings?.scoreUpdateFrequency || 1;
|
||||
bannedMods = tournament.settings?.bannedMods || [];
|
||||
tournamentRoles = tournament.settings?.roles || [];
|
||||
|
||||
console.log("roles", tournamentRoles)
|
||||
|
||||
// For demo purposes, populate authorised users
|
||||
// This would typically come from the tournament object
|
||||
let authorisedUserRes = await client.getAuthorizedUsers(tournamentGuid);
|
||||
console.log(authorisedUserRes)
|
||||
authorizedUsers = (authorisedUserRes as any).details.getAuthorizedUsers.authorizedUsers.filter((user: any) => user.discordId !== $discordDataStore.id) || [
|
||||
{
|
||||
guid: "user1",
|
||||
name: "Failed Data",
|
||||
discordInfo: {
|
||||
username: "TourneyAdmin",
|
||||
avatarUrl: "https://api.dicebear.com/7.x/avataaars/svg?seed=admin"
|
||||
},
|
||||
permissions: "admin"
|
||||
},
|
||||
{
|
||||
guid: "user2",
|
||||
name: "Failed Data",
|
||||
discordInfo: {
|
||||
username: "TourneyViewer",
|
||||
avatarUrl: "https://api.dicebear.com/7.x/avataaars/svg?seed=viewer"
|
||||
},
|
||||
permissions: "view only"
|
||||
}
|
||||
];
|
||||
authorizedUsers = (authorisedUserRes as any).details.getAuthorizedUsers.authorizedUsers.filter((user: any) => user.discordId !== $discordDataStore.id)
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
|
|
@ -465,7 +514,7 @@
|
|||
|
||||
<!-- Advanced Settings Section -->
|
||||
<div class="settings-section">
|
||||
<h3>Advanced Settings</h3>
|
||||
<h3>Scoring Settings</h3>
|
||||
|
||||
<div class="settings-group">
|
||||
<div class="setting-row">
|
||||
|
|
@ -519,6 +568,35 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings-section">
|
||||
<h3>Authorisation Roles</h3>
|
||||
|
||||
<div class="settings-group">
|
||||
<div class="actions">
|
||||
<button class="action-button add-user" on:click={openAddOrModifyRolePopup}>
|
||||
<span class="material-icons">add_moderator</span>
|
||||
Add Role
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="user-cards-grid">
|
||||
{#each tournamentRoles as role}
|
||||
<div class="user-card">
|
||||
<div class="user-info">
|
||||
<h4>{role.name || "Unknown Role"}</h4>
|
||||
</div>
|
||||
<button class="edit-button" on:click={handleEditRole(role.guid)}>
|
||||
<span class="material-icons">edit</span>
|
||||
</button>
|
||||
<button class="trash-button" on:click={async() => await handleDeleteRole(role.roleId)}>
|
||||
<span class="material-icons">delete</span>
|
||||
</button>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings-section full-width">
|
||||
|
|
@ -526,10 +604,13 @@
|
|||
|
||||
<div class="settings-group">
|
||||
<div class="authorized-users-container">
|
||||
<button class="action-button add-user" on:click={openAddUserPopup}>
|
||||
<span class="material-icons">person_add</span>
|
||||
Add User
|
||||
</button>
|
||||
<div class="actions">
|
||||
<button class="action-button add-user" on:click={openAddUserPopup}>
|
||||
<span class="material-icons">person_add</span>
|
||||
Add User
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{#if authorizedUsers.length === 0}
|
||||
<p class="no-users">No authorized users added yet</p>
|
||||
{:else}
|
||||
|
|
@ -543,7 +624,10 @@
|
|||
/>
|
||||
<div class="user-info">
|
||||
<h4>{user.name || user.discordUsername || "Unknown User"}</h4>
|
||||
<p class="user-permission">{Permissions[user.permission]}</p>
|
||||
{#each user.roles as roleId}
|
||||
<p class="user-permission">{tournamentRoles.find(x => x.roleId == roleId)?.name}</p>
|
||||
{/each}
|
||||
|
||||
</div>
|
||||
<button class="remove-user" on:click={() => removeAuthorisedUser(user.discordId)}>
|
||||
<span class="material-icons">delete</span>
|
||||
|
|
@ -562,10 +646,18 @@
|
|||
isOpen={showAddUserPopup}
|
||||
tournamentGuid={tournamentGuid}
|
||||
taClient={client}
|
||||
tournamentRoles={tournamentRoles}
|
||||
on:close={closeAddUserPopup}
|
||||
on:addUser={handleAddUser}
|
||||
/>
|
||||
|
||||
<AddOrModifyRole
|
||||
bind:isOpen={showRolePopup}
|
||||
role={editingRole}
|
||||
on:createRole={handleAddRole}
|
||||
on:modifyRole={handleModifyRole}
|
||||
/>
|
||||
|
||||
<Popup
|
||||
bind:open={showSuccessfullySaved}
|
||||
message="Successfully saved the tournament information!"
|
||||
|
|
@ -722,6 +814,58 @@
|
|||
.info-button:hover {
|
||||
color: var(--accent-hover);
|
||||
}
|
||||
|
||||
/* Edit Button */
|
||||
.edit-button {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--accent-color);
|
||||
padding: 0;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.edit-button .material-icons {
|
||||
font-size: 1.125rem;
|
||||
}
|
||||
|
||||
.edit-button:hover {
|
||||
color: var(--accent-hover);
|
||||
}
|
||||
|
||||
/* Trash Button */
|
||||
.trash-button {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #ff5555;
|
||||
padding: 0.3rem;
|
||||
font-size: 0.875rem;
|
||||
border-radius: 30%;
|
||||
}
|
||||
|
||||
.trash-button .material-icons {
|
||||
font-size: 1.125rem;
|
||||
}
|
||||
|
||||
.trash-button:hover {
|
||||
background-color: rgba(255, 85, 85, 0.1);
|
||||
}
|
||||
|
||||
.role-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: var(--bg-tertiary);
|
||||
border-radius: 0.5rem;
|
||||
padding: 0.3rem;
|
||||
padding-left: 1rem;
|
||||
}
|
||||
|
||||
/* Image upload */
|
||||
.image-upload-container {
|
||||
|
|
@ -890,7 +1034,7 @@
|
|||
}
|
||||
|
||||
.action-button.add-user {
|
||||
align-self: flex-start;
|
||||
/* align-self: flex-start; */
|
||||
background-color: var(--accent-color);
|
||||
border: none;
|
||||
border-radius: 0.375rem;
|
||||
|
|
|
|||
Loading…
Reference in a new issue