feat: a whole lot of stuff
This commit is contained in:
parent
a2f22843ee
commit
eba1a4543f
@ -1,6 +1,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Router, Link, Route } from "svelte-navigator";
|
import { Router, Link, Route } from "svelte-navigator";
|
||||||
import Navigation from "./lib/Navigation.svelte";
|
import Navigation from "./lib/Navigation.svelte";
|
||||||
|
import Dash from "./pages/Dash.svelte";
|
||||||
|
import Home from "./pages/Home.svelte";
|
||||||
|
|
||||||
|
|
||||||
// theme cdns (I might make some myself too)
|
// theme cdns (I might make some myself too)
|
||||||
@ -40,7 +42,10 @@
|
|||||||
<Navigation bind:style={style}/>
|
<Navigation bind:style={style}/>
|
||||||
<div>
|
<div>
|
||||||
<Route path="/">
|
<Route path="/">
|
||||||
<h2>Ooga booga</h2>
|
<Home />
|
||||||
|
</Route>
|
||||||
|
<Route path="/dash">
|
||||||
|
<Dash />
|
||||||
</Route>
|
</Route>
|
||||||
</div>
|
</div>
|
||||||
</Router>
|
</Router>
|
BIN
src/assets/default_avatar.png
Normal file
BIN
src/assets/default_avatar.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.4 KiB |
23
src/functions.ts
Normal file
23
src/functions.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { currentUser, loggedIn } from "./stores";
|
||||||
|
import PKAPI from "./api";
|
||||||
|
import type Sys from './api/system';
|
||||||
|
|
||||||
|
|
||||||
|
function blockQuote(text: string) {
|
||||||
|
let match = text.match(/(?<=\n|^)(> [^\n]*(?:\n>[^\n]*)*)/gim);
|
||||||
|
let parse: string[] = [];
|
||||||
|
for (let i = 0; i < match.length; i++) {
|
||||||
|
parse[i] = match[i].replace(/(?<=\n|^)> ?/gim, "");
|
||||||
|
text = text.replace(match[i], `<div class="bq">${parse[i]}</div>`);
|
||||||
|
}
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseMarkdown(text: string) {
|
||||||
|
text = blockQuote(text);
|
||||||
|
text = text.replace(/\*{3}(.*?)\*{3}/gim, '<b><i>$1</i></b>');
|
||||||
|
text = text.replace(/\*{2}(.*?)\*{2}/gim, '<b>$1</b>');
|
||||||
|
text = text.replace(/\*{1}(.*?)\*{1}/gim, '<i>$1</i>');
|
||||||
|
text = text.replace(/\n/gim, '<br />')
|
||||||
|
return text;
|
||||||
|
}
|
32
src/lib/CardsHeader.svelte
Normal file
32
src/lib/CardsHeader.svelte
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { Modal, CardHeader, CardTitle, Image } from 'sveltestrap';
|
||||||
|
import FaUserCircle from 'svelte-icons/fa/FaUserCircle.svelte'
|
||||||
|
import default_avatar from '../assets/default_avatar.png';
|
||||||
|
import type Sys from '../api/system';
|
||||||
|
|
||||||
|
export let item: Sys;
|
||||||
|
|
||||||
|
let avatarOpen = false;
|
||||||
|
const toggleAvatarModal = () => (avatarOpen = !avatarOpen);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle style="margin-top: 0px; margin-bottom: 0px; outline: none; align-items: center;" class="d-flex justify-content-between align-middle">
|
||||||
|
<div>
|
||||||
|
<div class="icon d-inline-block">
|
||||||
|
<FaUserCircle />
|
||||||
|
</div>
|
||||||
|
<span style="vertical-align: middle;">{item.name} ({item.id})</span>
|
||||||
|
</div>
|
||||||
|
{#if item && item.avatar_url}
|
||||||
|
<img on:click={toggleAvatarModal} class="rounded-circle avatar" src={item.avatar_url} alt="Your system avatar" />
|
||||||
|
{:else}
|
||||||
|
<img class="rounded-circle avatar" src={default_avatar} alt="your system avatar (default)" />
|
||||||
|
{/if}
|
||||||
|
<Modal isOpen={avatarOpen} toggle={toggleAvatarModal}>
|
||||||
|
<div slot="external" on:click={toggleAvatarModal} style="height: 100%; width: max-content; max-width: 100%; margin-left: auto; margin-right: auto; display: flex;">
|
||||||
|
<Image style="display: block; margin: auto;" src={item.avatar_url} thumbnail alt="Your system avatar" />
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
</CardTitle>
|
||||||
|
</CardHeader>
|
@ -1,42 +1,51 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {Navbar, NavbarBrand, Nav, NavItem, NavLink, Collapse, NavbarToggler, Dropdown, DropdownItem, DropdownMenu, DropdownToggle} from 'sveltestrap';
|
import {Navbar, NavbarBrand, Nav, NavItem, NavLink, Collapse, NavbarToggler, Dropdown, DropdownItem, DropdownMenu, DropdownToggle} from 'sveltestrap';
|
||||||
import { createEventDispatcher } from 'svelte';
|
import { loggedIn } from '../stores';
|
||||||
|
import { Link } from 'svelte-navigator';
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
export let style: string;
|
||||||
|
|
||||||
function changeStyle(style: string) {
|
|
||||||
dispatch('styleChange', style.toLowerCase());
|
|
||||||
}
|
|
||||||
|
|
||||||
let isOpen = false;
|
let isOpen = false;
|
||||||
|
|
||||||
const toggle = () => (isOpen = !isOpen);
|
const toggle = () => (isOpen = !isOpen);
|
||||||
</script>
|
|
||||||
|
|
||||||
<Navbar color="transparent" light expand="lg">
|
let loggedIn_value: boolean;
|
||||||
<NavbarBrand>pk-webs</NavbarBrand>
|
|
||||||
<NavbarToggler on:click={toggle}></NavbarToggler>
|
loggedIn.subscribe(value => {
|
||||||
<Collapse {isOpen} navbar expand="lg">
|
loggedIn_value = value;
|
||||||
<Nav class="ms-auto" navbar>
|
});
|
||||||
<Dropdown nav inNavbar>
|
</script>
|
||||||
<DropdownToggle>Styles</DropdownToggle>
|
<div style="background-color: #292929">
|
||||||
<DropdownMenu end>
|
<Navbar color="light" light expand="lg" class="mb-4">
|
||||||
<DropdownItem on:click={() => changeStyle("light")}>Light</DropdownItem>
|
<NavbarBrand>pk-webs</NavbarBrand>
|
||||||
<DropdownItem on:click={() => changeStyle("dark")}>Dark</DropdownItem>
|
<NavbarToggler on:click={toggle}></NavbarToggler>
|
||||||
</DropdownMenu>
|
<Collapse {isOpen} navbar expand="lg">
|
||||||
</Dropdown>
|
<Nav class="ms-auto" navbar>
|
||||||
<NavItem>
|
<Dropdown nav inNavbar>
|
||||||
<NavLink href="/dash">Dash</NavLink>
|
<DropdownToggle color="transparent">Styles</DropdownToggle>
|
||||||
</NavItem>
|
<DropdownMenu end>
|
||||||
<NavItem>
|
<DropdownItem on:click={() => style = "light"}>Light</DropdownItem>
|
||||||
<NavLink href="/settings">Settings</NavLink>
|
<DropdownItem on:click={() => style = "dark"}>Dark</DropdownItem>
|
||||||
</NavItem>
|
</DropdownMenu>
|
||||||
<NavItem>
|
</Dropdown>
|
||||||
<NavLink href="/templates">templates</NavLink>
|
{#if loggedIn_value || localStorage.getItem("pk-token")}
|
||||||
</NavItem>
|
<Dropdown nav inNavbar>
|
||||||
<NavItem>
|
<DropdownToggle color="transparent">Dash</DropdownToggle>
|
||||||
<NavLink href="/public">Public</NavLink>
|
<DropdownMenu end>
|
||||||
</NavItem>
|
<Link to="/dash" state={{tab: "system"}}><DropdownItem>System</DropdownItem></Link>
|
||||||
</Nav>
|
<Link to="/dash" state={{tab: "members"}}><DropdownItem>Members</DropdownItem></Link>
|
||||||
</Collapse>
|
</DropdownMenu>
|
||||||
</Navbar>
|
</Dropdown>
|
||||||
|
{/if}
|
||||||
|
<NavItem>
|
||||||
|
<NavLink href="/settings">Settings</NavLink>
|
||||||
|
</NavItem>
|
||||||
|
<NavItem>
|
||||||
|
<NavLink href="/templates">Templates</NavLink>
|
||||||
|
</NavItem>
|
||||||
|
<NavItem>
|
||||||
|
<NavLink href="/public">Public</NavLink>
|
||||||
|
</NavItem>
|
||||||
|
</Nav>
|
||||||
|
</Collapse>
|
||||||
|
</Navbar>
|
||||||
|
</div>
|
53
src/lib/cards/PrivateSystem.svelte
Normal file
53
src/lib/cards/PrivateSystem.svelte
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { currentUser } from '../../stores';
|
||||||
|
import { Modal, Card, CardHeader, CardBody, CardTitle, Image, ModalHeader, Col, Row, Button } from 'sveltestrap';
|
||||||
|
import CardsHeader from '../CardsHeader.svelte';
|
||||||
|
import { parseMarkdown } from '../../functions';
|
||||||
|
|
||||||
|
export let user;
|
||||||
|
$: htmlDescription = parseMarkdown(user.description);
|
||||||
|
|
||||||
|
let bannerOpen = false;
|
||||||
|
const toggleBannerModal = () => (bannerOpen = !bannerOpen);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Card>
|
||||||
|
<CardsHeader bind:item={user}/>
|
||||||
|
<CardBody style="border-left: 4px solid #{user.color}">
|
||||||
|
<Row>
|
||||||
|
<Col xs={12} lg={4} class="mb-2">
|
||||||
|
<b>ID:</b> {user.id}
|
||||||
|
</Col>
|
||||||
|
<Col xs={12} lg={4} class="mb-2">
|
||||||
|
<b>Name:</b> {user.name}
|
||||||
|
</Col>
|
||||||
|
{#if user.tag}
|
||||||
|
<Col xs={12} lg={4} class="mb-2">
|
||||||
|
<b>Tag:</b> {user.tag}
|
||||||
|
</Col>
|
||||||
|
{/if}
|
||||||
|
<Col xs={12} lg={4} class="mb-2">
|
||||||
|
<b>Timezone:</b> {user.timezone}
|
||||||
|
</Col>
|
||||||
|
{#if user.color}
|
||||||
|
<Col xs={12} lg={4} class="mb-2">
|
||||||
|
<b>Color:</b> {user.color}
|
||||||
|
</Col>
|
||||||
|
{/if}
|
||||||
|
{#if user.banner}
|
||||||
|
<Col xs={12} lg={3} class="mb-2">
|
||||||
|
<b>Banner:</b> <Button size="sm" color="light" on:click={toggleBannerModal}>View</Button>
|
||||||
|
<Modal isOpen={bannerOpen} toggle={toggleBannerModal}>
|
||||||
|
<div slot="external" on:click={toggleBannerModal} style="height: 100%; width: max-content; max-width: 100%; margin-left: auto; margin-right: auto; display: flex;">
|
||||||
|
<Image style="display: block; margin: auto;" src={user.banner} thumbnail alt="Your system banner" />
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
</Col>
|
||||||
|
{/if}
|
||||||
|
<div class="mt-2">
|
||||||
|
<b>Description:</b><br />
|
||||||
|
{@html htmlDescription}
|
||||||
|
</div>
|
||||||
|
</Row>
|
||||||
|
</CardBody>
|
||||||
|
</Card>
|
80
src/pages/Dash.svelte
Normal file
80
src/pages/Dash.svelte
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { Container, Col, Row, TabContent, TabPane, Card } from 'sveltestrap';
|
||||||
|
import { navigate, useLocation } from "svelte-navigator";
|
||||||
|
import { currentUser, loggedIn } from '../stores';
|
||||||
|
|
||||||
|
import PrivateSystem from '../lib/cards/PrivateSystem.svelte';
|
||||||
|
import PKAPI from '../api';
|
||||||
|
import type Sys from '../api/system';
|
||||||
|
|
||||||
|
let location = useLocation();
|
||||||
|
|
||||||
|
let tabPane = $location.state && $location.state.tab;
|
||||||
|
|
||||||
|
if (tabPane === undefined) {
|
||||||
|
tabPane = "system";
|
||||||
|
}
|
||||||
|
|
||||||
|
let current;
|
||||||
|
currentUser.subscribe(value => {
|
||||||
|
current = value;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!current) {
|
||||||
|
login(localStorage.getItem("pk-token"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let user = current !== null ? current : JSON.parse(localStorage.getItem("pk-user"));
|
||||||
|
|
||||||
|
if (!localStorage.getItem("pk-token") && !user) {
|
||||||
|
navigate("/");
|
||||||
|
}
|
||||||
|
|
||||||
|
async function login(token: string) {
|
||||||
|
const api = new PKAPI();
|
||||||
|
try {
|
||||||
|
if (!token) {
|
||||||
|
throw new Error("Token cannot be empty.")
|
||||||
|
}
|
||||||
|
const res: Sys = await api.getSystem({token: token});
|
||||||
|
localStorage.setItem("pk-token", token);
|
||||||
|
localStorage.setItem("pk-user", JSON.stringify(res));
|
||||||
|
loggedIn.update(() => true);
|
||||||
|
currentUser.update(() => res);
|
||||||
|
user = res;
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
localStorage.removeItem("pk-token");
|
||||||
|
localStorage.removeItem("pk-user");
|
||||||
|
currentUser.update(() => null);
|
||||||
|
navigate("/");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if user && user.banner}
|
||||||
|
<div class="banner" style="background-image: url({user.banner})" />
|
||||||
|
{/if}
|
||||||
|
<Container>
|
||||||
|
<Row>
|
||||||
|
<Col class="mx-auto" xs={12} lg={9}>
|
||||||
|
<TabContent class="mt-3">
|
||||||
|
<TabPane tabId="system" tab="System" active={tabPane === "system"}>
|
||||||
|
<Card style="border-radius: 0; border: none;">
|
||||||
|
<PrivateSystem bind:user={user}/>
|
||||||
|
</Card>
|
||||||
|
</TabPane>
|
||||||
|
<TabPane tabId="members" tab="Members" active={tabPane === "members"}>
|
||||||
|
<Card style="border-radius: 0; border: none;">
|
||||||
|
alo
|
||||||
|
</Card>
|
||||||
|
</TabPane>
|
||||||
|
</TabContent>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Container>
|
||||||
|
|
||||||
|
<svelte:head>
|
||||||
|
<title>pk-webs | dash</title>
|
||||||
|
</svelte:head>
|
112
src/pages/Home.svelte
Normal file
112
src/pages/Home.svelte
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
import { Container, Card, CardHeader, CardBody, CardTitle, Col, Row, Spinner, Input, Button, Label, Alert } from 'sveltestrap';
|
||||||
|
import FaLockOpen from 'svelte-icons/fa/FaLockOpen.svelte';
|
||||||
|
import { loggedIn, currentUser } from '../stores';
|
||||||
|
import { Link } from 'svelte-navigator';
|
||||||
|
|
||||||
|
import PKAPI from '../api/index';
|
||||||
|
import type Sys from '../api/system';
|
||||||
|
|
||||||
|
let loading = false;
|
||||||
|
let err: string;
|
||||||
|
let token: string;
|
||||||
|
|
||||||
|
let isLoggedIn: boolean;
|
||||||
|
let user;
|
||||||
|
|
||||||
|
loggedIn.subscribe(value => {
|
||||||
|
isLoggedIn = value;
|
||||||
|
});
|
||||||
|
|
||||||
|
currentUser.subscribe(value => {
|
||||||
|
user = value;
|
||||||
|
})
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
if (localStorage.getItem("pk-token")) {
|
||||||
|
login(localStorage.getItem("pk-token"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
async function login(token: string) {
|
||||||
|
loading = true;
|
||||||
|
const api = new PKAPI();
|
||||||
|
try {
|
||||||
|
if (!token) {
|
||||||
|
throw new Error("Token cannot be empty.")
|
||||||
|
}
|
||||||
|
const res: Sys = await api.getSystem({token: token});
|
||||||
|
localStorage.setItem("pk-token", token);
|
||||||
|
localStorage.setItem("pk-user", JSON.stringify(res));
|
||||||
|
err = null;
|
||||||
|
loggedIn.update(() => true);
|
||||||
|
currentUser.update(() => res);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
localStorage.removeItem("pk-token");
|
||||||
|
localStorage.removeItem("pk-user");
|
||||||
|
currentUser.update(() => null);
|
||||||
|
err = error.message;
|
||||||
|
}
|
||||||
|
loading = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function logout() {
|
||||||
|
token = null;
|
||||||
|
localStorage.removeItem("pk-token");
|
||||||
|
localStorage.removeItem("pk-user");
|
||||||
|
loggedIn.update(() => false);
|
||||||
|
currentUser.update(() => null);
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Container>
|
||||||
|
<Row>
|
||||||
|
<Col class="mx-auto" xs={12} lg={9}>
|
||||||
|
{#if err}
|
||||||
|
<Alert color="danger" >{err}</Alert>
|
||||||
|
{/if}
|
||||||
|
<Card class="mb-4">
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle style="margin-top: 8px; outline: none;">
|
||||||
|
<div class="icon d-inline-block">
|
||||||
|
<FaLockOpen />
|
||||||
|
</div>Log in {#if loading} <div style="float: right"><Spinner color="primary" /></div> {/if}
|
||||||
|
</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardBody>
|
||||||
|
{#if loading}
|
||||||
|
verifying login...
|
||||||
|
{:else if isLoggedIn}
|
||||||
|
{#if user && user.name}
|
||||||
|
<p>Welcome back, <b>{user.name}</b>!</p>
|
||||||
|
{:else}
|
||||||
|
<p>Welcome back!</p>
|
||||||
|
{/if}
|
||||||
|
<Link to="/dash"><Button style="float: left;" color='primary'>Go to dash</Button></Link><Button style="float: right;" color='danger' on:click={logout}>Log out</Button>
|
||||||
|
{:else}
|
||||||
|
<Row>
|
||||||
|
<Label>Enter your token here. You can get this by using <b>pk;token</b></Label>
|
||||||
|
<Col xs={12} md={10}>
|
||||||
|
<Input class="mb-2" type="text" bind:value={token}/>
|
||||||
|
</Col>
|
||||||
|
<Col xs={12} md={2}>
|
||||||
|
<Button style="width: 100%" color="primary" on:click={() => login(token)}>Submit</Button>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
{/if}
|
||||||
|
</CardBody>
|
||||||
|
</Card>
|
||||||
|
{#if isLoggedIn}
|
||||||
|
<Card>
|
||||||
|
<CardBody>
|
||||||
|
Some cool stuff will go here.
|
||||||
|
</CardBody>
|
||||||
|
</Card>
|
||||||
|
{/if}
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Container>
|
25
src/stores.ts
Normal file
25
src/stores.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { writable } from 'svelte/store';
|
||||||
|
|
||||||
|
export const loggedIn = writable(false);
|
||||||
|
|
||||||
|
/* export const user = writable({
|
||||||
|
id: null,
|
||||||
|
uuid: null,
|
||||||
|
name: null,
|
||||||
|
description: null,
|
||||||
|
tag: null,
|
||||||
|
avatar_url: null,
|
||||||
|
banner: null,
|
||||||
|
timezone: null,
|
||||||
|
created: null,
|
||||||
|
color: null,
|
||||||
|
privacy: {
|
||||||
|
description_privacy: null,
|
||||||
|
member_list_privacy: null,
|
||||||
|
front_privacy: null,
|
||||||
|
front_history_privacy: null,
|
||||||
|
group_list_privacy: null
|
||||||
|
}
|
||||||
|
}); */
|
||||||
|
|
||||||
|
export const currentUser = writable(null);
|
53
style.css
Normal file
53
style.css
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
|
||||||
|
.icon {
|
||||||
|
height: 1.5em;
|
||||||
|
width: 1.5em;
|
||||||
|
margin-right: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar {
|
||||||
|
height: 2.5em;
|
||||||
|
width: 2.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
border: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.banner {
|
||||||
|
z-index: -200;
|
||||||
|
width: 100vw;
|
||||||
|
height: 40vh;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
background-size: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bq {
|
||||||
|
padding-left: 0.5em;
|
||||||
|
margin: 0.25em 0 0.25em 0;
|
||||||
|
border-left: 4px solid rgba(128, 128, 128, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-link {
|
||||||
|
background-color: var(--bs-body-bg) !important;
|
||||||
|
border-color: rgba(128, 128, 128, 0.3) !important;
|
||||||
|
border-bottom-color: transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-tabs {
|
||||||
|
gap: 0.25em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-tabs {
|
||||||
|
border-bottom: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* .nav-tabs {
|
||||||
|
justify-content: center !important;
|
||||||
|
} */
|
Loading…
Reference in New Issue
Block a user