mirror of
https://github.com/psforever/PSFPortal.git
synced 2026-07-16 00:44:40 +00:00
Initial commit
This commit is contained in:
commit
20946fdb43
49 changed files with 2393 additions and 0 deletions
24
app/components/AccountLink.svelte
Normal file
24
app/components/AccountLink.svelte
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
<script>
|
||||
import { isAdmin } from '../UserState'
|
||||
export let account;
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.banned-account {
|
||||
color: red;
|
||||
text-decoration: line-through;
|
||||
}
|
||||
</style>
|
||||
|
||||
<span class="account-link">
|
||||
|
||||
{#if $isAdmin}
|
||||
<a class:banned-account={account.inactive} href="/user/{account.id}">{account.name}</a>
|
||||
{:else}
|
||||
<span class:banned-account={account.inactive}>{account.name}</span>
|
||||
{/if}
|
||||
|
||||
{#if account.admin}
|
||||
<span class="badge badge-success">GM</span>
|
||||
{/if}
|
||||
</span>
|
||||
22
app/components/Alert.svelte
Normal file
22
app/components/Alert.svelte
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
<script>
|
||||
import { fade } from 'svelte/transition';
|
||||
|
||||
export function message(e, ehtml) {
|
||||
errorMessage = e ? e : "";
|
||||
errorMessageHTML = ehtml ? ehtml : "";
|
||||
|
||||
shake = true;
|
||||
setTimeout(() => {shake = false;}, 800)
|
||||
}
|
||||
|
||||
let errorMessage = ""
|
||||
let errorMessageHTML = ""
|
||||
let shake = false;
|
||||
</script>
|
||||
|
||||
{#if errorMessage || errorMessageHTML}
|
||||
<div in:fade class:notification-shake={shake} class="alert alert-danger" role="alert">
|
||||
{errorMessage}
|
||||
{@html errorMessageHTML}
|
||||
</div>
|
||||
{/if}
|
||||
20
app/components/CharacterLink.svelte
Normal file
20
app/components/CharacterLink.svelte
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
<script>
|
||||
import { isAdmin } from '../UserState'
|
||||
export let character;
|
||||
</script>
|
||||
|
||||
<span class="character-link">
|
||||
{#if character.faction_id == 1}
|
||||
<img height=24 src="/img/nc_icon.png" alt="NC" />
|
||||
{:else if character.faction_id == 0}
|
||||
<img height=32 src="/img/tr_icon.png" alt="TR" />
|
||||
{:else if character.faction_id == 2}
|
||||
<img height=32 src="/img/vs_icon.png" alt="VS" />
|
||||
{/if}
|
||||
|
||||
{#if $isAdmin}
|
||||
<a href="/character/{character.id}">{character.name}</a>
|
||||
{:else}
|
||||
<span>{character.name}</span>
|
||||
{/if}
|
||||
</span>
|
||||
20
app/components/CharacterList.svelte
Normal file
20
app/components/CharacterList.svelte
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
<script>
|
||||
import { onMount } from 'svelte'
|
||||
import axios from 'axios'
|
||||
import CharacterLink from '../components/CharacterLink'
|
||||
import moment from 'moment'
|
||||
|
||||
onMount(async () => {
|
||||
axios.get("/api/user/"+userId+"/characters
|
||||
});
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
{#if characters}
|
||||
{#each characters as char, i}
|
||||
<ul>
|
||||
<li><CharacterLink character={char} /></li>
|
||||
</ul>
|
||||
{/each}
|
||||
{/if}
|
||||
89
app/components/EmpireStats.svelte
Normal file
89
app/components/EmpireStats.svelte
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
<script>
|
||||
import { onMount } from 'svelte';
|
||||
import { tweened } from 'svelte/motion';
|
||||
import { cubicOut } from 'svelte/easing';
|
||||
|
||||
const progress = tweened(0, {
|
||||
duration: 1000,
|
||||
easing: cubicOut
|
||||
});
|
||||
|
||||
export let stats = { "TR" : 0, "NC" : 0, "VS" : 0};
|
||||
let total = stats.TR + stats.NC + stats.VS;
|
||||
let percentages = { "TR" : stats.TR/total,
|
||||
"NC" : stats.NC/total,
|
||||
"VS" : stats.VS/total}
|
||||
let tr, nc, vs;
|
||||
|
||||
onMount(() => {
|
||||
setTimeout(() => progress.set(1.0), 100);
|
||||
|
||||
tr.style.height = "1px";
|
||||
nc.style.height = "1px";
|
||||
vs.style.height = "1px";
|
||||
})
|
||||
|
||||
progress.subscribe((v) => {
|
||||
if (tr === undefined || !tr.style)
|
||||
return
|
||||
|
||||
tr.style.height = v*percentages.TR*200 + "px";
|
||||
nc.style.height = v*percentages.NC*200 + "px";
|
||||
vs.style.height = v*percentages.VS*200 + "px";
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.empire-stats {
|
||||
background: black;
|
||||
height: 200px;
|
||||
width: 220px;
|
||||
border: 1px solid white;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.empire-stat {
|
||||
border: 1px solid white;
|
||||
border-bottom: 0;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
text-align: center;
|
||||
min-width: 50px;
|
||||
min-height: 3em;
|
||||
}
|
||||
|
||||
.empire-stat:nth-child(2) {
|
||||
left: 10%;
|
||||
}
|
||||
|
||||
.empire-stat:nth-child(3) {
|
||||
left: 40.0%;
|
||||
}
|
||||
|
||||
.empire-stat:nth-child(4) {
|
||||
left: 70.0%;
|
||||
}
|
||||
|
||||
.empire-stats-header {
|
||||
display: inline;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
padding-top: 3px;
|
||||
padding-bottom: 3px;
|
||||
background: white;
|
||||
border-bottom: 3px solid black;
|
||||
font-size: 1.0em;
|
||||
color: black;
|
||||
text-align: center;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<div class="empire-stats clearfix">
|
||||
<div class="empire-stats-header">Empire Need</div>
|
||||
<div title={stats.TR} bind:this={tr} class="empire-stat faction-tr-bg"><strong>TR</strong><br/>{Math.round(percentages.TR*100*$progress)}%</div>
|
||||
<div title={stats.NC} bind:this={nc} class="empire-stat faction-nc-bg"><strong>NC</strong><br/>{Math.round(percentages.NC*100*$progress)}%</div>
|
||||
<div title={stats.VS} bind:this={vs} class="empire-stat faction-vs-bg"><strong>VS</strong><br/>{Math.round(percentages.VS*100*$progress)}%</div>
|
||||
</div>
|
||||
37
app/components/LoginList.svelte
Normal file
37
app/components/LoginList.svelte
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
<script>
|
||||
import { onMount } from 'svelte'
|
||||
import PaginatedList from './PaginatedList'
|
||||
import axios from 'axios'
|
||||
import moment from 'moment'
|
||||
|
||||
export let account_id;
|
||||
|
||||
async function fetch(page) {
|
||||
try {
|
||||
const resp = await axios.get("/api/user/" + account_id + "/logins?page="+page);
|
||||
return [resp.data.logins, resp.data.page];
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
onMount(async () => {
|
||||
});
|
||||
</script>
|
||||
|
||||
<PaginatedList {fetch} let:data={logins} let:pagination={pagination}>
|
||||
<table slot="body" class="table table-dark table-responsive">
|
||||
<thead>
|
||||
<td>Login Time</td>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
{#each logins as login, i}
|
||||
<tr>
|
||||
<td>{moment(login.login_time).fromNow()}</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</PaginatedList>
|
||||
48
app/components/Nav.svelte
Normal file
48
app/components/Nav.svelte
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
<script>
|
||||
import { get_initial_state, logout, isAdmin, loggedIn, username } from '../UserState.js';
|
||||
import axios from 'axios'
|
||||
export let route;
|
||||
export let pageCtx;
|
||||
console.log(pageCtx)
|
||||
</script>
|
||||
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark justify-content-between">
|
||||
<div class="container">
|
||||
<a class="navbar-brand" href="/">
|
||||
<img src="/img/logo_crop.png" height="30" class="d-inline-block align-top" alt="">
|
||||
</a>
|
||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
|
||||
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
||||
<ul class="navbar-nav mr-auto">
|
||||
<li class="nav-item" class:active={route=="/"}>
|
||||
<a class="nav-link" href="/">Server Status</a>
|
||||
</li>
|
||||
{#if $isAdmin}
|
||||
<li class="nav-item" class:active={route=="/admin"}>
|
||||
<a class="nav-link" style="color: red;" href="/admin">Admin Panel</a>
|
||||
</li>
|
||||
{/if}
|
||||
</ul>
|
||||
<ul class="navbar-nav">
|
||||
{#if $loggedIn}
|
||||
<li class="nav-item" class:active={route=="/profile"}>
|
||||
<span class="navbar-text">Welcome <a href="/profile">{$username}</a>!</span>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" on:click|preventDefault={logout} href="/logout">Logout</a>
|
||||
</li>
|
||||
{:else}
|
||||
<li class="nav-item" class:active={route=="/login"}>
|
||||
<a class="nav-link" href="/login">Login</a>
|
||||
</li>
|
||||
<li class="nav-item" class:active={route=="/register"}>
|
||||
<a class="nav-link" href="/register">Register</a>
|
||||
</li>
|
||||
{/if}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
57
app/components/PaginatedList.svelte
Normal file
57
app/components/PaginatedList.svelte
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
<script>
|
||||
import { onMount } from 'svelte'
|
||||
import axios from 'axios'
|
||||
import Pagination from '../components/Pagination'
|
||||
|
||||
export let fetch;
|
||||
|
||||
let data;
|
||||
let fetching = false;
|
||||
let pagination = { page: 1 };
|
||||
|
||||
export async function refresh() {
|
||||
await list_fetch(pagination.page)
|
||||
}
|
||||
|
||||
onMount(async () => {
|
||||
const url = new URL(window.location.href)
|
||||
let page = url.searchParams.get('page')
|
||||
|
||||
if (page == undefined)
|
||||
page = 1;
|
||||
|
||||
await list_fetch(page);
|
||||
})
|
||||
|
||||
async function pageChange(page) {
|
||||
if (pagination.page == page || fetching)
|
||||
return
|
||||
|
||||
await list_fetch(page);
|
||||
}
|
||||
|
||||
async function list_fetch(page) {
|
||||
fetching = true;
|
||||
|
||||
try {
|
||||
if (fetch != undefined) {
|
||||
const results = await fetch(page)
|
||||
|
||||
if (results != undefined) {
|
||||
data = results[0];
|
||||
pagination = results[1]
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
fetching = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if data}
|
||||
<slot name="header" data={data} pagination={pagination}></slot>
|
||||
<Pagination {pagination} {pageChange} />
|
||||
<slot name="body" data={data} pagination={pagination}></slot>
|
||||
<Pagination {pagination} {pageChange} />
|
||||
<slot name="footer" data={data} pagination={pagination}></slot>
|
||||
{/if}
|
||||
80
app/components/Pagination.svelte
Normal file
80
app/components/Pagination.svelte
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
<script>
|
||||
export let pagination;
|
||||
export let pageChange;
|
||||
let numPages = 10;
|
||||
let pages = []
|
||||
|
||||
$ : {
|
||||
const new_pages = [];
|
||||
let pi = 0, i;
|
||||
let pg = pagination;
|
||||
|
||||
const pageChunk = Math.max(Math.ceil(numPages/3), 1);
|
||||
const middleChunk = Math.max(Math.ceil(pageChunk/2), 1);
|
||||
const leftBound = Math.min(pageChunk+1, pagination.page_count);
|
||||
const rightBound = Math.max(pagination.page_count-pageChunk, 1);
|
||||
|
||||
// fast path: draw all pages
|
||||
if (pg.page_count <= numPages || rightBound <= leftBound) {
|
||||
for (i = 1; i <= pg.page_count; i++)
|
||||
new_pages[pi++] = i;
|
||||
} else {
|
||||
let middleLeft = Math.max(pg.page-middleChunk, leftBound);
|
||||
let middleRight = Math.min(pg.page+middleChunk, rightBound);
|
||||
|
||||
// left and middle chunks are joined
|
||||
if (middleLeft == leftBound) {
|
||||
middleLeft += 1;
|
||||
middleRight = Math.min(middleLeft+pageChunk, rightBound);
|
||||
// middle and right chunks are joined
|
||||
} else if (middleRight == rightBound) {
|
||||
middleRight -= 1;
|
||||
middleLeft = Math.min(middleRight-middleChunk, rightBound);
|
||||
}
|
||||
|
||||
//console.log("[1-"+leftBound+"]", "["+middleLeft+"-"+middleRight+"]", "["+rightBound+"-"+pagination.page_count+"]");
|
||||
|
||||
// left chunk
|
||||
for (i = 1; i <= leftBound; i++) new_pages[pi++] = i;
|
||||
if (leftBound+1 != middleLeft) new_pages[pi++] = -1;
|
||||
|
||||
// middle chunk
|
||||
for (i = middleLeft; i <= middleRight; i++) new_pages[pi++] = i;
|
||||
|
||||
// right chunk
|
||||
if (middleRight+1 != rightBound) new_pages[pi++] = -1;
|
||||
for (i = rightBound; i <= pg.page_count; i++) new_pages[pi++] = i;
|
||||
}
|
||||
|
||||
pages = new_pages
|
||||
//console.log(pages);
|
||||
}
|
||||
</script>
|
||||
|
||||
<p>Displaying {(pagination.page-1)*pagination.items_per_page+1} — {Math.min(pagination.page*pagination.items_per_page, pagination.item_count)}</p>
|
||||
|
||||
<nav aria-label="Page navigation">
|
||||
<ul class="pagination pagination-sm">
|
||||
<li class="page-item" class:disabled={pagination.page<=1}>
|
||||
<a class="page-link" href={"?page="+(pagination.page-1)}
|
||||
on:click={(e) => pageChange(pagination.page-1)}
|
||||
aria-label="Previous">
|
||||
<span aria-hidden="true">«</span>
|
||||
</a>
|
||||
</li>
|
||||
{#each pages as page,i}
|
||||
{#if page == -1}
|
||||
<li class="page-item page-last-separator disabled" ><a class="page-link">...<a></li>
|
||||
{:else}
|
||||
<li class="page-item" class:active={page==pagination.page}><a on:click={(e) => pageChange(page)} href={"?page="+page} class="page-link">{page}</a></li>
|
||||
{/if}
|
||||
{/each}
|
||||
<li class="page-item" class:disabled={pagination.page>=pagination.page_count}>
|
||||
<a class="page-link" href={"?page="+(pagination.page+1)}
|
||||
on:click={(e) => pageChange(pagination.page+1)}
|
||||
aria-label="Next">
|
||||
<span aria-hidden="true">»</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
Loading…
Add table
Add a link
Reference in a new issue