init
|
|
@ -0,0 +1,52 @@
|
|||
# This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node
|
||||
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs
|
||||
|
||||
name: Meteorite Push to Prod
|
||||
|
||||
on:
|
||||
workflow_dispatch
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [16.x]
|
||||
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: 'npm'
|
||||
- run: npm ci
|
||||
- run: npm run build --if-present
|
||||
|
||||
- name: Copy files to serv
|
||||
uses: garygrossgarten/github-action-scp@release
|
||||
with:
|
||||
local: build
|
||||
remote: /root/meteoriterewrite/temp
|
||||
host: ${{secrets.IPCLOUD}}
|
||||
username: root
|
||||
concurrency: 10
|
||||
privateKey: ${{secrets.SSH}}
|
||||
|
||||
- name: restart server and shit
|
||||
# You may pin to the exact commit or the version.
|
||||
# uses: appleboy/ssh-action@b60142998894e495c513803efc6d5d72a72c968a
|
||||
uses: appleboy/ssh-action@v0.1.8
|
||||
with:
|
||||
key: ${{secrets.SSH}}
|
||||
host: ${{secrets.IPCLOUD}}
|
||||
username: root
|
||||
script: |
|
||||
cd /root/meteoriterewrite
|
||||
rm -rf build
|
||||
mv temp build
|
||||
cd /root/SushiBlox-Website
|
||||
npm run prodreload
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
.DS_Store
|
||||
node_modules
|
||||
/build
|
||||
/.svelte-kit
|
||||
/package
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
vite.config.js.timestamp-*
|
||||
vite.config.ts.timestamp-*
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
# create-svelte
|
||||
## Hey idiots to push to PRODUCTION go to actions tab and use Meteorite Push to Prod ONLY USE THIS WHEN READY
|
||||
|
||||
Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/master/packages/create-svelte).
|
||||
|
||||
## Creating a project
|
||||
|
||||
If you're seeing this, you've probably already done this step. Congrats!
|
||||
|
||||
```bash
|
||||
# create a new project in the current directory
|
||||
npm create svelte@latest
|
||||
|
||||
# create a new project in my-app
|
||||
npm create svelte@latest my-app
|
||||
```
|
||||
|
||||
## Developing
|
||||
|
||||
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
|
||||
# or start the server and open the app in a new browser tab
|
||||
npm run dev -- --open
|
||||
```
|
||||
|
||||
## Building
|
||||
|
||||
To create a production version of your app:
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
You can preview the production build with `npm run preview`.
|
||||
|
||||
> To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment.
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
{
|
||||
"name": "meteoriterewrite",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@skeletonlabs/skeleton": "^0.124.2",
|
||||
"@sveltejs/adapter-auto": "^2.0.0",
|
||||
"@sveltejs/adapter-node": "^1.1.4",
|
||||
"@sveltejs/kit": "^1.5.0",
|
||||
"@tailwindcss/forms": "^0.5.3",
|
||||
"@tailwindcss/typography": "^0.5.8",
|
||||
"autoprefixer": "^10.4.13",
|
||||
"postcss": "^8.4.21",
|
||||
"svelte": "^3.56.0",
|
||||
"svelte-check": "^3.0.3",
|
||||
"svelte-feather-icons": "^4.0.0",
|
||||
"svelte-hcaptcha": "^0.1.1",
|
||||
"svelte-preprocess": "^5.0.1",
|
||||
"tailwindcss": "^3.2.4",
|
||||
"tslib": "^2.4.1",
|
||||
"typescript": "^4.9.3",
|
||||
"vite": "^4.0.0"
|
||||
},
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@yaireo/relative-time": "^1.0.3",
|
||||
"dotenv": "^16.0.3",
|
||||
"lucide-svelte": "^0.216.0"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
// See https://kit.svelte.dev/docs/types#app
|
||||
// for information about these interfaces
|
||||
// and what to do when importing types
|
||||
declare namespace App {
|
||||
|
||||
interface Locals {
|
||||
user: User | null
|
||||
jwt: cookie | null
|
||||
useragent: useragent | null
|
||||
}
|
||||
// interface Locals {}
|
||||
// interface PageData {}
|
||||
// interface Error {}
|
||||
// interface Platform {}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
<!DOCTYPE html >
|
||||
<html lang="en" class="dark">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
<body data-sveltekit-preload-data="hover" data-theme="crimson">
|
||||
<div style="display: contents" class="h-full overflow-hidden">%sveltekit.body%</div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
/*place global styles here */
|
||||
html, body { @apply h-full overflow-hidden; }
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
<script lang="ts">
|
||||
import Itemcard from "../itemcard.svelte";
|
||||
|
||||
|
||||
let currentPage = 1;
|
||||
let maxiumumPage = 1;
|
||||
let currentCategory = "All"
|
||||
let queuecount = 0
|
||||
|
||||
export let jwt:string
|
||||
|
||||
let items: [];
|
||||
async function updateItems(){
|
||||
|
||||
const response = await fetch('admin/queue',{method: "POST", body: JSON.stringify({sort: currentCategory,page: currentPage}),headers: {"content-type": "application/json",'Authorization': jwt,}});
|
||||
const data = await response.json()
|
||||
if (!data.error){
|
||||
if (items){
|
||||
items.length = 0
|
||||
items = items
|
||||
}
|
||||
items = data.data
|
||||
items = items
|
||||
maxiumumPage = data.pages
|
||||
queuecount = data.count
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
function setPage(value: number){
|
||||
if (currentPage-value >= 1 && currentPage-value <= maxiumumPage){
|
||||
currentPage -= value
|
||||
updateItems()
|
||||
}
|
||||
}
|
||||
function setCategory(event: any){
|
||||
currentCategory = event.target.innerText
|
||||
currentPage = 1
|
||||
updateItems()
|
||||
}
|
||||
async function moderateaction(action: any,itemid: Number){
|
||||
console.log(action)
|
||||
console.log(itemid)
|
||||
const itemactionresult = await fetch('/api/moderate/queue', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': jwt,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
action,
|
||||
itemid
|
||||
})
|
||||
})
|
||||
const itemaction = await itemactionresult.json()
|
||||
updateItems()
|
||||
|
||||
}
|
||||
updateItems()
|
||||
</script>
|
||||
|
||||
<div class="grow">
|
||||
<h2 class="">Queue Count: {queuecount} </h2>
|
||||
<div class="flex flex-col flex-wrap md:grid sm:grid-cols-6 sm:grid-rows-5 gap-2">
|
||||
{#if items}
|
||||
{#each items as {Name, ItemId, Creator, Type}}
|
||||
<Itemcard itemname={Name} itemid={ItemId} moderation={true} type={Type} moderate={moderateaction}/>
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
<div class="flex flex-row space-x-2 justify-center">
|
||||
<button on:click={() => {setPage(1)}} class="btn btn-sm bg-surface-600 rounded-md"><</button>
|
||||
<h5 class="">{currentPage} / {maxiumumPage}</h5>
|
||||
<button on:click={() => {setPage(-1)}} class="btn btn-sm bg-surface-600 rounded-md">></button>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
<script lang="ts">
|
||||
let disabled = false
|
||||
let message = {error: false, message: ""}
|
||||
|
||||
let userid : any
|
||||
let username : any
|
||||
export let jwt: string
|
||||
export let data
|
||||
async function searchuser(){
|
||||
|
||||
const response = await fetch("admin/moderateuserlookup", {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': jwt,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
userid: userid??"",
|
||||
username: username??""
|
||||
})
|
||||
})
|
||||
const responsejson = await response.json()
|
||||
if (!responsejson.error){
|
||||
message.message = "woo"
|
||||
message.error = false
|
||||
data = responsejson.data
|
||||
}else{
|
||||
message.message = responsejson.error
|
||||
message.error = true
|
||||
}
|
||||
|
||||
}
|
||||
$:if (!userid && !username){
|
||||
disabled = true
|
||||
message.message = "Enter userid or username"
|
||||
message.error = true
|
||||
}else if(userid && username){
|
||||
disabled = true
|
||||
message.message = "You can only search userid or username dummy"
|
||||
message.error = true
|
||||
}
|
||||
else{
|
||||
disabled = false
|
||||
message.message = ""
|
||||
message.error = false
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<div class="flex flex-wrap space-y-2">
|
||||
|
||||
<input type="text" bind:value={userid} placeholder="UserID" class="input input-bordered input-primary w-full rounded-md">
|
||||
<div class="w-full !text-xs">or</div>
|
||||
<input type="text" bind:value={username} placeholder="Username" class="input input-bordered input-primary w-full rounded-md">
|
||||
<div class="!text-xs mt-6 {message.error === true ? 'text-error-600' : 'text-success-600'}">{message.message??""}</div>
|
||||
<button type="submit" on:click={searchuser} class="btn variant-filled-primary btn-sm w-full rounded-md" disabled={disabled}>Search</button>
|
||||
|
||||
</div>
|
||||
|
|
@ -0,0 +1,144 @@
|
|||
<script lang="ts">
|
||||
import { Avatar } from "@skeletonlabs/skeleton";
|
||||
|
||||
export let lookupdata:any
|
||||
export let jwt:string
|
||||
let lookupaction = "Banned"
|
||||
let banreason: string
|
||||
let expirationdate: string
|
||||
let disabled = false
|
||||
let message = {error: false, message: ""}
|
||||
|
||||
$:if (!banreason || banreason === ""){
|
||||
disabled = true
|
||||
message.message = "Enter ban reason"
|
||||
message.error = true
|
||||
}else if(!expirationdate && lookupaction === "Banned"){
|
||||
disabled = true
|
||||
message.message = "Date required"
|
||||
message.error = true
|
||||
}else{
|
||||
disabled = false
|
||||
message.message = ""
|
||||
message.error = false
|
||||
}
|
||||
|
||||
async function submitaction(){
|
||||
const response = await fetch("admin/moderateuser", {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({userid: lookupdata.userid,reason:banreason,unbantime:expirationdate??"0",Type:lookupaction}),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': jwt,
|
||||
}
|
||||
})
|
||||
const json = await response.json()
|
||||
if (!json.error){
|
||||
message.message = json.message
|
||||
message.error = false
|
||||
}else{
|
||||
message.message = json.error
|
||||
message.error = true
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
<div class="w-full flex flex-row gap-6">
|
||||
|
||||
<div>
|
||||
|
||||
<h5>Action:</h5>
|
||||
|
||||
<label>
|
||||
<input type=radio bind:group={lookupaction} value="Banned">
|
||||
Ban
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<input type=radio bind:group={lookupaction} value="Permanent Ban">
|
||||
Permanent Ban
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<input type=radio bind:group={lookupaction} value="Warning">
|
||||
Warning
|
||||
</label>
|
||||
|
||||
{#if lookupaction === "Banned"}
|
||||
<input bind:value={expirationdate} type="date" />
|
||||
{/if}
|
||||
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-y-1">
|
||||
|
||||
<h5>Auto Fill: </h5>
|
||||
<button type="submit" on:click={()=> {banreason="real"}} class="btn variant-filled-primary btn-sm w-full rounded-md" >Exploiting</button>
|
||||
<button type="submit" on:click={()=> {banreason="Adult content is not allowed on Meteorite. You are not allowed to create new accounts."}} class="btn variant-filled-primary btn-sm w-full rounded-md" >NSFW</button>
|
||||
</div>
|
||||
|
||||
<div class="grow space-y-2">
|
||||
<h5>Moderator Note:</h5>
|
||||
<textarea bind:value={banreason} class="input input-bordered input-primary w-full rounded-md h-32"></textarea>
|
||||
<div class="!text-xs mt-6 {message.error === true ? 'text-error-600' : 'text-success-600'}">{message.message??""}</div>
|
||||
<div class="flex flex-row justify-around gap-4">
|
||||
<button type="submit" on:click={submitaction} class="btn variant-filled-primary btn-base grow rounded-md"disabled={disabled} >Submit</button>
|
||||
<button type="submit" on:click={() => {lookupdata = null}} class="btn variant-filled-primary btn-base rounded-md" >Close</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row justify-around w-full border-none gap-8">
|
||||
<div class="w-96 flex flex-wrap flex-row bg-surface-700 rounded-md p-4 border-none divide-y">
|
||||
<div class="flex flex-row w-full">
|
||||
<h5 class="font-bold grow">{lookupdata.username}</h5>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row w-full grow">
|
||||
<a class="unstyled" href="/users/{lookupdata.userid}"><Avatar src="/api/thumbnailrender/?id={lookupdata.userid}" background="" width="w-32" /></a>
|
||||
|
||||
<div>
|
||||
|
||||
{#if JSON.parse(lookupdata.moderation).status.toUpperCase() != "OK"}
|
||||
<h5 class="!text-sm flex flex-row gap-1">Moderation Status: <h5 class="!text-sm text-error-600">{JSON.parse(lookupdata.moderation).status}</h5></h5>
|
||||
{:else}
|
||||
<h5 class="!text-sm flex flex-row gap-1">Moderation Status: <h5 class="!text-sm text-success-600">{JSON.parse(lookupdata.moderation).status}</h5></h5>
|
||||
{/if}
|
||||
<h5 class="!text-sm">Rocks: {lookupdata.coins}</h5>
|
||||
<h5 class="!text-sm">Discord ID: {lookupdata.discordid??"Not Linked"}</h5>
|
||||
<a><h5 class="!text-sm">View Screenshots</h5></a>
|
||||
<a><h5 class="!text-sm">View Identity</h5></a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<div class="table-container rounded-none h-52">
|
||||
<h5>Moderation History</h5>
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Type</th>
|
||||
<th>Reason</th>
|
||||
<th>BannedBy</th>
|
||||
<th>Date</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#if lookupdata.moderationhistory}
|
||||
{#each lookupdata.moderationhistory as {status, Reason, BannedBy, Date}}
|
||||
<tr>
|
||||
<td>{status}</td>
|
||||
<td style="white-space: nowrap; text-overflow:ellipsis; overflow: hidden; max-width:1px;">{Reason}</td>
|
||||
<td>{BannedBy}</td>
|
||||
<td>{Date}</td>
|
||||
</tr>
|
||||
{/each}
|
||||
{/if}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
<script lang="ts">
|
||||
let message = {error: false, message: ""}
|
||||
let disabled = false
|
||||
let AdId = ""
|
||||
export let jwt: string
|
||||
export let itemid: string
|
||||
export let type: string
|
||||
|
||||
async function advertise(){
|
||||
const result = await fetch('/api/advertise', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': jwt,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
itemid,
|
||||
AdId,
|
||||
type
|
||||
})
|
||||
})
|
||||
const advertiseresult = await result.json()
|
||||
if (!advertiseresult.error){
|
||||
message.message = advertiseresult.message
|
||||
message.error = false
|
||||
}else{
|
||||
message.message = advertiseresult.error
|
||||
message.error = true
|
||||
}
|
||||
}
|
||||
|
||||
$:if (!AdId){
|
||||
disabled = true
|
||||
message.message = "Ad ID not inputted"
|
||||
message.error = true
|
||||
}else{
|
||||
disabled = false
|
||||
message.message = ""
|
||||
message.error = false
|
||||
}
|
||||
</script>
|
||||
<h5>Advertise</h5>
|
||||
|
||||
<div class="p-2 space-y-2">
|
||||
<label class="label">
|
||||
<span>Ad ID</span>
|
||||
<input type="number" bind:value={AdId} class="input input-bordered input-primary w-full rounded-md" required>
|
||||
</label>
|
||||
|
||||
<div>
|
||||
|
||||
<h5 class="!text-xs mt-6 {message.error === true ? 'text-error-600' : 'text-success-600'}">{message.message??""}</h5>
|
||||
<button on:click={advertise} class="btn variant-filled-primary btn-base w-full rounded-md" disabled={disabled}>
|
||||
Advertise for 10 Rocks (For 24 hours)
|
||||
</button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,298 @@
|
|||
<script lang="ts">
|
||||
let bodypart = 'all'
|
||||
let color: any
|
||||
export let jwt: string;
|
||||
import { toastStore } from '@skeletonlabs/skeleton';
|
||||
import type { ToastSettings } from '@skeletonlabs/skeleton';
|
||||
const t: ToastSettings = {
|
||||
message: 'resp',
|
||||
// Optional: Presets for primary | secondary | tertiary | warning
|
||||
preset: 'primary',
|
||||
// Optional: The auto-hide settings
|
||||
autohide: true,
|
||||
timeout: 2000
|
||||
};
|
||||
async function changebodycolor(){
|
||||
const changebodycolorresp = await fetch("/api/bodycolorupdate", {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': jwt,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
Type: bodypart,
|
||||
color: color
|
||||
})
|
||||
})
|
||||
const changebodycolorjson = await changebodycolorresp.json()
|
||||
t.message = changebodycolorjson.error??changebodycolorjson.message
|
||||
if (!changebodycolorjson.error){
|
||||
t.preset = "primary"
|
||||
}else{
|
||||
t.preset = "error"
|
||||
}
|
||||
toastStore.trigger(t)
|
||||
|
||||
}
|
||||
|
||||
$:if (bodypart && color ){
|
||||
changebodycolor()
|
||||
color = undefined
|
||||
}
|
||||
|
||||
</script>
|
||||
<h5>Skin Tone by Body Parts</h5>
|
||||
|
||||
<div class="flex flex-row">
|
||||
|
||||
<div class="space-y-2 grow truncate p-2">
|
||||
|
||||
<label>
|
||||
<input type=radio bind:group={bodypart} value="all">
|
||||
All
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<input type=radio bind:group={bodypart} value="Head">
|
||||
Head
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<input type=radio bind:group={bodypart} value="Torso">
|
||||
Torso
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<input type=radio bind:group={bodypart} value="Left Arm">
|
||||
Left Arm
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<input type=radio bind:group={bodypart} value="Right Arm">
|
||||
Right Arm
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<input type=radio bind:group={bodypart} value="Left Leg">
|
||||
Left Leg
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<input type=radio bind:group={bodypart} value="Right Leg">
|
||||
Right Leg
|
||||
</label>
|
||||
|
||||
</div>
|
||||
<div class=" overflow-y-scroll gap-2 h-96 grid grid-cols-10 grid-flow-row auto-cols-max">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="1" style="background-color:rgb(242, 243, 243);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="2" style="background-color:rgb(161, 165, 162);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="3" style="background-color:rgb(249, 233, 153);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="5" style="background-color:rgb(215, 197, 154);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="6" style="background-color:rgb(194, 218, 184);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="9" style="background-color:rgb(232, 186, 200);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="11" style="background-color:rgb(128, 187, 219);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="12" style="background-color:rgb(203, 132, 66);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="18" style="background-color:rgb(204, 142, 105);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="21" style="background-color:rgb(196, 40, 28);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="22" style="background-color:rgb(196, 112, 160);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="23" style="background-color:rgb(13, 105, 172);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="24" style="background-color:rgb(245, 205, 48);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="25" style="background-color:rgb(98, 71, 50);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="26" style="background-color:rgb(27, 42, 53);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="27" style="background-color:rgb(109, 110, 108);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="28" style="background-color:rgb(40, 127, 71);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="29" style="background-color:rgb(161, 196, 140);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="36" style="background-color:rgb(243, 207, 155);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="37" style="background-color:rgb(75, 151, 75);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="38" style="background-color:rgb(160, 95, 53);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="39" style="background-color:rgb(193, 202, 222);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="40" style="background-color:rgb(236, 236, 236);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="41" style="background-color:rgb(205, 84, 75);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="42" style="background-color:rgb(193, 223, 240);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="43" style="background-color:rgb(123, 182, 232);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="44" style="background-color:rgb(247, 241, 141);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="45" style="background-color:rgb(180, 210, 228);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="47" style="background-color:rgb(217, 133, 108);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="48" style="background-color:rgb(132, 182, 141);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="49" style="background-color:rgb(248, 241, 132);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="50" style="background-color:rgb(236, 232, 222);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="100" style="background-color:rgb(238, 196, 182);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="101" style="background-color:rgb(218, 134, 122);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="102" style="background-color:rgb(110, 153, 202);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="103" style="background-color:rgb(199, 193, 183);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="104" style="background-color:rgb(107, 50, 124);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="105" style="background-color:rgb(226, 155, 64);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="106" style="background-color:rgb(218, 133, 65);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="107" style="background-color:rgb(0, 143, 156);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="108" style="background-color:rgb(104, 92, 67);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="110" style="background-color:rgb(67, 84, 147);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="111" style="background-color:rgb(191, 183, 177);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="112" style="background-color:rgb(104, 116, 172);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="113" style="background-color:rgb(229, 173, 200);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="115" style="background-color:rgb(199, 210, 60);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="116" style="background-color:rgb(85, 165, 175);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="118" style="background-color:rgb(183, 215, 213);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="119" style="background-color:rgb(164, 189, 71);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="120" style="background-color:rgb(217, 228, 167);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="121" style="background-color:rgb(231, 172, 88);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="123" style="background-color:rgb(211, 111, 76);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="124" style="background-color:rgb(146, 57, 120);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="125" style="background-color:rgb(234, 184, 146);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="126" style="background-color:rgb(165, 165, 203);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="127" style="background-color:rgb(220, 188, 129);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="128" style="background-color:rgb(174, 122, 89);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="131" style="background-color:rgb(156, 163, 168);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="133" style="background-color:rgb(213, 115, 61);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="134" style="background-color:rgb(216, 221, 86);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="135" style="background-color:rgb(116, 134, 157);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="136" style="background-color:rgb(135, 124, 144);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="137" style="background-color:rgb(224, 152, 100);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="138" style="background-color:rgb(149, 138, 115);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="140" style="background-color:rgb(32, 58, 86);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="141" style="background-color:rgb(39, 70, 45);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="143" style="background-color:rgb(207, 226, 247);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="145" style="background-color:rgb(121, 136, 161);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="146" style="background-color:rgb(149, 142, 163);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="147" style="background-color:rgb(147, 135, 103);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="148" style="background-color:rgb(87, 88, 87);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="149" style="background-color:rgb(22, 29, 50);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="150" style="background-color:rgb(171, 173, 172);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="151" style="background-color:rgb(120, 144, 130);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="153" style="background-color:rgb(149, 121, 119);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="154" style="background-color:rgb(123, 46, 47);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="157" style="background-color:rgb(255, 246, 123);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="158" style="background-color:rgb(225, 164, 194);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="168" style="background-color:rgb(117, 108, 98);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="176" style="background-color:rgb(151, 105, 91);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="178" style="background-color:rgb(180, 132, 85);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="179" style="background-color:rgb(137, 135, 136);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="180" style="background-color:rgb(215, 169, 75);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="190" style="background-color:rgb(249, 214, 46);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="191" style="background-color:rgb(232, 171, 45);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="192" style="background-color:rgb(105, 64, 40);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="193" style="background-color:rgb(207, 96, 36);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="194" style="background-color:rgb(163, 162, 165);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="195" style="background-color:rgb(70, 103, 164);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="196" style="background-color:rgb(35, 71, 139);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="198" style="background-color:rgb(142, 66, 133);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="199" style="background-color:rgb(99, 95, 98);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="200" style="background-color:rgb(130, 138, 93);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="208" style="background-color:rgb(229, 228, 223);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="209" style="background-color:rgb(176, 142, 68);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="210" style="background-color:rgb(112, 149, 120);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="211" style="background-color:rgb(121, 181, 181);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="212" style="background-color:rgb(159, 195, 233);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="213" style="background-color:rgb(108, 129, 183);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="216" style="background-color:rgb(144, 76, 42);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="217" style="background-color:rgb(124, 92, 70);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="218" style="background-color:rgb(150, 112, 159);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="219" style="background-color:rgb(107, 98, 155);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="220" style="background-color:rgb(167, 169, 206);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="221" style="background-color:rgb(205, 98, 152);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="222" style="background-color:rgb(228, 173, 200);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="223" style="background-color:rgb(220, 144, 149);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="224" style="background-color:rgb(240, 213, 160);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="225" style="background-color:rgb(235, 184, 127);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="226" style="background-color:rgb(253, 234, 141);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="232" style="background-color:rgb(125, 187, 221);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="268" style="background-color:rgb(52, 43, 117);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="301" style="background-color:rgb(80, 109, 84);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="302" style="background-color:rgb(91, 93, 105);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="303" style="background-color:rgb(0, 16, 176);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="304" style="background-color:rgb(44, 101, 29);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="305" style="background-color:rgb(82, 124, 174);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="306" style="background-color:rgb(51, 88, 130);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="307" style="background-color:rgb(16, 42, 220);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="308" style="background-color:rgb(61, 21, 133);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="309" style="background-color:rgb(52, 142, 64);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="310" style="background-color:rgb(91, 154, 76);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="311" style="background-color:rgb(159, 161, 172);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="312" style="background-color:rgb(89, 34, 89);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="313" style="background-color:rgb(31, 128, 29);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="314" style="background-color:rgb(159, 173, 192);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="315" style="background-color:rgb(9, 137, 207);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="316" style="background-color:rgb(123, 0, 123);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="317" style="background-color:rgb(124, 156, 107);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="318" style="background-color:rgb(138, 171, 133);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="319" style="background-color:rgb(185, 196, 177);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="320" style="background-color:rgb(202, 203, 209);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="321" style="background-color:rgb(167, 94, 155);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="322" style="background-color:rgb(123, 47, 123);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="323" style="background-color:rgb(148, 190, 129);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="324" style="background-color:rgb(168, 189, 153);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="325" style="background-color:rgb(223, 223, 222);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="327" style="background-color:rgb(151, 0, 0);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="328" style="background-color:rgb(177, 229, 166);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="329" style="background-color:rgb(152, 194, 219);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="330" style="background-color:rgb(255, 152, 220);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="331" style="background-color:rgb(255, 89, 89);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="332" style="background-color:rgb(117, 0, 0);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="333" style="background-color:rgb(239, 184, 56);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="334" style="background-color:rgb(248, 217, 109);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="335" style="background-color:rgb(231, 231, 236);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="336" style="background-color:rgb(199, 212, 228);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="337" style="background-color:rgb(255, 148, 148);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="338" style="background-color:rgb(190, 104, 98);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="339" style="background-color:rgb(86, 36, 36);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="340" style="background-color:rgb(241, 231, 199);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="341" style="background-color:rgb(254, 243, 187);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="342" style="background-color:rgb(224, 178, 208);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="343" style="background-color:rgb(212, 144, 189);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="344" style="background-color:rgb(150, 85, 85);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="345" style="background-color:rgb(143, 76, 42);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="346" style="background-color:rgb(211, 190, 150);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="347" style="background-color:rgb(226, 220, 188);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="348" style="background-color:rgb(237, 234, 234);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="349" style="background-color:rgb(233, 218, 218);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="350" style="background-color:rgb(136, 62, 62);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="351" style="background-color:rgb(188, 155, 93);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="352" style="background-color:rgb(199, 172, 120);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="353" style="background-color:rgb(202, 191, 163);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="354" style="background-color:rgb(187, 179, 178);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="355" style="background-color:rgb(108, 88, 75);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="356" style="background-color:rgb(160, 132, 79);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="357" style="background-color:rgb(149, 137, 136);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="358" style="background-color:rgb(171, 168, 158);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="359" style="background-color:rgb(175, 148, 131);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="360" style="background-color:rgb(150, 103, 102);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="361" style="background-color:rgb(86, 66, 54);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="362" style="background-color:rgb(126, 104, 63);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="363" style="background-color:rgb(105, 102, 92);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="364" style="background-color:rgb(90, 76, 66);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="365" style="background-color:rgb(106, 57, 9);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="1001" style="background-color:rgb(248, 248, 248);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="1002" style="background-color:rgb(205, 205, 205);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="1003" style="background-color:rgb(17, 17, 17);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="1004" style="background-color:rgb(255, 0, 0);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="1005" style="background-color:rgb(255, 176, 0);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="1006" style="background-color:rgb(180, 128, 255);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="1007" style="background-color:rgb(163, 75, 75);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="1008" style="background-color:rgb(193, 190, 66);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="1009" style="background-color:rgb(255, 255, 0);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="1010" style="background-color:rgb(0, 0, 255);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="1011" style="background-color:rgb(0, 32, 96);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="1012" style="background-color:rgb(33, 84, 185);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="1013" style="background-color:rgb(4, 175, 236);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="1014" style="background-color:rgb(170, 85, 0);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="1015" style="background-color:rgb(170, 0, 170);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="1016" style="background-color:rgb(255, 102, 204);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="1017" style="background-color:rgb(255, 175, 0);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="1018" style="background-color:rgb(18, 238, 212);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="1019" style="background-color:rgb(0, 255, 255);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="1020" style="background-color:rgb(0, 255, 0);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="1021" style="background-color:rgb(58, 125, 21);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="1022" style="background-color:rgb(127, 142, 100);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="1023" style="background-color:rgb(140, 91, 159);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="1024" style="background-color:rgb(175, 221, 255);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="1025" style="background-color:rgb(255, 201, 201);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="1026" style="background-color:rgb(177, 167, 255);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="1027" style="background-color:rgb(159, 243, 233);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="1028" style="background-color:rgb(204, 255, 204);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="1029" style="background-color:rgb(255, 255, 204);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="1030" style="background-color:rgb(255, 204, 153);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="1031" style="background-color:rgb(98, 37, 209);height:40px;width:40px;">
|
||||
<input type=radio class="rounded-full" bind:group={color} value="1032" style="background-color:rgb(255, 0, 191);height:40px;width:40px;">
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
|
@ -0,0 +1,110 @@
|
|||
<script lang="ts">
|
||||
import { Avatar } from "@skeletonlabs/skeleton";
|
||||
import RelativeTime from '@yaireo/relative-time'
|
||||
import { url } from "$lib/url"
|
||||
const relativeTime = new RelativeTime()
|
||||
|
||||
export let width: String;
|
||||
export let PostText = "Post Comment"
|
||||
export let PlaceholderText = "Write a comment!"
|
||||
export let disabled = true
|
||||
export let AssociatedAssetType: String
|
||||
export let AssociatedAssetId: String
|
||||
export let jwt: string
|
||||
let comment: String
|
||||
let currentPage = 1;
|
||||
let maxiumumPage = 1;
|
||||
//export let type:string
|
||||
|
||||
let loading = true
|
||||
let comments: any
|
||||
$:if (!comment){
|
||||
disabled = true
|
||||
}else{
|
||||
disabled = false
|
||||
}
|
||||
|
||||
async function postComment(){
|
||||
const response = await fetch("/api/comments/post", {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': jwt,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
comment,
|
||||
AssociatedAssetType,
|
||||
AssociatedAssetId
|
||||
})
|
||||
})
|
||||
const responsedata = await response.json()
|
||||
if (!responsedata.error){
|
||||
loadComments()
|
||||
}else{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
async function loadComments(){
|
||||
const res = await fetch(url+"/api/comments/get", {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': jwt,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
AssociatedAssetType,
|
||||
AssociatedAssetId,
|
||||
page: currentPage
|
||||
})
|
||||
})
|
||||
comments = await res.json()
|
||||
if (comments.error){
|
||||
comments = []
|
||||
}
|
||||
maxiumumPage = comments.pages
|
||||
loading = false
|
||||
}
|
||||
loadComments()
|
||||
|
||||
function setPage(value: number){
|
||||
if (currentPage-value >= 1 && currentPage-value <= maxiumumPage){
|
||||
currentPage -= value
|
||||
loadComments()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<div class="bg-surface-700 p-4 space-x-4 space-y-4 flex flex-row flex-wrap max-w-[{width}px] m-0 m-auto">
|
||||
<div class="flex flex-row w-full space-x-4">
|
||||
<textarea class="rounded-md grow input input-bordered input-primary" maxlength={200} bind:value={comment} placeholder={PlaceholderText} />
|
||||
<button on:click={postComment} class="btn mt-6 variant-filled-primary rounded-md "disabled={disabled}>{PostText}</button>
|
||||
</div>
|
||||
|
||||
<div class="w-full">
|
||||
{#if loading === true}
|
||||
<p>Loading...</p>
|
||||
{:else if loading === false && comments.data.length === 0}
|
||||
<p>No comments found.</p>
|
||||
{:else if loading === false}
|
||||
|
||||
{#each comments.data as {content, posterid, date, poster}}
|
||||
<div class="flex flex-row gap-x-6">
|
||||
<a class="unstyled" href="/users/{posterid}"><Avatar src={"/api/thumbnailrender/?id="+posterid+"&type=headshot"} width="w-14" /></a>
|
||||
<div>
|
||||
<a class='truncate !text-base' href="/users/{posterid}">{poster.username}</a>
|
||||
<h5 class="!text-base whitespace-pre-line overflow-hidden">"{content}"</h5>
|
||||
<h5 class="!text-xs">Posted {relativeTime.from(new Date(date))}</h5>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/each}
|
||||
<div class="flex flex-row gap-x-2 justify-center">
|
||||
<button on:click={() => {setPage(1)}} class="btn btn-sm bg-surface-600 rounded-md"><</button>
|
||||
<h5 class="">{currentPage} / {maxiumumPage}</h5>
|
||||
<button on:click={() => {setPage(-1)}} class="btn btn-sm bg-surface-600 rounded-md">></button>
|
||||
</div>
|
||||
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
<script lang="ts">
|
||||
let message = {error: false, message: ""}
|
||||
let disabled = false
|
||||
let itemname: string
|
||||
let itemdesc: string
|
||||
let files: FileList;
|
||||
let price:string
|
||||
let creations: any[] = []
|
||||
|
||||
$:if (!itemname || !price){
|
||||
disabled = true
|
||||
message.message = "Item name and price required."
|
||||
message.error = true
|
||||
}else if (!files){
|
||||
disabled = true
|
||||
message.message = "File required."
|
||||
message.error = true
|
||||
}else{
|
||||
disabled = false
|
||||
message.message = ""
|
||||
message.error = false
|
||||
}
|
||||
|
||||
export let type:string
|
||||
export let jwt: string
|
||||
|
||||
async function upload(){
|
||||
const formData = new FormData();
|
||||
formData.append("itemfile", files[0])
|
||||
formData.append("itemname", itemname)
|
||||
formData.append("description", itemdesc??"No Description")
|
||||
formData.append("price", price)
|
||||
formData.append("Type", type)
|
||||
|
||||
const req = await fetch("/admin/uploaditem", {
|
||||
method: "post",
|
||||
body: formData,
|
||||
headers: {
|
||||
'Authorization': jwt,
|
||||
},
|
||||
});
|
||||
|
||||
const res = await req.json();
|
||||
|
||||
if (!res.error){
|
||||
message.message = "Done!"
|
||||
message.error = false
|
||||
}else{
|
||||
message.message = res.error
|
||||
message.error = true
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
</script>
|
||||
<div class="space-y-2 grow">
|
||||
<h3>{type}</h3>
|
||||
<label class="input-label">
|
||||
|
||||
<span class="pt-3">Find your rbxm:</span>
|
||||
|
||||
<input class="w-fit" accept=".rbxm" bind:files type="file" />
|
||||
|
||||
</label>
|
||||
<label class="input-label gap-8">
|
||||
<span class="pt-3">{type} Name:</span>
|
||||
<input bind:value={itemname} type="text" class="input input-bordered input-primary w-full max-w-md rounded-md" required>
|
||||
</label>
|
||||
|
||||
<label class="input-label gap-8">
|
||||
<span class="pt-3">Description:</span>
|
||||
<input bind:value={itemdesc} type="text" class="input input-bordered input-primary w-full max-w-md rounded-md" required>
|
||||
</label>
|
||||
|
||||
<label class="input-label gap-8">
|
||||
<span class="pt-3">Price:</span>
|
||||
<input bind:value={price} type="number" class="input input-bordered input-primary w-full max-w-md rounded-md" required>
|
||||
</label>
|
||||
|
||||
<h5 class="!text-xs mt-6 {message.error === true ? 'text-error-600' : 'text-success-600'}">{message.message??""}</h5>
|
||||
|
||||
<button on:click={upload} type="submit" class="btn variant-filled-primary btn-sm text-base rounded-md" disabled={disabled}>Upload for 0 Rocks</button>
|
||||
|
||||
<div class="pt-8 space-y-4">
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
<script lang="ts">
|
||||
let message = {error: false, message: ""}
|
||||
let disabled = false
|
||||
let itemname: string
|
||||
let itemdesc: string
|
||||
let files: FileList;
|
||||
let price:string
|
||||
let creations: any[] = []
|
||||
|
||||
$:if (!itemname || !price){
|
||||
disabled = true
|
||||
message.message = "Item name and price required."
|
||||
message.error = true
|
||||
}else if (!files){
|
||||
disabled = true
|
||||
message.message = "File required."
|
||||
message.error = true
|
||||
}else{
|
||||
disabled = false
|
||||
message.message = ""
|
||||
message.error = false
|
||||
}
|
||||
|
||||
export let type:string
|
||||
export let jwt: string
|
||||
|
||||
async function upload(){
|
||||
const formData = new FormData();
|
||||
formData.append("itemfile", files[0])
|
||||
formData.append("itemname", itemname)
|
||||
formData.append("description", itemdesc)
|
||||
formData.append("price", price)
|
||||
formData.append("Type", type)
|
||||
|
||||
const req = await fetch("/admin/uploaditem", {
|
||||
method: "post",
|
||||
body: formData,
|
||||
headers: {
|
||||
'Authorization': jwt,
|
||||
},
|
||||
});
|
||||
|
||||
const res = await req.json();
|
||||
|
||||
if (!res.error){
|
||||
message.message = "Done!"
|
||||
message.error = false
|
||||
}else{
|
||||
message.message = res.error
|
||||
message.error = true
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
</script>
|
||||
<div class="space-y-2 grow">
|
||||
{#if type === "Packages"}
|
||||
<h3>Create a Package</h3>
|
||||
{/if}
|
||||
<label class="input-label">
|
||||
|
||||
<span class="pt-3">Find your rbxm:</span>
|
||||
|
||||
<input class="w-fit" accept=".rbxm" bind:files type="file" />
|
||||
|
||||
</label>
|
||||
<label class="input-label gap-8">
|
||||
{#if type === "Packages"}
|
||||
<span class="pt-3">{type} Name:</span>
|
||||
{/if}
|
||||
<input bind:value={itemname} type="text" class="input input-bordered input-primary w-full max-w-md rounded-md" required>
|
||||
</label>
|
||||
|
||||
<label class="input-label gap-8">
|
||||
<span class="pt-3">Description:</span>
|
||||
<input bind:value={itemdesc} type="text" class="input input-bordered input-primary w-full max-w-md rounded-md" required>
|
||||
</label>
|
||||
|
||||
<label class="input-label gap-8">
|
||||
<span class="pt-3">Price:</span>
|
||||
<input bind:value={price} type="number" class="input input-bordered input-primary w-full max-w-md rounded-md" required>
|
||||
</label>
|
||||
|
||||
<h5 class="!text-xs mt-6 {message.error === true ? 'text-error-600' : 'text-success-600'}">{message.message??""}</h5>
|
||||
|
||||
<button on:click={upload} type="submit" class="btn variant-filled-primary btn-sm text-base rounded-md" disabled={disabled}>Upload for 0 Rocks</button>
|
||||
|
||||
<div class="pt-8 space-y-4">
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
|
@ -0,0 +1,168 @@
|
|||
<script lang="ts">
|
||||
import Createditems from "../develop/createditems.svelte"
|
||||
import { url } from "$lib/url"
|
||||
let message = {error: false, message: ""}
|
||||
let disabled = false
|
||||
let itemname: string
|
||||
let files: FileList;
|
||||
let price:string
|
||||
let associatedgameid:string
|
||||
let creations: any[] = []
|
||||
|
||||
$:if (!itemname){
|
||||
disabled = true
|
||||
message.message = "Item name required."
|
||||
message.error = true
|
||||
}else if (!files){
|
||||
disabled = true
|
||||
message.message = "File required."
|
||||
message.error = true
|
||||
}else if (type === "gamepasses" && (!price || !associatedgameid)){
|
||||
disabled = true
|
||||
message.message = "Price and associated game id required."
|
||||
message.error = true
|
||||
}
|
||||
else{
|
||||
disabled = false
|
||||
message.message = ""
|
||||
message.error = false
|
||||
}
|
||||
|
||||
export let type:string
|
||||
export let jwt: string
|
||||
|
||||
async function updatecreations(){
|
||||
const response = await fetch(url+'/develop/creations',{method: "POST", body: JSON.stringify({type}),headers: {"content-type": "application/json", 'Authorization': jwt,}});
|
||||
const data = await response.json()
|
||||
if (!data.error){
|
||||
creations = data
|
||||
//console.log(creations)
|
||||
}
|
||||
}
|
||||
updatecreations()
|
||||
|
||||
async function upload(){
|
||||
const formData = new FormData();
|
||||
formData.append("assetfile", files[0])
|
||||
formData.append("itemname", itemname)
|
||||
formData.append("price", price)
|
||||
formData.append("gameid", associatedgameid)
|
||||
|
||||
const req = await fetch("/develop/upload"+type, {
|
||||
method: "post",
|
||||
body: formData,
|
||||
headers: {
|
||||
'Authorization': jwt,
|
||||
},
|
||||
});
|
||||
|
||||
const res = await req.json();
|
||||
|
||||
if (!res.error){
|
||||
message.message = "Done!"
|
||||
message.error = false
|
||||
updatecreations()
|
||||
}else{
|
||||
message.message = res.error
|
||||
message.error = true
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
$:type,updatecreations() // if type variable changes run updatecreations again
|
||||
</script>
|
||||
<div class="space-y-2 grow">
|
||||
{#if type === "audios"}
|
||||
<h3>Create a Audio</h3>
|
||||
{:else if type === "badges"}
|
||||
<h3>Create a Badge</h3>
|
||||
{:else if type === "meshes"}
|
||||
<h3>Create a Mesh</h3>
|
||||
{:else if type === "userads"}
|
||||
<h3>Create a User Ad</h3>
|
||||
{:else if type === "gamepasses"}
|
||||
<h3>Create a Gamepass</h3>
|
||||
{:else if type === "videos"}
|
||||
<h3>Create a Video</h3>
|
||||
{/if}
|
||||
<label class="input-label">
|
||||
{#if type === "badges" || type === "userads" || type === "gamepasses"}
|
||||
<span class="pt-3">Find your image:</span>
|
||||
{:else if type === "meshes"}
|
||||
<span class="pt-3">Find your mesh:</span>
|
||||
{:else if type === "audios"}
|
||||
<span class="pt-3">Find your audio:</span>
|
||||
{:else if type === "videos"}
|
||||
<span class="pt-3">Find your video:</span>
|
||||
{/if}
|
||||
|
||||
{#if type === "audios"}
|
||||
<input class="w-fit" accept="audio/mp3" bind:files type="file" />
|
||||
{:else if type === "meshes"}
|
||||
<input class="w-fit" accept=".mesh" bind:files type="file" />
|
||||
{:else if type === "badges" || type === "userads" || type === "gamepasses"}
|
||||
<input class="w-fit" accept="image/png" bind:files type="file" />
|
||||
{:else if type === "videos"}
|
||||
<input class="w-fit" accept=".webm" bind:files type="file" />
|
||||
{/if}
|
||||
|
||||
</label>
|
||||
<label class="input-label gap-8">
|
||||
{#if type === "audios"}
|
||||
<span class="pt-3">Audio Name:</span>
|
||||
{:else if type === "badges"}
|
||||
<span class="pt-3">Badge Name:</span>
|
||||
{:else if type === "meshes"}
|
||||
<span class="pt-3">Mesh Name:</span>
|
||||
{:else if type === "userads"}
|
||||
<span class="pt-3">Ad Name:</span>
|
||||
{:else if type === "gamepasses"}
|
||||
<span class="pt-3">Gamepass Name:</span>
|
||||
{:else if type === "videos"}
|
||||
<span class="pt-3">Video Name:</span>
|
||||
{/if}
|
||||
<input bind:value={itemname} type="text" class="input input-bordered input-primary w-full max-w-md rounded-md" required>
|
||||
</label>
|
||||
|
||||
{#if type === "gamepasses"}
|
||||
<label class="input-label gap-8">
|
||||
<span class="pt-3">Price:</span>
|
||||
<input bind:value={price} type="number" class="input input-bordered input-primary w-full max-w-md rounded-md" required>
|
||||
</label>
|
||||
{/if}
|
||||
|
||||
{#if type === "gamepasses"}
|
||||
<label class="input-label gap-8">
|
||||
<span class="pt-3">Associated game id:</span>
|
||||
<input bind:value={associatedgameid} type="number" class="input input-bordered input-primary w-full max-w-md rounded-md" required>
|
||||
</label>
|
||||
{/if}
|
||||
|
||||
<h5 class="!text-xs mt-6 {message.error === true ? 'text-error-600' : 'text-success-600'}">{message.message??""}</h5>
|
||||
|
||||
<button on:click={upload} type="submit" class="btn variant-filled-primary btn-sm text-base rounded-md" disabled={disabled}>Upload for 0 Rocks</button>
|
||||
|
||||
<div class="pt-8 space-y-4">
|
||||
{#if type === "audios"}
|
||||
<h3>Audios</h3>
|
||||
{:else if type === "badges"}
|
||||
<h3>Badges</h3>
|
||||
{:else if type === "meshes"}
|
||||
<h3>Meshes</h3>
|
||||
{:else if type === "userads"}
|
||||
<h3>User Ads</h3>
|
||||
{:else if type === "gamepasses"}
|
||||
<h3>Gamepasses</h3>
|
||||
{:else if type === "videos"}
|
||||
<h3>Videos</h3>
|
||||
{/if}
|
||||
|
||||
{#each creations as {Name, ItemId, Description}}
|
||||
<Createditems itemname={Name} itemid={ItemId} type={type} />
|
||||
{/each}
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
|
@ -0,0 +1,122 @@
|
|||
<script lang="ts">
|
||||
import { coinstore } from "$lib/coinstore"
|
||||
import Createditems from "../develop/createditems.svelte"
|
||||
import { url } from "$lib/url"
|
||||
let message = {error: false, message: ""}
|
||||
let disabled = false
|
||||
let itemname: string
|
||||
let itemdesc: string
|
||||
let files: FileList;
|
||||
let price:string
|
||||
let creations: any[] = []
|
||||
|
||||
$:if (!itemname || !price){
|
||||
disabled = true
|
||||
message.message = "Item name and price required."
|
||||
message.error = true
|
||||
}else if (parseFloat(price) < 5){
|
||||
disabled = true
|
||||
message.message = "Minimum price is 5 rocks."
|
||||
message.error = true
|
||||
}else if (!files){
|
||||
disabled = true
|
||||
message.message = "File required."
|
||||
message.error = true
|
||||
}
|
||||
else{
|
||||
disabled = false
|
||||
message.message = ""
|
||||
message.error = false
|
||||
}
|
||||
|
||||
export let type:string
|
||||
export let jwt: string
|
||||
|
||||
async function updatecreations(){
|
||||
const response = await fetch(url+'/develop/creations',{method: "POST", body: JSON.stringify({type}),headers: {"content-type": "application/json", 'Authorization': jwt,}});
|
||||
const data = await response.json()
|
||||
if (!data.error){
|
||||
creations = data
|
||||
//console.log(creations)
|
||||
}
|
||||
}
|
||||
updatecreations()
|
||||
|
||||
|
||||
async function upload(){
|
||||
const formData = new FormData();
|
||||
formData.append("clothingfile", files[0])
|
||||
formData.append("clothingname", itemname)
|
||||
formData.append("description", itemdesc??"...")
|
||||
formData.append("price", price)
|
||||
formData.append("type",type)
|
||||
|
||||
|
||||
const req = await fetch("develop/uploadclothing", {
|
||||
method: "post",
|
||||
body: formData,
|
||||
headers: {
|
||||
'Authorization': jwt,
|
||||
},
|
||||
});
|
||||
|
||||
const res = await req.json();
|
||||
|
||||
if (!res.error){
|
||||
message.message = "Done!"
|
||||
message.error = false
|
||||
coinstore.update(n => n - 5)
|
||||
updatecreations()
|
||||
}else{
|
||||
message.message = res.error
|
||||
message.error = true
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
$:type,updatecreations() // if type variable changes run updatecreations again
|
||||
</script>
|
||||
<div class="space-y-2 grow">
|
||||
{#if type === "Shirts"}
|
||||
<h3>Create a Shirt</h3>
|
||||
{:else if type === "Pants"}
|
||||
<h3>Create a Pant</h3>
|
||||
{/if}
|
||||
<label class="input-label">
|
||||
<span class="pt-3">Find your image:</span>
|
||||
<input class="w-fit" accept="image/png" bind:files type="file" />
|
||||
</label>
|
||||
<label class="input-label gap-8">
|
||||
{#if type === "Shirts"}
|
||||
<span class="pt-3">Shirt Name:</span>
|
||||
{:else if type === "Pants"}
|
||||
<span class="pt-3">Pant Name:</span>
|
||||
{/if}
|
||||
<input bind:value={itemname} type="text" class="input input-bordered input-primary w-full max-w-md rounded-md" required>
|
||||
</label>
|
||||
|
||||
<label class="input-label gap-8">
|
||||
<span class="pt-3">Description:</span>
|
||||
<input bind:value={itemdesc} type="text" class="input input-bordered input-primary w-full max-w-md rounded-md" required>
|
||||
</label>
|
||||
|
||||
<label class="input-label gap-8">
|
||||
<span class="pt-3">Price:</span>
|
||||
<input bind:value={price} type="number" class="input input-bordered input-primary w-full max-w-md rounded-md" required>
|
||||
</label>
|
||||
|
||||
<h5 class="!text-xs mt-6 {message.error === true ? 'text-error-600' : 'text-success-600'}">{message.message??""}</h5>
|
||||
|
||||
<button on:click={upload} type="submit" class="btn variant-filled-primary btn-sm text-base rounded-md" disabled={disabled}>Upload for 5 Rocks</button>
|
||||
|
||||
<div class="pt-8 space-y-4">
|
||||
<h3>{type}</h3>
|
||||
|
||||
{#each creations as {Name, ItemId, Description}}
|
||||
<Createditems itemname={Name} itemid={ItemId}/>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
<script lang="ts">
|
||||
|
||||
export let itemname: string
|
||||
export let itemid: string
|
||||
export let avatartype: string
|
||||
export let gearallowed: boolean
|
||||
|
||||
|
||||
|
||||
import { Modal, modalStore } from '@skeletonlabs/skeleton';
|
||||
import type { ModalSettings, ModalComponent } from '@skeletonlabs/skeleton';
|
||||
import Editgamemodal from './modals/editgamemodal.svelte';
|
||||
import type { PageData } from '../../routes/$types';
|
||||
|
||||
export let data: PageData;
|
||||
|
||||
function triggerCustomModal(itemid: string): void {
|
||||
const modalComponent: ModalComponent = {
|
||||
// Pass a reference to your custom component
|
||||
ref: Editgamemodal,
|
||||
// Add your props as key/value pairs
|
||||
props: { jwt: data.jwt, gameid: itemid, avatartype, gearallowed},
|
||||
};
|
||||
const d: ModalSettings = {
|
||||
type: 'component',
|
||||
// NOTE: title, body, response, etc are supported!
|
||||
component: modalComponent,
|
||||
modalClasses: 'w-full max-w-[700px] p-8'
|
||||
};
|
||||
modalStore.trigger(d);
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<div class="flex flex-row gap-4 justify-around w-full">
|
||||
|
||||
<div class="flex flex-row grow gap-2">
|
||||
<img alt={itemname} class="w-16 aspect-video" src="assets/gameassets/thumbnail-{itemid}.png#"/>
|
||||
<a href="/games/{itemid}"><h5 class="!text-sm">{itemname}</h5></a>
|
||||
</div>
|
||||
<svg fill="none" on:click={()=> {triggerCustomModal(itemid)}} stroke="currentColor" height="24" width="24" stroke-width="1.5" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="M10.343 3.94c.09-.542.56-.94 1.11-.94h1.093c.55 0 1.02.398 1.11.94l.149.894c.07.424.384.764.78.93.398.164.855.142 1.205-.108l.737-.527a1.125 1.125 0 011.45.12l.773.774c.39.389.44 1.002.12 1.45l-.527.737c-.25.35-.272.806-.107 1.204.165.397.505.71.93.78l.893.15c.543.09.94.56.94 1.109v1.094c0 .55-.397 1.02-.94 1.11l-.893.149c-.425.07-.765.383-.93.78-.165.398-.143.854.107 1.204l.527.738c.32.447.269 1.06-.12 1.45l-.774.773a1.125 1.125 0 01-1.449.12l-.738-.527c-.35-.25-.806-.272-1.203-.107-.397.165-.71.505-.781.929l-.149.894c-.09.542-.56.94-1.11.94h-1.094c-.55 0-1.019-.398-1.11-.94l-.148-.894c-.071-.424-.384-.764-.781-.93-.398-.164-.854-.142-1.204.108l-.738.527c-.447.32-1.06.269-1.45-.12l-.773-.774a1.125 1.125 0 01-.12-1.45l.527-.737c.25-.35.273-.806.108-1.204-.165-.397-.505-.71-.93-.78l-.894-.15c-.542-.09-.94-.56-.94-1.109v-1.094c0-.55.398-1.02.94-1.11l.894-.149c.424-.07.765-.383.93-.78.165-.398.143-.854-.107-1.204l-.527-.738a1.125 1.125 0 01.12-1.45l.773-.773a1.125 1.125 0 011.45-.12l.737.527c.35.25.807.272 1.204.107.397-.165.71-.505.78-.929l.15-.894z"></path><path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path></svg>
|
||||
|
||||
</div>
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
<script lang="ts">
|
||||
|
||||
export let itemname: string
|
||||
export let itemid: string
|
||||
export let type = ""
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<div class="flex flex-row gap-4 justify-around w-full">
|
||||
|
||||
<div class="flex flex-row grow gap-2">
|
||||
<img alt={itemname} class="w-16" src="/api/thumbnailrender/asset/?id={itemid}"/>
|
||||
<a href="/catalog/{itemid}/{itemname.replace(/[^a-zA-Z ]/g, "").replaceAll(' ', '-')}"><h5 class="!text-sm">{itemname}</h5></a>
|
||||
</div>
|
||||
<svg fill="none" stroke="currentColor" height="24" width="24" stroke-width="1.5" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="M10.343 3.94c.09-.542.56-.94 1.11-.94h1.093c.55 0 1.02.398 1.11.94l.149.894c.07.424.384.764.78.93.398.164.855.142 1.205-.108l.737-.527a1.125 1.125 0 011.45.12l.773.774c.39.389.44 1.002.12 1.45l-.527.737c-.25.35-.272.806-.107 1.204.165.397.505.71.93.78l.893.15c.543.09.94.56.94 1.109v1.094c0 .55-.397 1.02-.94 1.11l-.893.149c-.425.07-.765.383-.93.78-.165.398-.143.854.107 1.204l.527.738c.32.447.269 1.06-.12 1.45l-.774.773a1.125 1.125 0 01-1.449.12l-.738-.527c-.35-.25-.806-.272-1.203-.107-.397.165-.71.505-.781.929l-.149.894c-.09.542-.56.94-1.11.94h-1.094c-.55 0-1.019-.398-1.11-.94l-.148-.894c-.071-.424-.384-.764-.781-.93-.398-.164-.854-.142-1.204.108l-.738.527c-.447.32-1.06.269-1.45-.12l-.773-.774a1.125 1.125 0 01-.12-1.45l.527-.737c.25-.35.273-.806.108-1.204-.165-.397-.505-.71-.93-.78l-.894-.15c-.542-.09-.94-.56-.94-1.109v-1.094c0-.55.398-1.02.94-1.11l.894-.149c.424-.07.765-.383.93-.78.165-.398.143-.854-.107-1.204l-.527-.738a1.125 1.125 0 01.12-1.45l.773-.773a1.125 1.125 0 011.45-.12l.737.527c.35.25.807.272 1.204.107.397-.165.71-.505.78-.929l.15-.894z"></path><path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path></svg>
|
||||
|
||||
</div>
|
||||
|
|
@ -0,0 +1,121 @@
|
|||
<script lang="ts">
|
||||
import { coinstore } from "$lib/coinstore"
|
||||
import type { PageData } from "../../routes/$types";
|
||||
import Createdgames from "../develop/createdgames.svelte"
|
||||
import { url } from "$lib/url"
|
||||
export let data: PageData;
|
||||
let message = {error: false, message: ""}
|
||||
let disabled = false
|
||||
let itemname: string
|
||||
let itemdesc: string
|
||||
let files: FileList;
|
||||
let gamefiles: FileList;
|
||||
let version: string
|
||||
let creations: any[] = []
|
||||
|
||||
$:if (!itemname){
|
||||
disabled = true
|
||||
message.message = "Game name required."
|
||||
message.error = true
|
||||
}else if (!files || !gamefiles){
|
||||
disabled = true
|
||||
message.message = "File required."
|
||||
message.error = true
|
||||
}
|
||||
else{
|
||||
disabled = false
|
||||
message.message = ""
|
||||
message.error = false
|
||||
}
|
||||
|
||||
export let type:string
|
||||
export let jwt: string
|
||||
|
||||
async function updatecreations(){
|
||||
const response = await fetch(url+'/develop/creations',{method: "POST", body: JSON.stringify({type: "games"}),headers: {"content-type": "application/json", 'Authorization': jwt,}});
|
||||
const data = await response.json()
|
||||
if (!data.error){
|
||||
creations = data
|
||||
//console.log(creations)
|
||||
}
|
||||
}
|
||||
updatecreations()
|
||||
|
||||
|
||||
async function upload(){
|
||||
const formData = new FormData();
|
||||
formData.append("gamefile", gamefiles[0])
|
||||
formData.append("thumbnail", files[0])
|
||||
formData.append("gamename", itemname)
|
||||
formData.append("description", itemdesc??"...")
|
||||
formData.append("version",version)
|
||||
|
||||
|
||||
const req = await fetch("/develop/uploadgame", {
|
||||
method: "post",
|
||||
body: formData,
|
||||
headers: {
|
||||
'Authorization': jwt,
|
||||
},
|
||||
});
|
||||
|
||||
const res = await req.json();
|
||||
|
||||
if (!res.error){
|
||||
message.message = "Done!"
|
||||
message.error = false
|
||||
coinstore.update(n => n - 5)
|
||||
updatecreations()
|
||||
}else{
|
||||
message.message = res.error
|
||||
message.error = true
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
$:type,updatecreations() // if type variable changes run updatecreations again
|
||||
</script>
|
||||
<div class="space-y-2 grow">
|
||||
<h3>Create a Game</h3>
|
||||
<label class="input-label">
|
||||
<span class="pt-3">Thumbnail :</span>
|
||||
<input class="w-fit" accept="image/png" bind:files type="file" />
|
||||
</label>
|
||||
<label class="input-label">
|
||||
<span class="pt-3">Game File :</span>
|
||||
<input class="w-fit" accept=".rbxl" bind:files={gamefiles} type="file" />
|
||||
</label>
|
||||
<label class="input-label gap-6">
|
||||
<span class="pt-3">Game Name:</span>
|
||||
<input bind:value={itemname} type="text" class="input input-bordered input-primary w-full max-w-md rounded-md" required>
|
||||
</label>
|
||||
|
||||
<label class="input-label gap-8">
|
||||
<span class="pt-3">Description:</span>
|
||||
<textarea bind:value={itemdesc} class="input input-bordered input-primary w-full max-w-md rounded-md" required></textarea>
|
||||
</label>
|
||||
|
||||
<label class="input-label gap-14">
|
||||
<span class="pt-3">Version:</span>
|
||||
<select bind:value={version} class="select w-full max-w-md">
|
||||
<option value="2020">2020</option>
|
||||
<option value="2018">2018</option>
|
||||
<option value="2016">2016</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<h5 class="!text-xs mt-6 {message.error === true ? 'text-error-600' : 'text-success-600'}">{message.message??""}</h5>
|
||||
|
||||
<button on:click={upload} type="submit" class="btn variant-filled-primary btn-sm text-base rounded-md" disabled={disabled}>Upload for 5 Rocks</button>
|
||||
|
||||
<div class="pt-8 space-y-4">
|
||||
<h3>Games</h3>
|
||||
|
||||
{#each creations as {nameofgame, idofgame, Description, avatartype, gearallowed}}
|
||||
<Createdgames itemname={nameofgame} itemid={idofgame} data={data} avatartype={avatartype} gearallowed={gearallowed}/>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
|
@ -0,0 +1,192 @@
|
|||
<script lang="ts">
|
||||
let title:string;
|
||||
let description:string
|
||||
let files: FileList;
|
||||
|
||||
let disabled = false
|
||||
let disabledupload = false
|
||||
let message = {error: false, message: ""}
|
||||
let messageupload = {error: false, message: ""}
|
||||
let allowmeshes = false
|
||||
import { RadioGroup, RadioItem } from '@skeletonlabs/skeleton';
|
||||
export let jwt: string;
|
||||
export let gameid:string
|
||||
export let avatartype = "PC"
|
||||
export let gearallowed = false
|
||||
|
||||
$:if (!title && !description){
|
||||
disabled = true
|
||||
message.message = "Enter new title or new description."
|
||||
message.error = true
|
||||
}else{
|
||||
disabled = false
|
||||
message.message = ""
|
||||
message.error = false
|
||||
}
|
||||
|
||||
$:if (!files){
|
||||
disabledupload = true
|
||||
messageupload.message = "Choose a file."
|
||||
messageupload.error = true
|
||||
}else{
|
||||
disabledupload = false
|
||||
messageupload.message = ""
|
||||
messageupload.error = false
|
||||
}
|
||||
|
||||
|
||||
|
||||
async function updategame(){
|
||||
const updategameresp = await fetch("/develop/editgame", {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': jwt,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
nameofgame: title,
|
||||
description: description,
|
||||
gameid: gameid.toString()
|
||||
})
|
||||
})
|
||||
const updategamejson = await updategameresp.json()
|
||||
console.log(updategamejson)
|
||||
if (!updategamejson.error){
|
||||
message.message = updategamejson.message
|
||||
message.error = false
|
||||
}else{
|
||||
message.message = updategamejson.error
|
||||
message.error = true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
async function uploadnew(){
|
||||
const formData = new FormData();
|
||||
formData.append("gameid", gameid.toString())
|
||||
formData.append("gamefile", files[0])
|
||||
|
||||
|
||||
const req = await fetch("/develop/editgameupload", {
|
||||
method: "post",
|
||||
body: formData,
|
||||
headers: {
|
||||
'Authorization': jwt,
|
||||
},
|
||||
});
|
||||
|
||||
const res = await req.json();
|
||||
console.log(res)
|
||||
if (!res.error){
|
||||
messageupload.message = res.message
|
||||
messageupload.error = false
|
||||
}else{
|
||||
messageupload.message = res.error
|
||||
messageupload.error = true
|
||||
}
|
||||
}
|
||||
|
||||
async function updateAvatarType(newavatartype: string){
|
||||
if (avatartype != newavatartype){
|
||||
avatartype = newavatartype
|
||||
const updateAvatarType = await fetch("/develop/editavatartype", {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': jwt,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
avatartype,
|
||||
gameid: gameid.toString()
|
||||
})
|
||||
})
|
||||
const updateAvatarTypejson = await updateAvatarType.json()
|
||||
console.log(updateAvatarTypejson)
|
||||
}
|
||||
}
|
||||
|
||||
async function updateGearStatus(newgearstatus: boolean){
|
||||
if (allowmeshes != newgearstatus){
|
||||
allowmeshes = newgearstatus
|
||||
const updateGearStatus = await fetch("/develop/editgearstatus", {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': jwt,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
newgearstatus,
|
||||
gameid: gameid.toString()
|
||||
})
|
||||
})
|
||||
const updateGearStatusjson = await updateGearStatus.json()
|
||||
console.log(updateGearStatusjson)
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<div class="space-y-2 flex flex-row flex-wrap justify-center">
|
||||
|
||||
|
||||
|
||||
<div class="w-full flex flex-row gap-4">
|
||||
|
||||
<div class="space-y-4 w-full">
|
||||
<h5 class="m-auto tex t-center w-full">Edit Game</h5>
|
||||
<label class="label">
|
||||
<span>Title</span>
|
||||
<input type="text" bind:value={title} class="input input-bordered input-primary w-full rounded-md" required>
|
||||
</label>
|
||||
<label class="label">
|
||||
<span>Description</span>
|
||||
<textarea bind:value={description} class="input input-bordered input-primary w-full rounded-md" required></textarea>
|
||||
</label>
|
||||
<!-- svelte-ignore a11y-label-has-associated-control -->
|
||||
<label class="label">
|
||||
<span>Avatar Type</span>
|
||||
<RadioGroup rounded="rounded-md" display="flex flex-row flex-wrap">
|
||||
<RadioItem bind:group={avatartype} on:click={() => {updateAvatarType("R15")}} value="R15">R15</RadioItem>
|
||||
<RadioItem bind:group={avatartype} on:click={() => {updateAvatarType("R6")}} value="R6">R6</RadioItem>
|
||||
<RadioItem bind:group={avatartype} on:click={() => {updateAvatarType("PC")}} value="PC">Player Choice</RadioItem>
|
||||
</RadioGroup>
|
||||
</label>
|
||||
<!-- svelte-ignore a11y-label-has-associated-control -->
|
||||
<label class="label">
|
||||
<span>Allow Gear</span>
|
||||
<RadioGroup rounded="rounded-md" display="flex flex-row flex-wrap">
|
||||
<RadioItem bind:group={gearallowed} on:click={() => {updateGearStatus(true)}} value={true}>Yes</RadioItem>
|
||||
<RadioItem bind:group={gearallowed} on:click={() => {updateGearStatus(false)}} value={false}>No</RadioItem>
|
||||
</RadioGroup>
|
||||
</label>
|
||||
<h5 class="!text-xs mt-6 {message.error === true ? 'text-error-600' : 'text-success-600'}">{message.message??""}</h5>
|
||||
</div>
|
||||
|
||||
<div class="space-y-4 w-full h-full">
|
||||
<h5 class="m-auto tex t-center w-full">Edit File</h5>
|
||||
<!-- svelte-ignore a11y-label-has-associated-control -->
|
||||
<label class="label">
|
||||
<span>RBXL File</span>
|
||||
<input class="w-full" accept=".rbxl" bind:files type="file" />
|
||||
</label>
|
||||
<h5 class="!text-xs {messageupload.error === true ? 'text-error-600' : 'text-success-600'}">{messageupload.message??""}</h5>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row w-full gap-4">
|
||||
<button on:click={updategame} class="btn variant-filled-primary btn-base w-full" disabled={disabled}>
|
||||
Update
|
||||
</button>
|
||||
|
||||
<button on:click={uploadnew} class="btn variant-filled-primary btn-base w-full" disabled={disabledupload}>
|
||||
Upload
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
<script lang="ts">
|
||||
import { Avatar } from "@skeletonlabs/skeleton";
|
||||
import { onMount } from "svelte";
|
||||
export let gamename= "";
|
||||
export let playercount = "";
|
||||
export let idofgame: String;
|
||||
export let version = "";
|
||||
export let visits= "";
|
||||
export let useridofowner = "";
|
||||
export let useragent:string
|
||||
export let username = ""
|
||||
let imageloading = true
|
||||
const onload = (node:HTMLImageElement, callback: VoidFunction) => {
|
||||
if(node.complete) {
|
||||
imageloading = false
|
||||
} else {
|
||||
node.addEventListener('load', callback)
|
||||
|
||||
return {
|
||||
destroy() {
|
||||
node.removeEventListener('load', callback)
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
{#if useragent.includes("Android") === true || useragent.includes("iPhone") === true}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div class="unstyled group" on:click={()=> {document.location.href="/games/"+idofgame+"/"+gamename.replace(/[^a-zA-Z ]/g, "").replaceAll(' ', '-')}}>
|
||||
<div class="card rounded-md card-glass-surface snap-center card-hover w-20 sm:w-40 relative">
|
||||
{#if imageloading}
|
||||
<div class="w-20 h-20 sm:w-40 sm:h-40 rounded-none placeholder animate-pulse"></div>
|
||||
{/if}
|
||||
<img alt="" class="avatar-image w-20 h-20 sm:w-40 sm:h-40 object-cover {imageloading === true ? 'hidden' : ''} " use:onload={()=> imageloading=false} src="/assets/gameassets/thumbnail-{idofgame}.png#" />
|
||||
<p class="truncate w-auto">{gamename}</p>
|
||||
<div class="!text-sm">{playercount} Playing</div>
|
||||
<div class="btn h-2 w-6 sm:w-14 variant-filled-surface rounded-none absolute top-0 right-0 ">{version}</div>
|
||||
<div class="hidden group-hover:block hover:block absolute top-32 sm:top-52">
|
||||
<div class="!text-xs">By <a href="/users/{useridofowner}">{username}</a></div>
|
||||
<div class="!text-xs">{visits??"0"} Visits</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<a class="unstyled group" href="/games/{idofgame}/{gamename.replace(/[^a-zA-Z ]/g, "").replaceAll(' ', '-')}">
|
||||
<div class="card rounded-md card-glass-surface snap-center card-hover w-20 sm:w-40 relative">
|
||||
{#if imageloading}
|
||||
<div class="w-20 h-20 sm:w-40 sm:h-40 rounded-none placeholder animate-pulse"></div>
|
||||
{/if}
|
||||
<img alt="" class="avatar-image w-20 h-20 sm:w-40 sm:h-40 object-cover {imageloading === true ? 'hidden' : ''} " use:onload={()=> imageloading=false} src="/assets/gameassets/thumbnail-{idofgame}.png#" />
|
||||
<p class="truncate w-auto">{gamename}</p>
|
||||
<div class="!text-sm">{playercount} Playing</div>
|
||||
<div class="btn h-2 w-6 sm:w-14 variant-filled-surface rounded-none absolute top-0 right-0 ">{version}</div>
|
||||
{#if username}<div class="hidden group-hover:block hover:block absolute top-32 sm:top-52">
|
||||
<div class="!text-xs flex flex-row">By <div class="text-primary-700 dark:text-primary-500 hover:brightness-110 underline truncate w-20 sm:w-40">{username}</div></div> <!-- not using anchor links here because it breaks styling in certain instances -->
|
||||
<div class="!text-xs">{visits??"0"} Visits</div>
|
||||
</div>{/if}
|
||||
</div>
|
||||
</a>
|
||||
{/if}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<script lang="ts">
|
||||
import { ProgressBar } from '@skeletonlabs/skeleton';
|
||||
</script>
|
||||
<div class="flex flex-wrap flex-row justify-center gap-y-6">
|
||||
<div class="w-full"><img alt="meteorite" class="h-32 m-auto" src="/assets/images/logosmall.png"></div>
|
||||
<h5 class="text-base">Do you have Meteorite Installed? If not install now!</h5>
|
||||
<ProgressBar meter="variant-filled-primary" />
|
||||
<a href="https://cdn.discordapp.com/attachments/1015311856744792104/1087210604273606766/Meteorite_0.5.0_x64_en-US.msi" class="btn variant-filled-primary btn-base rounded-md">Download</a>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
<div class="flex flex-wrap flex-row justify-center gap-y-6">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="44" height="64" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="w-full"><path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"></path><line x1="12" y1="9" x2="12" y2="13"></line><line x1="12" y1="17" x2="12.01" y2="17"></line></svg>
|
||||
<h5>Hi! In order to play games here at Meteorite you must link your discord account.</h5>
|
||||
<h5 class="!text-sm m-auto w-full">Thank you...</h5>
|
||||
<a href="https://discord.com/api/oauth2/authorize?client_id=1008206768989544449&redirect_uri=http%3A%2F%2Fmete0r.xyz%2Fsettings%2Fauthenticate&response_type=code&scope=identify" class="btn variant-filled-primary btn-base rounded-md">Link</a>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
<script lang="ts">
|
||||
import { Avatar } from "@skeletonlabs/skeleton";
|
||||
export let selectedgroup:string
|
||||
export let grouplist: array
|
||||
|
||||
</script>
|
||||
<div class="hidden sm:block">
|
||||
<div class="flex flex-col">
|
||||
<h2 class="font-bold">Groups</h2>
|
||||
|
||||
{#if grouplist}
|
||||
{#each grouplist as {Name, groupid}}
|
||||
<a class="unstyled group" href="/groups/{groupid}">
|
||||
<div class="flex flex-row bg-surface-700 p-4 gap-2 shadow-[inset_4px_0_0_0_white]" >
|
||||
|
||||
<Avatar width="w-8" rounded="rounded-none" src="/assets/groupicons/icon-0.png" />
|
||||
<h5 class="!text-xs truncate w-48 m-auto group-hover:text-white">{Name}</h5>
|
||||
</div>
|
||||
</a>
|
||||
{/each}
|
||||
{/if}
|
||||
|
||||
|
||||
<a href="/groups/create" class="btn variant-ringed-surface rounded-md w-full btn-sm">Create Group</a>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
<script lang="ts">
|
||||
import { Avatar } from "@skeletonlabs/skeleton";
|
||||
import Rocks from '../components/rocks.svelte';
|
||||
export let itemname: String;
|
||||
export let price:any = undefined
|
||||
export let itemid: String;
|
||||
export let sales:any = undefined
|
||||
export let type = ""
|
||||
|
||||
export let width = "w-36"
|
||||
|
||||
export let interact = "false"
|
||||
|
||||
export let moderation = false
|
||||
|
||||
export let equipped = false
|
||||
|
||||
export let action = () => {}
|
||||
|
||||
export let moderate = () => {}
|
||||
|
||||
let imageloading = true
|
||||
const onload = (node:HTMLImageElement, callback: VoidFunction) => {
|
||||
if(node.complete) {
|
||||
imageloading = false
|
||||
} else {
|
||||
node.addEventListener('load', callback)
|
||||
|
||||
return {
|
||||
destroy() {
|
||||
node.removeEventListener('load', callback)
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<div class="card rounded-md card-glass-surface snap-center card-hover {width} relative hidden sm:block">
|
||||
<a class="unstyled hidden sm:block" href="/catalog/{itemid}/{itemname.replace(/[^a-zA-Z ]/g, "").replaceAll(' ', '-')}">
|
||||
{#if type != "Audio" && type != "Video"}
|
||||
{#if imageloading}
|
||||
<div class="w-36 h-36 rounded-none placeholder animate-pulse"></div>
|
||||
{/if}
|
||||
<img alt="" class="avatar-image bg-surface-400-500-token w-full h-full flex aspect-square object-cover {imageloading === true ? 'hidden' : ''} " use:onload={()=> imageloading=false} src="/api/thumbnailrender/asset/?id={itemid}{moderation === false ? "": "&nonapproved=true"}" />
|
||||
{/if}
|
||||
</a>
|
||||
|
||||
{#if type === "Audio"}
|
||||
<audio class="block" controls>
|
||||
<source src="/asset?id={itemid}&nonapproved=true" type="audio/mp3">
|
||||
</audio>
|
||||
{/if}
|
||||
|
||||
{#if type === "Video"}
|
||||
<!-- svelte-ignore a11y-media-has-caption -->
|
||||
<video controls>
|
||||
<source src="/asset?id={itemid}&nonapproved=true" type="video/webm">
|
||||
</video>
|
||||
{/if}
|
||||
|
||||
{#if interact === "true"}
|
||||
{#if equipped === true}
|
||||
<button on:click={() => {action('remove',parseFloat({itemid}.itemid))}} class="btn variant-filled-primary rounded-md btn-sm absolute right-0 top-0">Remove</button>
|
||||
{:else}
|
||||
<button on:click={() => {action('wear',parseFloat({itemid}.itemid))}} class="btn variant-filled-primary rounded-md btn-sm absolute right-0 top-0">Wear</button>
|
||||
{/if}
|
||||
{/if}
|
||||
<p class="truncate w-28">{itemname}</p>
|
||||
{#if sales}
|
||||
<div class="!text-xs">{sales??"0"} Sales</div>
|
||||
{/if}
|
||||
|
||||
{#if moderation === true}
|
||||
<p class="!text-xs">{type}</p>
|
||||
<div class="flex flex-col gap-y-1">
|
||||
<div class="flex flex-row gap-x-2">
|
||||
<button on:click={() => {moderate('deny',parseFloat({itemid}.itemid))}} class="btn variant-filled-primary rounded-md btn-sm grow">Deny</button>
|
||||
<button on:click={() => {moderate('approve',parseFloat({itemid}.itemid))}} class="btn variant-filled-primary rounded-md btn-sm grow">Approve</button>
|
||||
</div>
|
||||
<!--<button on:click={() => {}} class="btn variant-filled-primary rounded-md btn-sm w-full">Download</button>-->
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if price}
|
||||
|
||||
<div class="flex flex-row ">
|
||||
<Rocks width="w-5"/>
|
||||
<div class="!text-sm">{price}</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
<script lang="ts">
|
||||
import { toastStore, type ToastSettings } from "@skeletonlabs/skeleton";
|
||||
|
||||
|
||||
let message = {error: false, message: ""}
|
||||
let disabled = false
|
||||
|
||||
let _2fa: number;
|
||||
|
||||
export let username: string
|
||||
export let password:string
|
||||
|
||||
$:if (!_2fa){
|
||||
disabled = true
|
||||
message.message = "2FA code not inputted"
|
||||
message.error = true
|
||||
}else{
|
||||
disabled = false
|
||||
message.message = ""
|
||||
message.error = false
|
||||
}
|
||||
|
||||
const t: ToastSettings = {
|
||||
message: 'resp',
|
||||
// Optional: Presets for primary | secondary | tertiary | warning
|
||||
preset: 'error',
|
||||
// Optional: The auto-hide settings
|
||||
autohide: true,
|
||||
timeout: 2000
|
||||
};
|
||||
|
||||
async function retrylogin(){
|
||||
if (username && password){
|
||||
const loginresp = await fetch('/login', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
credentials: 'include',
|
||||
body: JSON.stringify({
|
||||
username,
|
||||
password,
|
||||
_2fa
|
||||
})
|
||||
})
|
||||
const login = await loginresp.json()
|
||||
if (!login.error){
|
||||
t.preset = 'success'
|
||||
t.message = 'Logged in!'
|
||||
toastStore.trigger(t)
|
||||
document.location = '/home'
|
||||
}else{
|
||||
t.message = login.error
|
||||
toastStore.trigger(t)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<div class="space-y-2 flex flex-row flex-wrap justify-center">
|
||||
|
||||
<h5 class="m-auto text-center w-full">Confirm 2FA</h5>
|
||||
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="w-32"><path d="M21 2l-2 2m-7.61 7.61a5.5 5.5 0 1 1-7.778 7.778 5.5 5.5 0 0 1 7.777-7.777zm0 0L15.5 7.5m0 0l3 3L22 7l-3-3m-3.5 3.5L19 4"></path></svg>
|
||||
|
||||
<h5 class="m-auto text-center w-full">Enter the code generated by your authenticator app.</h5>
|
||||
|
||||
<div class="space-y-4 w-full">
|
||||
<input type="number" bind:value={_2fa} placeholder="Input 2FA" class="input input-bordered input-primary w-full rounded-md" required>
|
||||
<h5 class="!text-xs mt-6 {message.error === true ? 'text-error-600' : 'text-success-600'}">{message.message??""}</h5>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<button on:click={retrylogin} class="btn variant-filled-primary btn-base rounded-md" disabled={disabled}>
|
||||
Confirm
|
||||
</button>
|
||||
|
||||
</div>
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
<script>
|
||||
import { tweened } from 'svelte/motion';
|
||||
import { cubicOut } from 'svelte/easing';
|
||||
import { navigating } from '$app/stores';
|
||||
import { fade } from 'svelte/transition';
|
||||
|
||||
// progress bar value
|
||||
const p = tweened(0, {
|
||||
duration: 200,
|
||||
easing: cubicOut
|
||||
});
|
||||
|
||||
let isVisible = false;
|
||||
|
||||
function increase(){
|
||||
if ($p >= 0 && $p < 0.2) { p.update(_ => _ + 0.04); }
|
||||
else if ($p >= 0.2 && $p < 0.5) { p.update(_ => _ + 0.02); }
|
||||
else if ($p >= 0.5 && $p < 0.8) { p.update(_ => _ + 0.002); }
|
||||
else if ($p >= 0.8 && $p < 0.99) { p.update(_ => _ + 0.0005); }
|
||||
else { p.set(0) }
|
||||
|
||||
if($navigating){
|
||||
const rand = Math.round(Math.random() * (300 - 50)) + 50;
|
||||
setTimeout(function() {
|
||||
increase();
|
||||
}, rand);
|
||||
}
|
||||
}
|
||||
|
||||
$: {
|
||||
if($navigating){
|
||||
increase();
|
||||
isVisible = true;
|
||||
}
|
||||
if(!$navigating){
|
||||
p.update(_ => _ + 0.3);
|
||||
setTimeout(function() {
|
||||
p.set(1);
|
||||
setTimeout(function() {
|
||||
isVisible = false;
|
||||
p.set(0);
|
||||
}, 100)
|
||||
}, 100)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if $p > 0 && $p < 1 && isVisible}
|
||||
<progress value={$p} transition:fade={{duration: 300}} />
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
progress {
|
||||
--bar-color: rgba(255,255,255,0.3);
|
||||
--val-color: rgb(212 22 60);
|
||||
position: fixed;
|
||||
top: 0;
|
||||
z-index: 99999;
|
||||
left: 0;
|
||||
height: 2px;
|
||||
width: 100vw;
|
||||
border-radius: 0;
|
||||
}
|
||||
/* bar: */
|
||||
progress::-webkit-progress-bar {background-color: var(--bar-color); width: 100%;}
|
||||
progress {background-color: var(--bar-color);}
|
||||
|
||||
/* value: */
|
||||
progress::-webkit-progress-value {background-color: var(--val-color) !important;}
|
||||
progress::-moz-progress-bar {background-color: var(--val-color) !important;}
|
||||
progress {color: var(--val-color);}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
<script lang="ts">
|
||||
export let width = "w-6"
|
||||
</script>
|
||||
<svg class={width} fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17.657 18.657A8 8 0 016.343 7.343S7 9 9 10c0-2 .5-5 2.986-7C14 5 16.09 5.777 17.656 7.343A7.975 7.975 0 0120 13a7.975 7.975 0 01-2.343 5.657z"></path><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.879 16.121A3 3 0 1012.015 11L11 14H9c0 .768.293 1.536.879 2.121z"></path></svg>
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
<script lang="ts">
|
||||
export let scrollDirection: String;
|
||||
export let scrollElement: HTMLDivElement;
|
||||
const scroll = async (node: HTMLDivElement, direction: String) => {
|
||||
if (direction === "right"){
|
||||
if (window.matchMedia("(min-width: 640px)").matches === true){
|
||||
return node.scrollLeft += 800
|
||||
}
|
||||
node.scrollLeft += 200
|
||||
}else{
|
||||
if (window.matchMedia("(min-width: 640px)").matches === true){
|
||||
return node.scrollLeft -= 800
|
||||
}
|
||||
node.scrollLeft -= 200
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
</script>
|
||||
<button class="btn btn-ringed btn-base w-3 rounded-md float-{scrollDirection} h-32 sm:h-52" on:click={() => scroll(scrollElement, scrollDirection)} style="transition: none !important;transform: none !important;">
|
||||
|
||||
{#if scrollDirection === "right"}
|
||||
>
|
||||
{:else}
|
||||
<
|
||||
{/if}
|
||||
</button>
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
<script lang="ts">
|
||||
let _2facode: string
|
||||
let message = {error: false, message: ""}
|
||||
let disabled = false
|
||||
|
||||
export let jwt: string;
|
||||
export let qrcode: string;
|
||||
|
||||
$:if (!_2facode){
|
||||
disabled = true
|
||||
message.message = "2FA code not inputted"
|
||||
message.error = true
|
||||
}else{
|
||||
disabled = false
|
||||
message.message = ""
|
||||
message.error = false
|
||||
}
|
||||
|
||||
async function verify2fa(){
|
||||
const response = await fetch("/settings/verify2fa", {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({code: _2facode}),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': jwt,
|
||||
}
|
||||
})
|
||||
const json = await response.json()
|
||||
if (!json.error){
|
||||
message.message = "2FA Enabled"
|
||||
message.error = false
|
||||
document.location = '/settings'
|
||||
}else{
|
||||
message.message = json.error
|
||||
message.error = true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<div class="space-y-2 flex flex-row flex-wrap justify-center">
|
||||
|
||||
<h5 class="m-auto text-center w-full">Confirm 2FA</h5>
|
||||
|
||||
<img alt={qrcode} class="w-64" src={qrcode}/>
|
||||
|
||||
<div class="space-y-4 w-full">
|
||||
<input type="text" bind:value={_2facode} placeholder="Input 2FA" class="input input-bordered input-primary w-full rounded-md" required>
|
||||
<h5 class="!text-xs mt-6 {message.error === true ? 'text-error-600' : 'text-success-600'}">{message.message??""}</h5>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<button on:click={verify2fa} class="btn variant-filled-primary btn-base rounded-md" disabled={disabled}>
|
||||
Confirm
|
||||
</button>
|
||||
|
||||
</div>
|
||||
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
<script lang="ts">
|
||||
let currentp:string;
|
||||
let newp:string;
|
||||
let confp:string;
|
||||
let disabled = false
|
||||
let message = {error: false, message: ""}
|
||||
export let jwt: string;
|
||||
|
||||
$:if (newp != confp || !newp && !confp){
|
||||
disabled = true
|
||||
message.message = "New password and Confirm Password are not the same."
|
||||
message.error = true
|
||||
}else{
|
||||
disabled = false
|
||||
message.message = ""
|
||||
message.error = false
|
||||
}
|
||||
|
||||
async function changepassword(){
|
||||
const changepasswordresp = await fetch('/api/changepassword', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': jwt,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
oldpassword:currentp,
|
||||
newpassword:confp
|
||||
})
|
||||
})
|
||||
const changepassword = await changepasswordresp.json()
|
||||
if (!changepassword.error){
|
||||
message.message = changepassword.message
|
||||
message.error = false
|
||||
}else{
|
||||
message.message = changepassword.error
|
||||
message.error = true
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<div class="space-y-2 flex flex-row flex-wrap justify-center">
|
||||
|
||||
<h5 class="m-auto text-center">Change Password</h5>
|
||||
|
||||
<div class="space-y-4">
|
||||
<input type="password" bind:value={currentp} placeholder="Current Password" class="input input-bordered input-primary w-full rounded-md" required>
|
||||
<input type="password" bind:value={newp} placeholder="New Password" class="input input-bordered input-primary w-full rounded-md" required>
|
||||
<input type="password" bind:value={confp} placeholder="Confirm Password" class="input input-bordered input-primary w-full rounded-md" required>
|
||||
<h5 class="!text-xs mt-6 {message.error === true ? 'text-error-600' : 'text-success-600'}">{message.message??""}</h5>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<button on:click={changepassword} class="btn variant-filled-primary btn-base" disabled={disabled}>
|
||||
Update
|
||||
</button>
|
||||
|
||||
</div>
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
<script lang="ts">
|
||||
import { url } from "$lib/url"
|
||||
|
||||
|
||||
|
||||
|
||||
export let customclass: string
|
||||
export let customMediaClass = "hidden lg:block"
|
||||
let imageUrl = ""
|
||||
let redirectUrl = "/games/0"
|
||||
let loading = true
|
||||
|
||||
async function grabAd(){
|
||||
|
||||
const res = await fetch(url+`/api/requestad`)
|
||||
const data = await res.json()
|
||||
imageUrl = data.imageUrl
|
||||
redirectUrl = data.redirectUrl
|
||||
|
||||
}
|
||||
grabAd()
|
||||
</script>
|
||||
|
||||
{#if loading}
|
||||
<div class="hidden lg:block w-[160px] h-[600px] rounded-none placeholder animate-pulse {customclass}"></div>
|
||||
{/if}
|
||||
|
||||
<a on:click={grabAd} href={redirectUrl}><img class="w-[160px] h-[600px] rounded-none {customclass} {loading === true ? 'hidden' : customMediaClass}" on:load={() => {loading = false}} alt="Ad" src={imageUrl}/></a>
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
|
||||
|
||||
export let size: string;
|
||||
export let status = "Offline"
|
||||
export let userid = "0"
|
||||
export let gameid = "0"
|
||||
export let offline = false
|
||||
export let customclass = ""
|
||||
$:if (status === "Offline"){
|
||||
offline = true
|
||||
}
|
||||
</script>
|
||||
{#if status === "Offline"}
|
||||
<div class="{customclass} absolute bottom-2 right-5 w-{size} h-{size} bg-surface-300 rounded-full"></div>
|
||||
|
||||
{:else if status === "Online"}
|
||||
<div class="{customclass} absolute bottom-2 right-5 w-{size} h-{size} bg-blue-500 rounded-full"></div>
|
||||
{:else if status.includes('Playing') === true}
|
||||
<a class="unstyled" href="http://mete0r.xyz/games/{gameid}"><div class="{customclass} absolute bottom-2 right-5 w-{size} h-{size} bg-green-500 rounded-full"></div></a>
|
||||
{/if}
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
<script lang="ts">
|
||||
import { Avatar } from "@skeletonlabs/skeleton";
|
||||
|
||||
export let userid: string
|
||||
export let username: string
|
||||
export let friendRequest = false
|
||||
export let jwt = ""
|
||||
export let refresh = async function(){
|
||||
|
||||
}
|
||||
|
||||
async function ignore(){
|
||||
|
||||
const result = await fetch('/api/friends/decline-friend-request', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': jwt,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
recipientUserId: userid.toString()
|
||||
})
|
||||
})
|
||||
const requestresult = await result.json()
|
||||
console.log(requestresult)
|
||||
refresh()
|
||||
|
||||
}
|
||||
|
||||
async function accept(){
|
||||
const result = await fetch('/api/friends/request-friendship', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': jwt,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
recipientUserId: userid.toString()
|
||||
})
|
||||
})
|
||||
const requestresult = await result.json()
|
||||
console.log(requestresult)
|
||||
refresh()
|
||||
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<div class="card rounded-md card-glass-surface snap-center card-hover group w-full lg:w-96 relative p-2">
|
||||
<a class="unstyled" href="/users/{userid}">
|
||||
<div class="flex flex-row gap-4">
|
||||
<Avatar width="w-32" src="/api/thumbnailrender/?id={userid}#" />
|
||||
<h5 class="truncate w-96">{username}</h5>
|
||||
</div>
|
||||
</a>
|
||||
{#if friendRequest === true}
|
||||
|
||||
<div class="flex flex-row gap-4 justify-around p-4">
|
||||
|
||||
<button on:click={ignore} type="submit" class="btn variant-filled-primary btn-base rounded-md w-full">Ignore</button>
|
||||
|
||||
<button on:click={accept} type="submit" class="btn variant-filled-primary btn-base rounded-md w-full">Accept</button>
|
||||
|
||||
</div>
|
||||
|
||||
{/if}
|
||||
|
||||
|
||||
</div>
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
import { authenticateUser } from "$lib/auth"
|
||||
import { redirect, type Handle } from "@sveltejs/kit"
|
||||
const protectedroutes = [
|
||||
'/home',
|
||||
'/catalog',
|
||||
'/develop',
|
||||
'/users',
|
||||
'/avatar',
|
||||
'/settings',
|
||||
'/admin'
|
||||
]
|
||||
|
||||
export const handle: Handle = async ({ event, resolve }) => {
|
||||
// Stage 1
|
||||
event.locals.user = await authenticateUser(event)
|
||||
event.locals.jwt = event.cookies.get('jwt')??""
|
||||
event.locals.useragent = event.request.headers.get('user-agent')
|
||||
//console.log(event.locals.user)
|
||||
|
||||
if (protectedroutes.includes(event.url.pathname) === true || protectedroutes.some(substr => event.url.pathname.toLowerCase().startsWith(substr.toLowerCase())) === true) {
|
||||
if (!event.locals.user) {
|
||||
throw redirect(303, "/")
|
||||
}
|
||||
if (event.locals.user?.moderationstatus && event.locals.user.moderationstatus?.status.toUpperCase() != "OK" && event.url.pathname != "moderated"){
|
||||
throw redirect(303, '/moderated')
|
||||
}
|
||||
}
|
||||
if (event.url.pathname.toLowerCase().startsWith('/admin') === true){
|
||||
// admin route
|
||||
if (!event.locals.user) {
|
||||
throw redirect(303, "/")
|
||||
}
|
||||
if (event.locals.user.admin === false){
|
||||
throw redirect(303, "/")
|
||||
}
|
||||
}
|
||||
|
||||
const response = await resolve(event) // Stage 2
|
||||
|
||||
// Stage 3
|
||||
//console.log(event.url.protocol)
|
||||
if (event.url.protocol === 'https:'){
|
||||
response.headers.append("Content-Security-Policy","img-src 'self' data: wsrv.nl images.weserv.nl;upgrade-insecure-requests;")
|
||||
}else{
|
||||
response.headers.append("Content-Security-Policy","img-src 'self' data: wsrv.nl images.weserv.nl;")
|
||||
}
|
||||
|
||||
return response
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
import type { RequestEvent } from "@sveltejs/kit"
|
||||
|
||||
export const authenticateUser = async (event: RequestEvent) => {
|
||||
// get the cookies from the request
|
||||
const { cookies } = event
|
||||
const { route } = event
|
||||
|
||||
// get the user token from the cookie
|
||||
const userToken = cookies.get("jwt")
|
||||
if (!userToken){
|
||||
return null
|
||||
}
|
||||
// if the user token is not valid, return null
|
||||
// this is where you would check the user token against your database
|
||||
// to see if it is valid and return the user object
|
||||
const res = await fetch("http://mete0r.xyz/api/auth",{credentials: 'include', headers: {cookie: "jwt="+userToken,route: route.id as string}})
|
||||
const data = await res.json()
|
||||
if (!data.error){
|
||||
return data
|
||||
}else if (data.moderationstatus){
|
||||
return data
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
import { writable } from 'svelte/store';
|
||||
|
||||
export const avatarstore = writable("");
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
import { writable } from 'svelte/store';
|
||||
|
||||
export const coinstore = writable(0);
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
import { browser } from '$app/environment';
|
||||
|
||||
export const url = !browser ? "http://mete0r.xyz": "" // if no browser return "http://mete0r.xyz" otherwise nothing
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
</script>
|
||||
|
||||
|
||||
<div class="flex h-screen space-y-2">
|
||||
<div class="m-auto w-96 text-center bg-surface-700 p-4">
|
||||
<h1 class="font-bold text-7xl">{$page.status} :(</h1>
|
||||
<h5 class="">Oops!</h5>
|
||||
<h5 class="text-xl font-bold">{$page?.error?.message}</h5>
|
||||
<button on:click={()=>{history.back()}} class="btn variant-filled-primary rounded-md w-full btn-lg">Go Back!</button>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
import { redirect } from '@sveltejs/kit';
|
||||
import type { LayoutServerLoad } from './$types';
|
||||
const protectedroutes = [
|
||||
'/home',
|
||||
'/catalog',
|
||||
'/develop',
|
||||
'/users',
|
||||
'/avatar',
|
||||
'/settings',
|
||||
'/admin'
|
||||
]
|
||||
|
||||
export const load: LayoutServerLoad = (async ({ url, locals }) => {
|
||||
//console.log(locals)
|
||||
//await parent;
|
||||
if (!locals.user){
|
||||
if (protectedroutes.includes(url.pathname) === true || protectedroutes.some(substr => url.pathname.toLowerCase().startsWith(substr.toLowerCase())) === true){
|
||||
throw redirect(303, "/")
|
||||
}
|
||||
}
|
||||
|
||||
if (locals.user?.moderationstatus && locals?.user?.moderationstatus?.status.toUpperCase() != "OK" && url.pathname != '/moderated'){
|
||||
throw redirect(303, '/moderated')
|
||||
}
|
||||
if (url.pathname.toLowerCase().startsWith('/admin') === true){
|
||||
// admin route
|
||||
if (!locals.user) {
|
||||
throw redirect(303, "/")
|
||||
}
|
||||
if (locals.user.admin === false){
|
||||
throw redirect(303, "/")
|
||||
}
|
||||
}
|
||||
|
||||
if (url.pathname === "/" && locals.user){
|
||||
throw redirect(303, "/home")
|
||||
}
|
||||
|
||||
return {
|
||||
user: locals.user,
|
||||
jwt : locals.jwt,
|
||||
useragent: locals.useragent,
|
||||
protocol: url.protocol,
|
||||
url: url.pathname
|
||||
}
|
||||
}) satisfies LayoutServerLoad;
|
||||
|
||||
|
|
@ -0,0 +1,309 @@
|
|||
<script lang="ts">
|
||||
import '@skeletonlabs/skeleton/themes/theme-crimson.css';
|
||||
import '@skeletonlabs/skeleton/styles/all.css';
|
||||
import '../app.postcss';
|
||||
import { AppShell, AppBar, Drawer, drawerStore, menu, Toast, AppRail, AppRailTile, Avatar } from '@skeletonlabs/skeleton';
|
||||
import type { PageData } from './$types';
|
||||
import { onMount } from 'svelte';
|
||||
import Rocks from '../components/rocks.svelte';
|
||||
import { slide } from 'svelte/transition'
|
||||
import { Modal } from '@skeletonlabs/skeleton';
|
||||
import { coinstore } from "$lib/coinstore"
|
||||
import { HomeIcon } from 'svelte-feather-icons'
|
||||
import { PersonStandingIcon, Users2Icon } from 'lucide-svelte'
|
||||
import Progressbar from '../components/progressbar.svelte';
|
||||
let coins: number;
|
||||
let avatar: string
|
||||
import { avatarstore } from "$lib/avatarstore"
|
||||
avatarstore.subscribe(value => {
|
||||
avatar = value
|
||||
})
|
||||
|
||||
coinstore.subscribe(value => {
|
||||
coins = value;
|
||||
})
|
||||
export let data: PageData;
|
||||
$:{
|
||||
coinstore.update(n => n = data.user?.coins);
|
||||
avatarstore.update(n => n = "/api/thumbnailrender/?id="+data.user?.userid);
|
||||
}
|
||||
let search: string;
|
||||
let previousearch: string
|
||||
|
||||
function p(){
|
||||
if (search != ""){
|
||||
previousearch = search
|
||||
}
|
||||
}
|
||||
|
||||
$:search,p()
|
||||
|
||||
|
||||
function drawerOpen() {
|
||||
const settings = {
|
||||
// Provide your prop overrides
|
||||
position: 'top',
|
||||
height: 'h-44'
|
||||
};
|
||||
// @ts-ignore
|
||||
drawerStore.open(settings);
|
||||
};
|
||||
onMount(() => {
|
||||
function listenCookieChange(callback: any, interval = 1000) {
|
||||
let lastCookie = document.cookie;
|
||||
setInterval(()=> {
|
||||
let cookie = document.cookie;
|
||||
if (cookie !== lastCookie) {
|
||||
try {
|
||||
callback({oldValue: lastCookie, newValue: cookie});
|
||||
} finally {
|
||||
lastCookie = cookie;
|
||||
}
|
||||
}
|
||||
}, interval);
|
||||
}
|
||||
listenCookieChange(async ()=> {
|
||||
function getCookie(name: any) {
|
||||
function escape(s: string) { return s.replace(/([.*+?\^$(){}|\[\]\/\\])/g, '\\$1'); }
|
||||
var match = document.cookie.match(RegExp('(?:^|;\\s*)' + escape(name) + '=([^;]*)'));
|
||||
return match ? match[1] : null;
|
||||
}
|
||||
const jwt = getCookie("jwt")
|
||||
const res = await fetch("/api/auth",{credentials: 'include', headers: {authorization: jwt as string}})
|
||||
const data = await res.json()
|
||||
if (data.error){
|
||||
document.location = '/'
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
})
|
||||
|
||||
</script>
|
||||
<svelte:head>
|
||||
<title>Meteorite - { ( (data?.url?.replace("/",""))?.[0]?.toUpperCase() + data?.url?.slice(2) )?.split("/")?.[0] }</title>
|
||||
<!-- Google tag (gtag.js) -->
|
||||
<script async src="https://www.googletagmanager.com/gtag/js?id=G-Z3QGF9XQM4"></script>
|
||||
<script>
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag(){dataLayer.push(arguments);}
|
||||
gtag('js', new Date());
|
||||
|
||||
gtag('config', 'G-Z3QGF9XQM4');
|
||||
</script>
|
||||
</svelte:head>
|
||||
|
||||
<!-- App Shell -->
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<Progressbar/>
|
||||
<AppShell slotSidebarLeft="!w-40 hidden sm:block" slotPageFooter="flex justify-center bg-transparent">
|
||||
<Drawer>
|
||||
<a href="/games">
|
||||
<div>
|
||||
<button class="btn btn-sm text-white">Games</button>
|
||||
</div>
|
||||
</a>
|
||||
<a href="/catalog">
|
||||
<div>
|
||||
<button class="btn btn-sm text-white">Catalog</button>
|
||||
</div>
|
||||
</a>
|
||||
<a href="/develop">
|
||||
<div>
|
||||
<button class="btn btn-sm text-white">Develop</button>
|
||||
</div>
|
||||
</a>
|
||||
<a href="/avatar">
|
||||
<div>
|
||||
<button class="btn btn-sm text-white">Avatar</button>
|
||||
</div>
|
||||
</a>
|
||||
<div class="relative">
|
||||
<input type="text" bind:value={search} on:click={() => {search = previousearch}} placeholder="Search" class="input input-bordered input-primary pr-8 w-full rounded-none" maxlength=50 required>
|
||||
{#if search}
|
||||
<svg on:click={()=>{search=""}} class="w-8 h-8 absolute inset-y-0 right-0 pt-2 pr-3" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
|
||||
{:else}
|
||||
<svg class="w-8 h-8 absolute inset-y-0 right-0 pt-2 pr-3 pointer-events-none" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path></svg>
|
||||
{/if}
|
||||
</div>
|
||||
{#if search}
|
||||
<nav class="list-nav bg-surface-700 rounded-md w-full absolute z-50">
|
||||
<ul>
|
||||
<li>
|
||||
<a href="/catalog/search/{search}" on:click={() => {search = ""}}>
|
||||
<span class="flex-auto truncate">Search "{search}" in Catalog</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="/games/search/{search}" on:click={() => {search = ""}}>
|
||||
<span class="flex-auto truncate">Search "{search}" in Games</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="/users/search/{search}" on:click={() => {search = ""}}>
|
||||
<span class="flex-auto truncate">Search "{search}" in Players</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="/groups/search/{search}" on:click={() => {search = ""}}>
|
||||
<span class="flex-auto truncate">Search "{search}" in Groups</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
{/if}
|
||||
</Drawer>
|
||||
<Toast />
|
||||
<Modal width="w-full max-w-[500px]" />
|
||||
<svelte:fragment slot="header">
|
||||
<!-- App Bar -->
|
||||
<AppBar padding="px" slotLead="space-x-2">
|
||||
<svelte:fragment slot="lead">
|
||||
<a href="/home"><img src="/assets/images/logo600200.png" alt="meteorite" class="max-h-12 pl-6 hidden xl:block"></a>
|
||||
<svg class="w-6 h-6 md:hidden block" on:click={drawerOpen} fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"></path></svg>
|
||||
<a href="/home"><img src="/assets/images/logosmall.png" alt="meteorite" class="max-h-12 xl:hidden block"></a>
|
||||
<div class="hidden md:block">
|
||||
<a class="btn btn-sm rounded-none group relative" href="/games">Games
|
||||
<div class="transition-width delay-0 absolute min-h-[1px] top-8 w-4 group-hover:w-16 group-hover:shadow-[inset_0_-2px_0_0_white]"></div>
|
||||
</a>
|
||||
{#if data.user?.admin === true}
|
||||
<a class="btn btn-sm rounded-none group relative" href="/admin">Admin
|
||||
<div class="transition-width delay-0 absolute min-h-[1px] top-8 w-4 group-hover:w-16 group-hover:shadow-[inset_0_-2px_0_0_white]"></div>
|
||||
</a>
|
||||
{/if}
|
||||
<a class="btn btn-sm rounded-none group relative" href="/catalog">Catalog
|
||||
<div class="transition-width delay-0 absolute min-h-[1px] top-8 w-4 group-hover:w-16 group-hover:shadow-[inset_0_-2px_0_0_white]"></div>
|
||||
</a>
|
||||
<a class="btn btn-sm rounded-none group relative" href="/develop">Develop
|
||||
<div class="transition-width delay-0 absolute min-h-[1px] top-8 w-4 group-hover:w-16 group-hover:shadow-[inset_0_-2px_0_0_white]"></div>
|
||||
</a>
|
||||
</div>
|
||||
<div class="relative hidden md:block">
|
||||
<input type="text" bind:value={search} on:dblclick={() => {search = previousearch}} placeholder="Search" class="input input-bordered input-primary pr-8 w-48 lg:w-96 max-w-full rounded-md" maxlength=50 required> <!-- refill on double click as well -->
|
||||
{#if search}
|
||||
<svg on:click={()=>{search=""}} class="w-8 h-8 absolute inset-y-0 right-0 pt-2 pr-3" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
|
||||
{:else}
|
||||
<svg class="w-8 h-8 absolute inset-y-0 right-0 pt-2 pr-3 pointer-events-none" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path></svg>
|
||||
{/if}
|
||||
{#if search}
|
||||
<nav class="list-nav bg-surface-700 rounded-md w-48 lg:w-96 max-w-full absolute z-50" transition:slide={{ duration: 100 }}>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="/catalog/search/{search}" on:click={() => {search = ""}}>
|
||||
<span class="flex-auto truncate">Search "{search}" in Catalog</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="/games/search/{search}" on:click={() => {search = ""}}>
|
||||
<span class="flex-auto truncate">Search "{search}" in Games</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="/users/search/{search}" on:click={() => {search = ""}}>
|
||||
<span class="flex-auto truncate">Search "{search}" in Players</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="/groups/search/{search}" on:click={() => {search = ""}}>
|
||||
<span class="flex-auto truncate">Search "{search}" in Groups</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="trail">
|
||||
{#if data.user}
|
||||
<Rocks/>
|
||||
{coins??"0"}
|
||||
<span class="relative pr-2">
|
||||
<button class="w-6 pt-2" use:menu={{ menu: 'navigation' }}>
|
||||
<svg fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M10.343 3.94c.09-.542.56-.94 1.11-.94h1.093c.55 0 1.02.398 1.11.94l.149.894c.07.424.384.764.78.93.398.164.855.142 1.205-.108l.737-.527a1.125 1.125 0 011.45.12l.773.774c.39.389.44 1.002.12 1.45l-.527.737c-.25.35-.272.806-.107 1.204.165.397.505.71.93.78l.893.15c.543.09.94.56.94 1.109v1.094c0 .55-.397 1.02-.94 1.11l-.893.149c-.425.07-.765.383-.93.78-.165.398-.143.854.107 1.204l.527.738c.32.447.269 1.06-.12 1.45l-.774.773a1.125 1.125 0 01-1.449.12l-.738-.527c-.35-.25-.806-.272-1.203-.107-.397.165-.71.505-.781.929l-.149.894c-.09.542-.56.94-1.11.94h-1.094c-.55 0-1.019-.398-1.11-.94l-.148-.894c-.071-.424-.384-.764-.781-.93-.398-.164-.854-.142-1.204.108l-.738.527c-.447.32-1.06.269-1.45-.12l-.773-.774a1.125 1.125 0 01-.12-1.45l.527-.737c.25-.35.273-.806.108-1.204-.165-.397-.505-.71-.93-.78l-.894-.15c-.542-.09-.94-.56-.94-1.109v-1.094c0-.55.398-1.02.94-1.11l.894-.149c.424-.07.765-.383.93-.78.165-.398.143-.854-.107-1.204l-.527-.738a1.125 1.125 0 01.12-1.45l.773-.773a1.125 1.125 0 011.45-.12l.737.527c.35.25.807.272 1.204.107.397-.165.71-.505.78-.929l.15-.894z"></path>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<nav class="list-nav !rounded-md pb-2 pt-2 w-40 bg-surface-800" data-menu="navigation">
|
||||
<ul>
|
||||
<li><a class="!rounded-none" href="/settings">Settings</a></li>
|
||||
<li><a class="!rounded-none" href="/logout">Logout</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</span>
|
||||
{/if}
|
||||
|
||||
|
||||
</svelte:fragment>
|
||||
|
||||
</AppBar>
|
||||
|
||||
</svelte:fragment>
|
||||
<!-- Page Route Content -->
|
||||
<slot />
|
||||
|
||||
<svelte:fragment slot="sidebarLeft">
|
||||
<!-- Hidden below Tailwind's large breakpoint -->
|
||||
{#if data.user}
|
||||
<div id="sidebar-left" class="z-50 h-full bg-surface-800 flex flex-col space-y-3 md:block p-3 ">
|
||||
|
||||
<a href="/users/{data.user.userid}" class="flex flex-row space-y-2 unstyled">
|
||||
<Avatar width="w-12" src={avatar+"&type=headshot"} />
|
||||
<h5 class="!text-sm font-semibold truncate w-40">{data.user.username}</h5>
|
||||
</a>
|
||||
|
||||
<a class="unstyled flex flex-row group gap-x-1 pt-4" href="/home">
|
||||
<HomeIcon class="group-hover:stroke-white" strokeWidth={1.3} size="28"/>
|
||||
<h5 class="font-semibold group-hover:text-white">Home</h5>
|
||||
</a>
|
||||
|
||||
<a class="unstyled flex flex-row group gap-x-1" href="/avatar">
|
||||
<PersonStandingIcon class="group-hover:stroke-white" strokeWidth={1.5} size="28"/>
|
||||
<h5 class="font-semibold group-hover:text-white">Avatar</h5>
|
||||
</a>
|
||||
|
||||
<a class="unstyled relative flex flex-row group gap-x-1" href="/friends">
|
||||
<Users2Icon class="group-hover:stroke-white" strokeWidth={1.5} size="28"/>
|
||||
<h5 class="font-semibold group-hover:text-white">Friends</h5>
|
||||
{#if data.user?.friendrequests?.length > 0}
|
||||
<div class="p-1 bg-primary-500 rounded-md !text-xs ml-auto my-auto">{data.user?.friendrequests?.length}</div>
|
||||
{/if}
|
||||
</a>
|
||||
|
||||
<a class="unstyled flex flex-row group gap-x-1" href="/groups">
|
||||
<svg fill="none" width="28" height="28" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" class="group-hover:stroke-white" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M18 18.72a9.094 9.094 0 003.741-.479 3 3 0 00-4.682-2.72m.94 3.198l.001.031c0 .225-.012.447-.037.666A11.944 11.944 0 0112 21c-2.17 0-4.207-.576-5.963-1.584A6.062 6.062 0 016 18.719m12 0a5.971 5.971 0 00-.941-3.197m0 0A5.995 5.995 0 0012 12.75a5.995 5.995 0 00-5.058 2.772m0 0a3 3 0 00-4.681 2.72 8.986 8.986 0 003.74.477m.94-3.197a5.971 5.971 0 00-.94 3.197M15 6.75a3 3 0 11-6 0 3 3 0 016 0zm6 3a2.25 2.25 0 11-4.5 0 2.25 2.25 0 014.5 0zm-13.5 0a2.25 2.25 0 11-4.5 0 2.25 2.25 0 014.5 0z"></path></svg>
|
||||
<h5 class="font-semibold group-hover:text-white">Groups</h5>
|
||||
</a>
|
||||
|
||||
<a class="unstyled flex flex-row group gap-x-1" href="https://discord.gg/5r6ZjG57kU">
|
||||
<svg class="group-hover:stroke-white" width="28" height="28" viewBox="0 0 34 34"><g fill="currentColor"><path d="M26.0015 6.9529C24.0021 6.03845 21.8787 5.37198 19.6623 5C19.3833 5.48048 19.0733 6.13144 18.8563 6.64292C16.4989 6.30193 14.1585 6.30193 11.8336 6.64292C11.6166 6.13144 11.2911 5.48048 11.0276 5C8.79575 5.37198 6.67235 6.03845 4.6869 6.9529C0.672601 12.8736 -0.41235 18.6548 0.130124 24.3585C2.79599 26.2959 5.36889 27.4739 7.89682 28.2489C8.51679 27.4119 9.07477 26.5129 9.55525 25.5675C8.64079 25.2265 7.77283 24.808 6.93587 24.312C7.15286 24.1571 7.36986 23.9866 7.57135 23.8161C12.6241 26.1255 18.0969 26.1255 23.0876 23.8161C23.3046 23.9866 23.5061 24.1571 23.7231 24.312C22.8861 24.808 22.0182 25.2265 21.1037 25.5675C21.5842 26.5129 22.1422 27.4119 22.7621 28.2489C25.2885 27.4739 27.8769 26.2959 30.5288 24.3585C31.1952 17.7559 29.4733 12.0212 26.0015 6.9529ZM10.2527 20.8402C8.73376 20.8402 7.49382 19.4608 7.49382 17.7714C7.49382 16.082 8.70276 14.7025 10.2527 14.7025C11.7871 14.7025 13.0425 16.082 13.0115 17.7714C13.0115 19.4608 11.7871 20.8402 10.2527 20.8402ZM20.4373 20.8402C18.9183 20.8402 17.6768 19.4608 17.6768 17.7714C17.6768 16.082 18.8873 14.7025 20.4373 14.7025C21.9717 14.7025 23.2271 16.082 23.1961 17.7714C23.1961 19.4608 21.9872 20.8402 20.4373 20.8402Z"></path></g></svg>
|
||||
<h5 class="font-semibold group-hover:text-white">Discord</h5>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="pageFooter">
|
||||
<div class="text-center">
|
||||
<h5 class="!text-xs">Copyright © Meteorite 2023</h5>
|
||||
<a href="/legal/terms">Terms of Service</a>
|
||||
<a href="/legal/privacy">Privacy Policy</a>
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
|
||||
</AppShell>
|
||||
|
|
@ -0,0 +1,139 @@
|
|||
<script lang="ts">
|
||||
import { toastStore } from '@skeletonlabs/skeleton';
|
||||
import type { ToastSettings } from '@skeletonlabs/skeleton';
|
||||
import HCaptcha from "svelte-hcaptcha";
|
||||
import { filter } from '@skeletonlabs/skeleton';
|
||||
import { Noir } from '@skeletonlabs/skeleton';
|
||||
import { Modal, modalStore } from '@skeletonlabs/skeleton';
|
||||
import type { ModalSettings, ModalComponent } from '@skeletonlabs/skeleton';
|
||||
import _2famodal from "../components/login/2famodal.svelte"
|
||||
|
||||
let current = "login"
|
||||
let username:string;
|
||||
let password:string;
|
||||
let _2fa:number;
|
||||
let captcha: any
|
||||
const t: ToastSettings = {
|
||||
message: 'resp',
|
||||
// Optional: Presets for primary | secondary | tertiary | warning
|
||||
preset: 'error',
|
||||
// Optional: The auto-hide settings
|
||||
autohide: true,
|
||||
timeout: 2000
|
||||
};
|
||||
function handleSuccess(token: any){
|
||||
captcha = token.detail.token
|
||||
}
|
||||
|
||||
async function login(){
|
||||
if (username && password){
|
||||
const loginresp = await fetch('/login', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
credentials: 'include',
|
||||
body: JSON.stringify({
|
||||
username,
|
||||
password,
|
||||
_2fa
|
||||
})
|
||||
})
|
||||
const login = await loginresp.json()
|
||||
if (!login.error){
|
||||
t.preset = 'success'
|
||||
t.message = 'Logged in!'
|
||||
toastStore.trigger(t)
|
||||
document.location = '/home'
|
||||
}else if (login?.error === "2FA Enabled on account but 2fa not sent"){
|
||||
const modalComponent: ModalComponent = {
|
||||
// Pass a reference to your custom component
|
||||
ref: _2famodal,
|
||||
// Add your props as key/value pairs
|
||||
// Provide default slot content as a template literal
|
||||
slot: '<p>Skeleton</p>',
|
||||
props: { username, password }
|
||||
}
|
||||
const _2faprompt: ModalSettings = {
|
||||
type: 'component',
|
||||
// NOTE: title, body, response, etc are supported!,
|
||||
component: modalComponent
|
||||
}
|
||||
|
||||
modalStore.trigger(_2faprompt)
|
||||
}
|
||||
else{
|
||||
t.message = login.error
|
||||
toastStore.trigger(t)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
async function signup(){
|
||||
if (username && password){
|
||||
const signupresp = await fetch('register', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
username,
|
||||
password,
|
||||
captcha
|
||||
})
|
||||
})
|
||||
const login = await signupresp.json()
|
||||
if (!login.error){
|
||||
t.preset = 'success'
|
||||
t.message = 'Signed up!'
|
||||
current = 'login'
|
||||
toastStore.trigger(t)
|
||||
}else{
|
||||
t.message = login.error
|
||||
toastStore.trigger(t)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
let scroll: number
|
||||
</script>
|
||||
<Noir/>
|
||||
|
||||
<div class="grid auto-rows-max gap-4 grid-cols-1 xl:grid-cols-2 pt-24">
|
||||
|
||||
<img src="/assets/images/logo600200.png" alt="meteorite!" class="max-w-full w-3/6 justify-self-center xl:justify-self-end xl:pt-20">
|
||||
|
||||
|
||||
|
||||
<div class="flex flex-col justify-center items-center space-y-5 needs-validation card rounded-md w-96 h-[28rem] max-w-full justify-self-center xl:justify-self-start " novalidate id="form" action="/login" method="post">
|
||||
{#if current === "signup"}
|
||||
<h3 class="font-bold">Sign up!</h3>
|
||||
|
||||
<input type="text" bind:value={username} placeholder="Username" class="input input-bordered input-primary w-full max-w-xs rounded-md" name="username" id="username" aria-describedby="usernamehelp" required>
|
||||
|
||||
<input type="password" bind:value={password} placeholder="Password" class="input input-bordered input-primary w-full max-w-xs rounded-md" name="pass" id="password" required>
|
||||
|
||||
<HCaptcha
|
||||
sitekey="30f6dee1-f765-42d0-ae34-29697c4aa623",
|
||||
theme="{'dark'}"
|
||||
on:success={handleSuccess}
|
||||
/>
|
||||
|
||||
<h5 class="!text-xs w-full max-w-xs">By clicking Sign Up, you are agreeing to the <a href="/legal/terms" class="!text-xs">Terms of Use</a> including the arbitration clause and you are acknowledging the <a href="/legal/privacy" class="!text-xs">Privacy Policy</a></h5>
|
||||
|
||||
<div><button type="submit" on:click={signup} class="btn variant-filled-primary btn-base w-80 rounded-md">Sign Up</button></div>
|
||||
<button on:click="{() => current = 'login'}" class="!text-sm flex flex-row">Already have an account? <h5 class="!text-sm pl-2 text-primary-500">Sign In</h5></button>
|
||||
{:else}
|
||||
<h3 class="font-bold">Login</h3>
|
||||
<input type="text" bind:value={username} placeholder="Username" class="input input-bordered input-primary w-full max-w-xs rounded-md" required>
|
||||
<input type="password" bind:value={password} placeholder="Password" class="input input-bordered input-primary w-full max-w-xs rounded-md" required>
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<h5 class="!text-xs w-full max-w-xs">By clicking Login, you are agreeing to the <a href="/legal/terms" class="!text-xs">Terms of Use</a> including the arbitration clause and you are acknowledging the <a href="/legal/privacy" class="!text-xs">Privacy Policy</a></h5>
|
||||
<div><button type="submit" on:click={login} class="btn variant-filled-primary btn-base w-80 rounded-md">Login</button></div>
|
||||
<button on:click="{() => current = 'signup'}" class="!text-sm flex flex-row">Don't have an account? <h5 class="!text-sm pl-2 text-primary-500">Sign Up</h5></button>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<svelte:window bind:scrollY={scroll} use:wheel={()=>{scroll+=1}} />
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
<script lang="ts">
|
||||
import { TabGroup, Tab, dataTableHandler } from '@skeletonlabs/skeleton';
|
||||
import { RadioGroup, RadioItem } from '@skeletonlabs/skeleton';
|
||||
import { writable, type Writable } from 'svelte/store';
|
||||
import { Avatar } from "@skeletonlabs/skeleton";
|
||||
import Lookup from "../../components/admin/lookup.svelte"
|
||||
import Lookupdone from "../../components/admin/lookupdone.svelte"
|
||||
import Assetqueue from '../../components/admin/assetqueue.svelte';
|
||||
import type { PageData } from '../$types';
|
||||
let lookupdata: any
|
||||
|
||||
let storeTab = "lookup"
|
||||
const storeaction: Writable<string> = writable('horz');
|
||||
|
||||
export let data: PageData;
|
||||
|
||||
</script>
|
||||
|
||||
<div class="gap-2 bg-surface-800 p-4 max-w-[1300px] m-0 m-auto flex flex-row flex-wrap">
|
||||
<h2 class="w-full">Admin Panel </h2>
|
||||
<div class="flex flex-row grow">
|
||||
<TabGroup justify="flex flex-col w-32" borderWidth="border-l-2" rounded="">
|
||||
<h5>People</h5>
|
||||
<h5 class="absolute right-[80rem] opacity-[0.005]">{data.user.username}</h5>
|
||||
<div class="pl-2">
|
||||
<Tab bind:group={storeTab} value="lookup">Lookup Users</Tab>
|
||||
</div>
|
||||
|
||||
<div class="pl-2">
|
||||
<Tab bind:group={storeTab} value="queue">Asset Queue</Tab>
|
||||
</div>
|
||||
|
||||
<h5>Forums</h5>
|
||||
|
||||
<h5>Website</h5>
|
||||
|
||||
<div class="pl-2">
|
||||
<Tab bind:group={storeTab} value="banner">Set Banner</Tab>
|
||||
</div>
|
||||
|
||||
</TabGroup>
|
||||
{#if storeTab === "lookup"}
|
||||
<div class="flex flex-row flex-wrap p-4 space-y-4 divide-y-2 divide-primary-500">
|
||||
|
||||
{#if !lookupdata}
|
||||
<Lookup bind:data={lookupdata} jwt={data.jwt}/>
|
||||
{/if}
|
||||
|
||||
{#if lookupdata}
|
||||
<Lookupdone jwt={data.jwt} bind:lookupdata={lookupdata}/>
|
||||
|
||||
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
{/if}
|
||||
|
||||
|
||||
{#if storeTab === "queue"}
|
||||
<Assetqueue jwt={data.jwt}/>
|
||||
{/if}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,175 @@
|
|||
<script lang="ts">
|
||||
import type { PageData } from './$types';
|
||||
import { RadioGroup, RadioItem } from '@skeletonlabs/skeleton';
|
||||
import { writable, type Writable } from 'svelte/store';
|
||||
import Itemcard from '../../components/itemcard.svelte';
|
||||
import Colorpicker from '../../components/colorpicker.svelte';
|
||||
import { modalStore } from '@skeletonlabs/skeleton';
|
||||
import type { ModalSettings, ModalComponent } from '@skeletonlabs/skeleton';
|
||||
import { url } from "$lib/url"
|
||||
let avatar: string;
|
||||
import { avatarstore } from "$lib/avatarstore"
|
||||
import { onMount } from 'svelte';
|
||||
avatarstore.subscribe(value => {
|
||||
avatar = value
|
||||
})
|
||||
|
||||
let regenerating = false
|
||||
|
||||
export let data: PageData;
|
||||
const jwt = data.jwt;
|
||||
const userid = data.user.userid
|
||||
|
||||
let avatartype = data.user.avatartype??"R6"
|
||||
let avatarfilter = "shirts"
|
||||
let currentItems: any[] = [];
|
||||
async function updateBodyType(newavatartype: string){
|
||||
if (avatartype != newavatartype){
|
||||
avatartype = newavatartype
|
||||
const response = await fetch("/api/avatar/updateavatartype", {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': jwt,
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
async function updateItems(){
|
||||
const response = await fetch(url+`/api/userinfo/${userid}`)
|
||||
const data = await response.json()
|
||||
if (currentItems){
|
||||
currentItems.length = 0
|
||||
currentItems = currentItems
|
||||
}
|
||||
currentItems = data.userinfo.inventory
|
||||
currentItems = currentItems
|
||||
currentItems = currentItems.filter(currentItems => currentItems.Type.toLowerCase() === avatarfilter)
|
||||
//console.log(currentItems)
|
||||
|
||||
}
|
||||
async function itemaction(action: any,itemid: Number){
|
||||
console.log(action)
|
||||
console.log(itemid)
|
||||
const itemactionresult = await fetch('/api/itemaction', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': jwt,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
action,
|
||||
itemid
|
||||
})
|
||||
})
|
||||
const itemaction = await itemactionresult.json()
|
||||
updateItems()
|
||||
console.log(itemaction)
|
||||
|
||||
}
|
||||
$:{
|
||||
if (avatarfilter != "bodycolors"){
|
||||
updateItems()
|
||||
}else{
|
||||
// pop up modal
|
||||
const modalComponent: ModalComponent = {
|
||||
// Pass a reference to your custom component
|
||||
ref: Colorpicker,
|
||||
props: { jwt: data.jwt},
|
||||
};
|
||||
const d: ModalSettings = {
|
||||
type: 'component',
|
||||
// NOTE: title, body, response, etc are supported!
|
||||
component: modalComponent,
|
||||
modalClasses: 'w-full max-w-[700px] p-4'
|
||||
};
|
||||
modalStore.trigger(d);
|
||||
}
|
||||
}
|
||||
let regeneratebtn = "Regenerate"
|
||||
let regenerateimg: HTMLImageElement
|
||||
|
||||
async function regenerate(){
|
||||
if (regenerating === false){
|
||||
regenerating = true
|
||||
regeneratebtn = "Regenerating..."
|
||||
await fetch(`/api/thumbnailrender/?id=${data.user.userid}&method=regenerate`, {
|
||||
headers: {'Authorization': jwt}
|
||||
})
|
||||
await fetch(`/api/thumbnailrender/?id=${data.user.userid}&type=headshot&method=regenerate`, {
|
||||
headers: {'Authorization': jwt}
|
||||
})
|
||||
avatarstore.update(n => n = "/api/thumbnailrender/?id="+data.user.userid+"&a="+Date.now())
|
||||
regeneratebtn = "Regenerate"
|
||||
regenerating = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="bg-surface-700 p-4 flex flex-row flex-wrap space-x-2 relative max-w-[1000px] m-0 m-auto">
|
||||
|
||||
<div class="bg-surface-800 relative block lg:hidden w-full">
|
||||
<RadioGroup rounded="rounded-md" display="flex absolute right-0 bottom-12">
|
||||
<RadioItem bind:group={avatartype} on:click={() => {updateBodyType("R15")}} value="R15">R15</RadioItem>
|
||||
<RadioItem bind:group={avatartype} on:click={() => {updateBodyType("R6")}} value="R6">R6</RadioItem>
|
||||
</RadioGroup>
|
||||
<img bind:this={regenerateimg} src={avatar} alt={data.user.username} class="h-64 m-auto">
|
||||
<button on:click={regenerate} class="btn variant-filled-primary w-full rounded-md btn-base" disabled={regenerating}>
|
||||
{regeneratebtn}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="w-full flex flex-row gap-2">
|
||||
|
||||
<div class="bg-surface-800 relative hidden lg:block h-[19rem]">
|
||||
<RadioGroup rounded="rounded-md" display="flex absolute right-0 bottom-12">
|
||||
<RadioItem bind:group={avatartype} on:click={() => {updateBodyType("R15")}} value="R15">R15</RadioItem>
|
||||
<RadioItem bind:group={avatartype} on:click={() => {updateBodyType("R6")}} value="R6">R6</RadioItem>
|
||||
</RadioGroup>
|
||||
<img bind:this={regenerateimg} src={avatar} alt={data.user.username} class="h-64">
|
||||
<button on:click={regenerate} class="btn variant-filled-primary w-full rounded-md btn-base" disabled={regenerating}>
|
||||
{regeneratebtn}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="bg-surface-800">
|
||||
<RadioGroup rounded="rounded-md truncate flex-wrap lg:flex-nowrap">
|
||||
<RadioItem bind:group={avatarfilter} value="shirts">Shirts</RadioItem>
|
||||
<RadioItem bind:group={avatarfilter} value="pants">Pants</RadioItem>
|
||||
<RadioItem bind:group={avatarfilter} value="hats">Hats</RadioItem>
|
||||
<RadioItem bind:group={avatarfilter} value="faces">Faces</RadioItem>
|
||||
<RadioItem bind:group={avatarfilter} value="packages">Packages</RadioItem>
|
||||
<RadioItem bind:group={avatarfilter} value="heads">Heads</RadioItem>
|
||||
<RadioItem bind:group={avatarfilter} value="gears">Gears</RadioItem>
|
||||
<RadioItem bind:group={avatarfilter} value="emotes">Emotes</RadioItem>
|
||||
<RadioItem bind:group={avatarfilter} value="bodycolors">🖌️</RadioItem>
|
||||
</RadioGroup>
|
||||
<div class="flex flex-col flex-wrap sm:grid sm:grid-cols-6 gap-2 p-2">
|
||||
{#if currentItems && avatarfilter != "bodycolors"}
|
||||
{#each currentItems as {ItemName, ItemId, Hidden, Equipped}}
|
||||
{#if !Hidden}
|
||||
<Itemcard itemname={ItemName} itemid={ItemId} width="w-24" interact="true" equipped={Equipped} action={itemaction}/>
|
||||
<div class="bg-surface-800 flex flex-row block sm:hidden px-2 relative">
|
||||
<a class="unstyled" href="/catalog/{ItemId}/{ItemName.replace(/[^a-zA-Z ]/g, "").replaceAll(' ', '-')}"><img class="w-20" alt="L" src="/api/thumbnailrender/asset/?id={ItemId}"/></a>
|
||||
<div>
|
||||
<h3 class="truncate">{ItemName}</h3>
|
||||
</div>
|
||||
{#if Equipped === true}
|
||||
<button on:click={() => {itemaction('remove',parseFloat(ItemId))}} class="btn variant-filled-primary rounded-md btn-sm absolute right-0 top-5">Remove</button>
|
||||
{:else}
|
||||
<button on:click={() => {itemaction('wear',parseFloat(ItemId))}} class="btn variant-filled-primary rounded-md btn-sm absolute right-0 top-5">Wear</button>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{/if}
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
|
@ -0,0 +1,152 @@
|
|||
<script lang="ts">
|
||||
import { json } from '@sveltejs/kit';
|
||||
import { slide } from 'svelte/transition'
|
||||
import Itemcard from '../../components/itemcard.svelte';
|
||||
import Rocks from '../../components/rocks.svelte';
|
||||
import { url } from "$lib/url"
|
||||
import { Avatar } from '@skeletonlabs/skeleton';
|
||||
import { page } from '$app/stores';
|
||||
import { goto } from '$app/navigation';
|
||||
import type { PageData } from "../$types";
|
||||
let accessories = false
|
||||
let bodyparts = false
|
||||
let animations = false
|
||||
let items: any
|
||||
$: query = $page.url.searchParams;
|
||||
|
||||
export let data: PageData;
|
||||
|
||||
$: maxiumumPage = data.maxiumumPage;
|
||||
|
||||
$: currentPage = data.currentPage;
|
||||
|
||||
$: currentFilter = data.currentFilter;
|
||||
|
||||
$: currentCategory = data.currentCategory;
|
||||
|
||||
$: items = data.items
|
||||
function setPage(value: number){
|
||||
if (currentPage-value >= 1 && currentPage-value <= maxiumumPage){
|
||||
currentPage -= value
|
||||
query.set('page', currentPage.toString())
|
||||
goto(`?${query.toString()}`)
|
||||
}
|
||||
}
|
||||
function setCategory(event: any){
|
||||
currentCategory = event.target.innerText
|
||||
currentPage = 1
|
||||
query.set('category', currentCategory.toString())
|
||||
query.set('page', "1")
|
||||
goto(`?${query.toString()}`);
|
||||
}
|
||||
function setFilter(event: any){
|
||||
currentFilter = event.target.innerText
|
||||
currentPage = 1
|
||||
query.set('filter', currentFilter.toString())
|
||||
query.set('page', "1");
|
||||
goto(`?${query.toString()}`);
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="sm:flex sm:flex-row gap-2 max-w-[1200px] m-0 m-auto">
|
||||
<div class="hidden sm:block">
|
||||
<div class="flex flex-col">
|
||||
<h2 class="font-bold">Catalog</h2>
|
||||
<h3>Category</h3>
|
||||
<button on:click={(event) => {setFilter(event)}} class="btn hover:text-blue-300">Featured</button>
|
||||
<button on:click={(event) => {setFilter(event)}} class="btn hover:text-blue-300">Best Selling</button>
|
||||
<button on:click={() => {accessories = !accessories}} class="btn hover:text-blue-300">Accessories</button>
|
||||
{#if accessories}
|
||||
<div class="flex flex-col" transition:slide|local="{{ duration: 400 }}">
|
||||
<button on:click={(event) => {setCategory(event)}} class="btn !text-sm hover:text-blue-300">Hats</button>
|
||||
<button on:click={(event) => {setCategory(event)}} class="btn !text-sm hover:text-blue-300">Shirts</button>
|
||||
<button on:click={(event) => {setCategory(event)}} class="btn !text-sm hover:text-blue-300">Pants</button>
|
||||
<button on:click={(event) => {setCategory(event)}} class="btn !text-sm hover:text-blue-300">Gears</button>
|
||||
</div>
|
||||
{/if}
|
||||
<button on:click={() => {bodyparts = !bodyparts}} class="btn hover:text-blue-300">Body Parts</button>
|
||||
{#if bodyparts}
|
||||
<div class="flex flex-col" transition:slide|local="{{ duration: 400 }}">
|
||||
<button on:click={(event) => {setCategory(event)}} class="btn !text-sm hover:text-blue-300">Faces</button>
|
||||
<button on:click={(event) => {setCategory(event)}} class="btn !text-sm hover:text-blue-300">Packages</button>
|
||||
<button on:click={(event) => {setCategory(event)}} class="btn !text-sm hover:text-blue-300">Heads</button>
|
||||
</div>
|
||||
{/if}
|
||||
<button on:click={() => {animations = !animations}} class="btn hover:text-blue-300">Animations</button>
|
||||
{#if animations}
|
||||
<div class="flex flex-col" transition:slide|local="{{ duration: 400 }}">
|
||||
<button on:click={(event) => {setCategory(event)}} class="btn !text-sm hover:text-blue-300">Emotes</button>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-span-5">
|
||||
<!-- mobile filter menu :D -->
|
||||
<div class="block sm:hidden ">
|
||||
<div class="flex flex-row flex-wrap justify-evenly">
|
||||
<button on:click={(event) => {setFilter(event)}} class="btn hover:text-blue-300">Featured</button>
|
||||
<button on:click={(event) => {setFilter(event)}} class="btn hover:text-blue-300">Best Selling</button>
|
||||
<button on:click={() => {accessories = !accessories}} class="btn hover:text-blue-300 w-full">Accessories</button>
|
||||
{#if accessories}
|
||||
<div class="flex flex-row" transition:slide|local="{{ duration: 400 }}">
|
||||
<button on:click={(event) => {setCategory(event)}} class="btn !text-sm hover:text-blue-300">Hats</button>
|
||||
<button on:click={(event) => {setCategory(event)}} class="btn !text-sm hover:text-blue-300">Shirts</button>
|
||||
<button on:click={(event) => {setCategory(event)}} class="btn !text-sm hover:text-blue-300">Pants</button>
|
||||
<button on:click={(event) => {setCategory(event)}} class="btn !text-sm hover:text-blue-300">Gears</button>
|
||||
</div>
|
||||
{/if}
|
||||
<button on:click={() => {bodyparts = !bodyparts}} class="btn hover:text-blue-300 w-full">Body Parts</button>
|
||||
{#if bodyparts}
|
||||
<div class="flex flex-row" transition:slide|local="{{duration: 400 }}">
|
||||
<button on:click={(event) => {setCategory(event)}} class="btn !text-sm hover:text-blue-300">Faces</button>
|
||||
<button on:click={(event) => {setCategory(event)}} class="btn !text-sm hover:text-blue-300">Packages</button>
|
||||
<button on:click={(event) => {setCategory(event)}} class="btn !text-sm hover:text-blue-300">Heads</button>
|
||||
</div>
|
||||
{/if}
|
||||
<button on:click={() => {animations = !animations}} class="btn hover:text-blue-300 w-full">Animations</button>
|
||||
{#if animations}
|
||||
<div class="flex flex-row" transition:slide|local="{{duration: 400 }}">
|
||||
<button on:click={(event) => {setCategory(event)}} class="btn !text-sm hover:text-blue-300">Emotes</button>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<h3>{currentFilter}</h3>
|
||||
<h4>{currentCategory}</h4>
|
||||
<div class="flex flex-col flex-wrap sm:grid sm:grid-cols-6 sm:grid-rows-5 gap-2">
|
||||
{#if items}
|
||||
{#each items as {Name, Price, ItemId, Hidden, Sales}}
|
||||
<Itemcard itemname={Name} itemid={ItemId} price={Price} sales={Sales}/>
|
||||
<a class="unstyled block sm:hidden px-2" href="/catalog/{ItemId}/{Name.replace(/[^a-zA-Z ]/g, "").replaceAll(' ', '-')}"><div class="bg-surface-800 flex flex-row">
|
||||
<img class="w-20" alt="L" src="/api/thumbnailrender/asset/?id={ItemId}"/>
|
||||
<div>
|
||||
<h3 class="truncate">{Name}</h3>
|
||||
<div class="flex flex-row mt-4">
|
||||
<Rocks width="w-6"/>
|
||||
<h4 class="">{Price??"0"}</h4>
|
||||
</div>
|
||||
</div>
|
||||
</div></a>
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
<div class="flex flex-row space-x-2 justify-center">
|
||||
<button on:click={() => {setPage(1)}} class="btn btn-sm bg-surface-600 rounded-md"><</button>
|
||||
<h5 class="">{currentPage} / {maxiumumPage}</h5>
|
||||
<button on:click={() => {setPage(-1)}} class="btn btn-sm bg-surface-600 rounded-md">></button>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
import { error } from '@sveltejs/kit';
|
||||
import type { PageLoad } from './$types';
|
||||
import { url as url2 } from '$lib/url';
|
||||
|
||||
|
||||
export const load = (async ({ fetch, url }) => {
|
||||
let items: [];
|
||||
const currentPage = Number(url.searchParams.get('page') ?? '1');
|
||||
const currentCategory = String(url.searchParams.get('category') ?? 'All');
|
||||
const currentFilter = String(url.searchParams.get('filter') ?? 'Featured');
|
||||
|
||||
const response = await fetch(url2+'/api/catalog/fetch',{method: "POST", body: JSON.stringify({filter: currentFilter,sort: currentCategory, page: currentPage}),headers: {"content-type": "application/json"}});
|
||||
const data = await response.json()
|
||||
|
||||
if (!data.error){
|
||||
return {
|
||||
items: data.data,
|
||||
maxiumumPage: data.pages,
|
||||
currentPage,
|
||||
currentFilter,
|
||||
currentCategory
|
||||
}
|
||||
}
|
||||
|
||||
}) satisfies PageLoad;
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
import { error, redirect } from '@sveltejs/kit';
|
||||
import type { PageLoad } from './$types';
|
||||
|
||||
export const load = (async ({ fetch, params }) => {
|
||||
const res = await fetch(`http://mete0r.xyz/api/catalog/iteminfo/${params.slug}`)
|
||||
const data = await res.json()
|
||||
if (data.error === false){
|
||||
throw redirect(301,'/catalog/'+params.slug+'/'+data.iteminfo.Name.replace(/[^a-zA-Z ]/g, "").replaceAll(' ', '-'))
|
||||
}
|
||||
throw error(404, 'Not found');
|
||||
}) satisfies PageLoad;
|
||||
|
|
@ -0,0 +1,137 @@
|
|||
<script lang="ts">
|
||||
import { Avatar } from "@skeletonlabs/skeleton";
|
||||
import Rocks from '../../../../components/rocks.svelte';
|
||||
import Commentcard from '../../../../components/commentcard.svelte';
|
||||
import type { PageData } from './$types';
|
||||
import { toastStore } from '@skeletonlabs/skeleton';
|
||||
import type { ToastSettings } from '@skeletonlabs/skeleton';
|
||||
import { coinstore } from "$lib/coinstore"
|
||||
|
||||
export let data: PageData;
|
||||
|
||||
let itemid = data.item.ItemId
|
||||
let price = data.item.Price
|
||||
let money = data.user.coins
|
||||
let jwt = data.jwt
|
||||
let disabled = false
|
||||
let buyButton = "Buy"
|
||||
const t: ToastSettings = {
|
||||
message: 'resp',
|
||||
// Optional: Presets for primary | secondary | tertiary | warning
|
||||
preset: 'primary',
|
||||
// Optional: The auto-hide settings
|
||||
autohide: true,
|
||||
timeout: 2000
|
||||
};
|
||||
|
||||
async function buy(){
|
||||
const response = await fetch("/api/purchase", {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': jwt,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
itemid
|
||||
})
|
||||
})
|
||||
const data:any = await response.json()
|
||||
t.message = data.error??data.message
|
||||
if (data.error){
|
||||
t.preset = "error"
|
||||
}else{
|
||||
t.preset = "primary"
|
||||
disabled = true
|
||||
buyButton = "Owned"
|
||||
coinstore.update(n => n - price)
|
||||
}
|
||||
toastStore.trigger(t)
|
||||
}
|
||||
|
||||
async function moderate(){
|
||||
const result = await fetch('/api/moderate', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': jwt,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
itemid
|
||||
})
|
||||
})
|
||||
const moderateresult = await result.json()
|
||||
console.log(moderateresult)
|
||||
document.location = '/catalog/'+itemid
|
||||
}
|
||||
|
||||
if (data.user.inventory){
|
||||
// check if user already owns item
|
||||
for (let v of data.user.inventory){
|
||||
if (v.ItemId === data.item.ItemId){
|
||||
// they already own it
|
||||
disabled = true
|
||||
buyButton = "Owned"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Meteorite - {data.item.Name}</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="bg-surface-700 p-4 space-x-2 flex flex-row flex-wrap sm:flex-nowrap max-w-[1100px] m-0 m-auto">
|
||||
|
||||
<div class="sm:border m-auto sm:p-12 sm:w-[418px] relative">
|
||||
<img class="m-auto border sm:border-0 object-cover" alt={data.item.Name} src="/api/thumbnailrender/asset/?id={data.item.ItemId}"/>
|
||||
{#if data.user.admin === true}
|
||||
<button on:click={moderate} class="btn variant-filled-primary rounded-md btn-sm absolute right-0 top-0">Delete</button>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="text-center sm:text-left">
|
||||
|
||||
<h2>{data.item.Name}</h2>
|
||||
|
||||
|
||||
<h5>By <a href="/users/{data.item.Creator??"0"}">{data.creatorusername??"SushiWasNotHere"}</a></h5>
|
||||
|
||||
|
||||
|
||||
<div class="flex flex-row pt-4">
|
||||
<div class="flex flex-row flex-wrap justify-center">
|
||||
|
||||
<div class="flex flex-row grow">
|
||||
<h5 class="text-base hidden sm:block">Price</h5>
|
||||
<div class="flex flex-row pl-20 w-[17.5rem]">
|
||||
<Rocks width="w-8 mb-10"/>
|
||||
<h4>{data.item.Price}</h4>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button on:click={buy} class="btn variant-filled-primary rounded-md w-full sm:w-44 h-14 btn-lg "disabled={disabled}>{buyButton}</button>
|
||||
|
||||
<div class="flex flex-row flex-wrap w-full">
|
||||
<h5 class="text-base">Type</h5>
|
||||
<div class="flex flex-row pl-20 ">
|
||||
<h5 class="text-base">{data.item.Type}</h5>
|
||||
</div>
|
||||
<div class="flex flex-row w-full pt-2">
|
||||
<h5 class="text-base">Description</h5>
|
||||
<div class=" pl-8 ">
|
||||
<h5 class="text-base w-52 h-48 overflow-y-auto break-words">{data.item?.Description??""}</h5>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<h3 class="pt-4 max-w-[1100px] m-0 m-auto">Commentary</h3>
|
||||
|
||||
<Commentcard AssociatedAssetType={"item"} AssociatedAssetId={data.item.ItemId} jwt={data.jwt} width="1100"/>
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
import { error, redirect } from '@sveltejs/kit';
|
||||
import type { PageLoad } from './$types';
|
||||
|
||||
export const load = (async ({ fetch, params }) => {
|
||||
const res = await fetch(`http://mete0r.xyz/api/catalog/iteminfo/${params.slug}`)
|
||||
const data = await res.json()
|
||||
|
||||
if (params.name != data.iteminfo.Name.replace(/[^a-zA-Z ]/g, "").replaceAll(' ', '-')){
|
||||
throw redirect(301,'/catalog/'+params.slug+'/'+data.iteminfo.Name.replace(/[^a-zA-Z ]/g, "").replaceAll(' ', '-'))
|
||||
}
|
||||
|
||||
if (data.error === false){
|
||||
const creatorusernameresp = await fetch(`http://mete0r.xyz/api/userinfo/${data.iteminfo.Creator??"0"}`)
|
||||
const creatorusername = await creatorusernameresp.json()
|
||||
return {
|
||||
item: data.iteminfo,
|
||||
creatorusername: creatorusername.userinfo.username
|
||||
}
|
||||
}
|
||||
throw error(404, 'Not found');
|
||||
}) satisfies PageLoad;
|
||||
|
|
@ -0,0 +1,150 @@
|
|||
<script lang="ts">
|
||||
import { json } from '@sveltejs/kit';
|
||||
import { slide } from 'svelte/transition'
|
||||
import Itemcard from '../../../../components/itemcard.svelte';
|
||||
import Rocks from '../../../../components/rocks.svelte';
|
||||
import { Avatar } from '@skeletonlabs/skeleton';
|
||||
import { url } from "$lib/url"
|
||||
import type { PageData } from './$types';
|
||||
|
||||
export let data: PageData;
|
||||
let search = data.slug
|
||||
|
||||
|
||||
let accessories = false
|
||||
let bodyparts = false
|
||||
let currentFilter = "Featured"
|
||||
let currentCategory = "All"
|
||||
let currentPage = 1;
|
||||
let maxiumumPage = 1;
|
||||
$:{
|
||||
search = data.slug
|
||||
currentPage = 1
|
||||
updateItems()
|
||||
}
|
||||
$:data,updateItems()
|
||||
let items: any
|
||||
async function updateItems(){
|
||||
|
||||
const response = await fetch(url+'/api/catalog/search',{method: "POST", body: JSON.stringify({filter: currentFilter,sort: currentCategory, page: currentPage, searchquery: search}),headers: {"content-type": "application/json"}});
|
||||
const data = await response.json()
|
||||
if (!data.error){
|
||||
if (items){
|
||||
items.length = 0
|
||||
items = items
|
||||
}
|
||||
items = data.data
|
||||
items = items
|
||||
maxiumumPage = data.pages
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
function setPage(value: number){
|
||||
if (currentPage-value >= 1 && currentPage-value <= maxiumumPage){
|
||||
currentPage -= value
|
||||
updateItems()
|
||||
}
|
||||
}
|
||||
function setCategory(event: any){
|
||||
currentCategory = event.target.innerText
|
||||
currentPage = 1
|
||||
updateItems()
|
||||
}
|
||||
function setFilter(event: any){
|
||||
currentFilter = event.target.innerText
|
||||
currentPage = 1
|
||||
updateItems()
|
||||
}
|
||||
updateItems()
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="sm:flex sm:flex-row gap-2 max-w-[1200px] m-0 m-auto">
|
||||
<div class="hidden sm:block">
|
||||
<div class="flex flex-col">
|
||||
<h2 class="font-bold">Catalog</h2>
|
||||
<h3>Category</h3>
|
||||
<button on:click={(event) => {setFilter(event)}} class="btn hover:text-blue-300">Featured</button>
|
||||
<button on:click={(event) => {setFilter(event)}} class="btn hover:text-blue-300">Best Selling</button>
|
||||
<button on:click={() => {accessories = !accessories}} class="btn hover:text-blue-300">Accessories</button>
|
||||
{#if accessories}
|
||||
<div class="flex flex-col" transition:slide|local="{{ duration: 400 }}">
|
||||
<button on:click={(event) => {setCategory(event)}} class="btn !text-sm hover:text-blue-300">Hats</button>
|
||||
<button on:click={(event) => {setCategory(event)}} class="btn !text-sm hover:text-blue-300">Shirts</button>
|
||||
<button on:click={(event) => {setCategory(event)}} class="btn !text-sm hover:text-blue-300">Pants</button>
|
||||
<button on:click={(event) => {setCategory(event)}} class="btn !text-sm hover:text-blue-300">Gears</button>
|
||||
</div>
|
||||
{/if}
|
||||
<button on:click={() => {bodyparts = !bodyparts}} class="btn hover:text-blue-300">Body Parts</button>
|
||||
{#if bodyparts}
|
||||
<div class="flex flex-col" transition:slide|local="{{ duration: 400 }}">
|
||||
<button on:click={(event) => {setCategory(event)}} class="btn !text-sm hover:text-blue-300">Faces</button>
|
||||
<button on:click={(event) => {setCategory(event)}} class="btn !text-sm hover:text-blue-300">Packages</button>
|
||||
<button on:click={(event) => {setCategory(event)}} class="btn !text-sm hover:text-blue-300">Heads</button>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-span-5">
|
||||
<!-- mobile filter menu :D -->
|
||||
<div class="block sm:hidden ">
|
||||
<div class="flex flex-row flex-wrap justify-evenly">
|
||||
<button on:click={(event) => {setFilter(event)}} class="btn hover:text-blue-300">Featured</button>
|
||||
<button on:click={(event) => {setFilter(event)}} class="btn hover:text-blue-300">Best Selling</button>
|
||||
<button on:click={() => {accessories = !accessories}} class="btn hover:text-blue-300 w-full">Accessories</button>
|
||||
{#if accessories}
|
||||
<div class="flex flex-row" transition:slide|local="{{ duration: 400 }}">
|
||||
<button on:click={(event) => {setCategory(event)}} class="btn !text-sm hover:text-blue-300">Hats</button>
|
||||
<button on:click={(event) => {setCategory(event)}} class="btn !text-sm hover:text-blue-300">Shirts</button>
|
||||
<button on:click={(event) => {setCategory(event)}} class="btn !text-sm hover:text-blue-300">Pants</button>
|
||||
<button on:click={(event) => {setCategory(event)}} class="btn !text-sm hover:text-blue-300">Gears</button>
|
||||
</div>
|
||||
{/if}
|
||||
<button on:click={() => {bodyparts = !bodyparts}} class="btn hover:text-blue-300 w-full">Body Parts</button>
|
||||
{#if bodyparts}
|
||||
<div class="flex flex-row" transition:slide|local="{{duration: 400 }}">
|
||||
<button on:click={(event) => {setCategory(event)}} class="btn !text-sm hover:text-blue-300">Faces</button>
|
||||
<button on:click={(event) => {setCategory(event)}} class="btn !text-sm hover:text-blue-300">Packages</button>
|
||||
<button on:click={(event) => {setCategory(event)}} class="btn !text-sm hover:text-blue-300">Heads</button>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<h3>{currentFilter}</h3>
|
||||
<h4>{currentCategory}</h4>
|
||||
<div class="flex flex-col flex-wrap sm:grid sm:grid-cols-6 sm:grid-rows-5 gap-2">
|
||||
{#if items}
|
||||
{#each items as {Name, Price, ItemId, Hidden, Sales}}
|
||||
<Itemcard itemname={Name} itemid={ItemId} price={Price} sales={Sales}/>
|
||||
<a class="unstyled block sm:hidden px-2" href="/catalog/{ItemId}/{Name.replace(/[^a-zA-Z ]/g, "").replaceAll(' ', '-')}"><div class="bg-surface-800 flex flex-row">
|
||||
<img class="w-20" alt="L" src="/api/thumbnailrender/asset/?id={ItemId}"/>
|
||||
<div>
|
||||
<h3 class="truncate">{Name}</h3>
|
||||
<div class="flex flex-row mt-4">
|
||||
<Rocks width="w-6"/>
|
||||
<h4 class="">{Price??"0"}</h4>
|
||||
</div>
|
||||
</div>
|
||||
</div></a>
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
<div class="flex flex-row space-x-2 justify-center">
|
||||
<button on:click={() => {setPage(1)}} class="btn btn-sm bg-surface-600 rounded-md"><</button>
|
||||
<h5 class="">{currentPage} / {maxiumumPage}</h5>
|
||||
<button on:click={() => {setPage(-1)}} class="btn btn-sm bg-surface-600 rounded-md">></button>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
export const load = ({ params }) => {
|
||||
return {
|
||||
slug: params.slug
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,142 @@
|
|||
<script lang="ts">
|
||||
import { Tab, TabGroup } from "@skeletonlabs/skeleton";
|
||||
import { writable, type Writable } from 'svelte/store';
|
||||
import Clothing from '../../components/develop/clothing.svelte';
|
||||
import Games from "../../components/develop/games.svelte"
|
||||
import Asset from "../../components/develop/asset.svelte"
|
||||
import Packages from "../../components/develop/admin/packages.svelte"
|
||||
import Accessories from "../../components/develop/admin/accessories.svelte"
|
||||
import { Modal, modalStore } from '@skeletonlabs/skeleton';
|
||||
import type { ModalSettings, ModalComponent } from '@skeletonlabs/skeleton';
|
||||
import { slide } from 'svelte/transition'
|
||||
import type { PageData } from "./$types";
|
||||
import Launchmodal from "../../components/games/launchmodal.svelte";
|
||||
|
||||
let storeTab = 'Shirts'
|
||||
|
||||
export let data: PageData;
|
||||
|
||||
let openstudio = false
|
||||
|
||||
function launch(version: string){
|
||||
document.location.href = `meteorite-launch://${0}[${data.jwt}[${version}[${"studio"}`
|
||||
|
||||
const modalComponent: ModalComponent = {
|
||||
// Pass a reference to your custom component
|
||||
ref: Launchmodal,
|
||||
// Add your props as key/value pairs
|
||||
// Provide default slot content as a template literal
|
||||
slot: '<p>Skeleton</p>'
|
||||
};
|
||||
const d: ModalSettings = {
|
||||
type: 'component',
|
||||
// NOTE: title, body, response, etc are supported!,
|
||||
component: modalComponent
|
||||
};
|
||||
modalStore.trigger(d);
|
||||
|
||||
setTimeout(function(){
|
||||
modalStore.close();
|
||||
}, 4000); // wait 4 seconds
|
||||
}
|
||||
|
||||
</script>
|
||||
<div class="bg-surface-800 p-4 flex flex-row flex-wrap space-x-2 space-y-4 relative max-w-[970px] m-0 m-auto">
|
||||
|
||||
<div class="w-full flex flex-row">
|
||||
|
||||
<h3>Develop</h3>
|
||||
|
||||
<div class="grow flex flex-row">
|
||||
|
||||
<button on:click={() => {openstudio = !openstudio}} class="btn hover:text-blue-300">Open Studio</button>
|
||||
{#if openstudio}
|
||||
<div class="flex flex-row" transition:slide|local="{{ duration: 400, axis: "x" }}">
|
||||
<button on:click={() => {launch("2016")}} class="btn !text-sm">2016</button>
|
||||
<button on:click={() => {launch("2020")}} class="btn !text-sm">2020</button>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<TabGroup justify="flex flex-row flex-wrap sm:flex-col sm:w-32" borderWidth="border-l-2" rounded="">
|
||||
<div >
|
||||
<Tab bind:group={storeTab} value="Shirts">Shirts</Tab>
|
||||
</div>
|
||||
<div >
|
||||
<Tab bind:group={storeTab} value="Pants">Pants</Tab>
|
||||
</div>
|
||||
<div >
|
||||
<Tab bind:group={storeTab} value="games">Games</Tab>
|
||||
</div>
|
||||
<div >
|
||||
<Tab bind:group={storeTab} value="audios">Audios</Tab>
|
||||
</div>
|
||||
<div >
|
||||
<Tab bind:group={storeTab} value="badges">Badges</Tab>
|
||||
</div>
|
||||
<div >
|
||||
<Tab bind:group={storeTab} value="meshes">Meshes</Tab>
|
||||
</div>
|
||||
<div >
|
||||
<Tab bind:group={storeTab} value="userads">User Ads</Tab>
|
||||
</div>
|
||||
<div >
|
||||
<Tab bind:group={storeTab} value="videos">Videos</Tab>
|
||||
</div>
|
||||
<div >
|
||||
<Tab bind:group={storeTab} value="gamepasses">Game Passes</Tab>
|
||||
</div>
|
||||
<div >
|
||||
<Tab bind:group={storeTab} value="devproducts">Developer Products</Tab>
|
||||
</div>
|
||||
{#if data.user.admin === true || data.user.ugcpermission === true}
|
||||
<h3 class="w-full">Admin</h3>
|
||||
<div >
|
||||
<Tab bind:group={storeTab} value="Hats">Hats</Tab>
|
||||
</div>
|
||||
<div >
|
||||
<Tab bind:group={storeTab} value="Gears">Gears</Tab>
|
||||
</div>
|
||||
<div >
|
||||
<Tab bind:group={storeTab} value="Faces">Faces</Tab>
|
||||
</div>
|
||||
<div >
|
||||
<Tab bind:group={storeTab} value="Packages">Packages</Tab>
|
||||
</div>
|
||||
<div >
|
||||
<Tab bind:group={storeTab} value="Animation Bundle">Animation Bundles</Tab>
|
||||
</div>
|
||||
<div >
|
||||
<Tab bind:group={storeTab} value="Emotes">Emotes</Tab>
|
||||
</div>
|
||||
{/if}
|
||||
</TabGroup>
|
||||
{#if storeTab === "Shirts" || storeTab === "Pants"}
|
||||
<Clothing jwt={data.jwt} type={storeTab}/>
|
||||
{/if}
|
||||
|
||||
{#if storeTab === "games"}
|
||||
<Games jwt={data.jwt} type={storeTab} data={data}/>
|
||||
{/if}
|
||||
|
||||
{#if storeTab === "audios" || storeTab === "badges" || storeTab === "meshes" || storeTab === "userads" || storeTab === "gamepasses" || storeTab === "videos"}
|
||||
<Asset jwt={data.jwt} type={storeTab}/>
|
||||
{/if}
|
||||
|
||||
{#if storeTab === "Packagess"}
|
||||
<Packages jwt={data.jwt} type={storeTab}/>
|
||||
{/if}
|
||||
|
||||
{#if storeTab === "Hats" || storeTab === "Gears" || storeTab === "Faces" || storeTab === "Emotes"}
|
||||
<Accessories jwt={data.jwt} type={storeTab}/>
|
||||
{/if}
|
||||
|
||||
{#if storeTab === "userads"}
|
||||
|
||||
{/if}
|
||||
|
||||
|
||||
</div>
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
<script lang="ts">
|
||||
import Usercard from '../../components/usercard.svelte';
|
||||
import type { PageData } from './$types';
|
||||
|
||||
async function refresh(){
|
||||
const response = await fetch('/api/friends/friend-requests',{method: "POST",headers: {"content-type": "application/json",'Authorization': data.jwt,}});
|
||||
const friendata = await response.json()
|
||||
data.friendRequests = friendata.data
|
||||
data.friendRequests = data.friendRequests
|
||||
}
|
||||
|
||||
|
||||
export let data: PageData;
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<div class="space-y-4 max-w-[1200px] m-0 m-auto">
|
||||
<div class="flex flex-row flex-wrap">
|
||||
<h2 class="grow">My Friend Requests</h2>
|
||||
<!-- <button class="btn variant-filled-primary rounded-md">Ignore All</button> -->
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col flex-wrap md:grid md:grid-cols-3 md:grid-rows-4 gap-2">
|
||||
{#if data.friendRequests}
|
||||
{#each data.friendRequests as {userid, username}}
|
||||
<Usercard userid={userid} username={username} friendRequest={true} jwt={data.jwt} refresh={refresh}/>
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
import { error } from '@sveltejs/kit';
|
||||
import type { PageLoad } from './$types';
|
||||
|
||||
export const load = (async ({ fetch, parent }) => {
|
||||
let data = await parent()
|
||||
|
||||
const response = await fetch('http://mete0r.xyz/api/friends/friend-requests',{method: "POST",headers: {"content-type": "application/json",'Authorization': data.jwt,}});
|
||||
const friendata = await response.json()
|
||||
|
||||
return {
|
||||
friendRequests: friendata.data
|
||||
}
|
||||
|
||||
}) satisfies PageLoad;
|
||||
|
|
@ -0,0 +1,120 @@
|
|||
<script lang="ts">
|
||||
import type { PageData } from "../$types";
|
||||
import Gamecard from "../../components/gamecard.svelte"
|
||||
import Scrollbar from "../../components/scrollbutton.svelte"
|
||||
export let data: PageData;
|
||||
let gamearrays:any = {Popular: {array: [],cursor:1},OurRecommendations: {array: [],cursor:1}, Visits: {array: [],cursor:1}, NewestArrivals: {array: [],cursor:1} }
|
||||
gamearrays.Popular.array = data?.firstpaint.Popular.array
|
||||
gamearrays.OurRecommendations.array = data?.firstpaint.OurRecommendations.array
|
||||
gamearrays.Visits.array = data?.firstpaint.Visits.array
|
||||
gamearrays.NewestArrivals.array = data?.firstpaint.NewestArrivals.array
|
||||
let scrollTimer: NodeJS.Timeout | null, lastScrollFireTime = 0;
|
||||
|
||||
async function addToArray(array: string){
|
||||
const response = await fetch('/games/scroll',{method: "POST", body: JSON.stringify({cursor: gamearrays[array].cursor,type: array}),headers: {"content-type": "application/json"}});
|
||||
const data = await response.json()
|
||||
if (!data.error && data != "[]"){
|
||||
//populararray = populararray.concat(data)
|
||||
gamearrays[array].array = gamearrays[array].array.concat(data)
|
||||
//console.log(gamearrays)
|
||||
//console.log(populararray)
|
||||
gamearrays[array].cursor += 1
|
||||
//console.log(gamearrays)
|
||||
}
|
||||
|
||||
}
|
||||
//addToArray("Popular")
|
||||
//addToArray("OurRecommendations")
|
||||
//addToArray("Visits")
|
||||
//addToArray("NewestArrivals")
|
||||
|
||||
|
||||
let popular: HTMLDivElement;
|
||||
|
||||
let rec: HTMLDivElement;
|
||||
|
||||
let visit: HTMLDivElement;
|
||||
|
||||
let newest: HTMLDivElement;
|
||||
|
||||
function scrolled(scrolleft: any,clientwidth: any,scrollWidth: any,array: string){
|
||||
const diff = Math.abs(scrolleft+clientwidth - scrollWidth)
|
||||
if (diff < 500){
|
||||
|
||||
var minScrollTime = 300;
|
||||
var now = new Date().getTime();
|
||||
|
||||
function processScroll() {
|
||||
addToArray(array)
|
||||
}
|
||||
|
||||
if (!scrollTimer) {
|
||||
if (now - lastScrollFireTime > (3 * minScrollTime)) {
|
||||
processScroll(); // fire immediately on first scroll
|
||||
lastScrollFireTime = now;
|
||||
}
|
||||
scrollTimer = setTimeout(function() {
|
||||
scrollTimer = null;
|
||||
lastScrollFireTime = new Date().getTime();
|
||||
processScroll();
|
||||
}, minScrollTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<div class="space-y-4 max-w-[1800px] m-0 m-auto">
|
||||
<h2 class="">Popular </h2>
|
||||
<div class="">
|
||||
<Scrollbar scrollDirection="left" scrollElement={popular}/>
|
||||
<Scrollbar scrollDirection="right" scrollElement={popular}/>
|
||||
|
||||
<div bind:this={popular} on:scroll={(e)=>scrolled(e.target?.scrollLeft,e.target?.clientWidth,e.target?.scrollWidth,"Popular")} class="grid grid-flow-col grid-rows-none overflow-hidden auto-cols-max gap-4 snap-mandatory snap-x scroll-smooth h-40 sm:h-60">
|
||||
{#each gamearrays.Popular.array as {nameofgame, idofgame, version, visits, numberofplayers, useridofowner, owner}}
|
||||
<Gamecard gamename={nameofgame} playercount={numberofplayers} version={version} useridofowner={useridofowner} visits={visits} idofgame={idofgame} useragent={data.useragent} username={owner.username} />
|
||||
{/each}
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<h2 class="">Our Recommendations</h2>
|
||||
<div class="">
|
||||
<Scrollbar scrollDirection="left" scrollElement={rec}/>
|
||||
<Scrollbar scrollDirection="right" scrollElement={rec}/>
|
||||
|
||||
<div bind:this={rec} on:scroll={(e)=>scrolled(e.target?.scrollLeft,e.target?.clientWidth,e.target?.scrollWidth,"OurRecommendations")} class="grid grid-flow-col grid-rows-none overflow-hidden auto-cols-max gap-4 snap-mandatory snap-x scroll-smooth h-40 sm:h-60">
|
||||
{#each gamearrays.OurRecommendations.array as {nameofgame, idofgame, version, visits, numberofplayers, useridofowner, owner}}
|
||||
<Gamecard gamename={nameofgame} playercount={numberofplayers} version={version} useridofowner={useridofowner} visits={visits} idofgame={idofgame} useragent={data.useragent} username={owner.username} />
|
||||
{/each}
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<h2 class="">Most Visited</h2>
|
||||
<div class="">
|
||||
<Scrollbar scrollDirection="left" scrollElement={visit}/>
|
||||
<Scrollbar scrollDirection="right" scrollElement={visit}/>
|
||||
|
||||
<div bind:this={visit} on:scroll={(e)=>scrolled(e.target?.scrollLeft,e.target?.clientWidth,e.target?.scrollWidth,"Visits")} class="grid grid-flow-col grid-rows-none overflow-hidden auto-cols-max gap-4 snap-mandatory snap-x scroll-smooth h-40 sm:h-60">
|
||||
{#each gamearrays.Visits.array as {nameofgame, idofgame, version, visits, numberofplayers, useridofowner, owner}}
|
||||
<Gamecard gamename={nameofgame} playercount={numberofplayers} version={version} useridofowner={useridofowner} visits={visits} idofgame={idofgame} useragent={data.useragent} username={owner.username} />
|
||||
{/each}
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<h2 class="">Newest Arrivals</h2>
|
||||
<div class="">
|
||||
<Scrollbar scrollDirection="left" scrollElement={newest}/>
|
||||
<Scrollbar scrollDirection="right" scrollElement={newest}/>
|
||||
|
||||
<div bind:this={newest} on:scroll={(e)=>scrolled(e.target?.scrollLeft,e.target?.clientWidth,e.target?.scrollWidth,"NewestArrivals")} class="grid grid-flow-col grid-rows-none overflow-hidden auto-cols-max gap-4 snap-mandatory snap-x scroll-smooth h-40 sm:h-60">
|
||||
{#each gamearrays.NewestArrivals.array as {nameofgame, idofgame, version, visits, numberofplayers, useridofowner, owner}}
|
||||
<Gamecard gamename={nameofgame} playercount={numberofplayers} version={version} useridofowner={useridofowner} visits={visits} idofgame={idofgame} useragent={data.useragent} username={owner.username} />
|
||||
{/each}
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
import type { PageLoad } from './$types';
|
||||
import { url } from "$lib/url"
|
||||
|
||||
export const load = (async ({ fetch }) => {
|
||||
let gamearrays:any = {Popular: {array: []},OurRecommendations: {array: []}, Visits: {array: []}, NewestArrivals: {array: []} }
|
||||
|
||||
async function addToArray(){
|
||||
const response = await fetch(url+'/games/firstpaint',{method: "POST",headers: {"content-type": "application/json"}});
|
||||
const data = await response.json()
|
||||
if (!data.error && data != "[]"){
|
||||
gamearrays = data
|
||||
}
|
||||
|
||||
}
|
||||
await addToArray()
|
||||
|
||||
return {
|
||||
firstpaint: gamearrays
|
||||
}
|
||||
|
||||
}) satisfies PageLoad;
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
import { error, redirect } from '@sveltejs/kit';
|
||||
import type { PageLoad } from './$types';
|
||||
|
||||
export const load = (async ({ fetch, params }) => {
|
||||
const res = await fetch(`http://mete0r.xyz/games/gameinfo/${params.slug}`)
|
||||
const data = await res.json()
|
||||
if (data.error === false){
|
||||
throw redirect(301,'/games/'+params.slug+'/'+data.gameinfo.nameofgame.replace(/[^a-zA-Z ]/g, "").replaceAll(' ', '-'))
|
||||
}
|
||||
throw error(404, 'Not found');
|
||||
}) satisfies PageLoad;
|
||||
|
|
@ -0,0 +1,248 @@
|
|||
<script lang="ts">
|
||||
import type { PageData } from './$types';
|
||||
import { menu, tooltip } from '@skeletonlabs/skeleton';
|
||||
import Commentcard from '../../../../components/commentcard.svelte';
|
||||
import { TabGroup, Tab } from '@skeletonlabs/skeleton';
|
||||
import { writable, type Writable } from 'svelte/store';
|
||||
import { Modal, modalStore } from '@skeletonlabs/skeleton';
|
||||
import type { ModalSettings, ModalComponent } from '@skeletonlabs/skeleton';
|
||||
import { invalidate } from '$app/navigation';
|
||||
import Skybanner from '../../../../components/skybanner.svelte';
|
||||
import Launchmodal from '../../../../components/games/launchmodal.svelte';
|
||||
import Linkdiscordmodal from '../../../../components/games/linkdiscordmodal.svelte';
|
||||
import Advertisemodal from '../../../../components/assets/advertisemodal.svelte';
|
||||
import Itemcard from '../../../../components/itemcard.svelte';
|
||||
let storeTab = 'About'
|
||||
|
||||
export let data: PageData;
|
||||
const jwt = data.jwt
|
||||
|
||||
let likefill = "none"
|
||||
let dislikefill = "none"
|
||||
let gamepasses: any[] = []
|
||||
|
||||
function launch(){
|
||||
console.log(data.user.discordid)
|
||||
if (!data?.user?.discordid){
|
||||
const modalComponent: ModalComponent = {
|
||||
// Pass a reference to your custom component
|
||||
ref: Linkdiscordmodal,
|
||||
// Add your props as key/value pairs
|
||||
// Provide default slot content as a template literal
|
||||
slot: '<p>Skeleton</p>'
|
||||
};
|
||||
const d: ModalSettings = {
|
||||
type: 'component',
|
||||
// NOTE: title, body, response, etc are supported!,
|
||||
component: modalComponent
|
||||
}
|
||||
modalStore.trigger(d);
|
||||
return
|
||||
}
|
||||
document.location.href = `meteorite-launch://${data.game.idofgame}[${data.jwt}[${data.game.version}`
|
||||
|
||||
const modalComponent: ModalComponent = {
|
||||
// Pass a reference to your custom component
|
||||
ref: Launchmodal,
|
||||
// Add your props as key/value pairs
|
||||
// Provide default slot content as a template literal
|
||||
slot: '<p>Skeleton</p>'
|
||||
};
|
||||
const d: ModalSettings = {
|
||||
type: 'component',
|
||||
// NOTE: title, body, response, etc are supported!,
|
||||
component: modalComponent
|
||||
};
|
||||
modalStore.trigger(d);
|
||||
|
||||
setTimeout(function(){
|
||||
modalStore.close();
|
||||
}, 4000); // wait 4 seconds
|
||||
}
|
||||
|
||||
async function shutdown(){
|
||||
const shutdownresp = await fetch("http://mete0r.xyz/games/shutdown", {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': jwt,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
gameid: data.game.idofgame
|
||||
})
|
||||
})
|
||||
const shutdownjson = await shutdownresp.json()
|
||||
console.log(shutdownjson)
|
||||
}
|
||||
|
||||
async function evictplayer(userid: Number){
|
||||
const evictplayerresp = await fetch("http://mete0r.xyz/games/evictplayer", {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': jwt,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
gameid: data.game.idofgame,
|
||||
userid
|
||||
})
|
||||
})
|
||||
const evictplayerjson = await evictplayerresp.json()
|
||||
console.log(evictplayerjson)
|
||||
invalidate((url) => url.pathname === '/games/gameinfo/'+data.game.idofgame);
|
||||
}
|
||||
|
||||
function advertise(){
|
||||
const modalComponent: ModalComponent = {
|
||||
ref: Advertisemodal,
|
||||
props: {jwt: data.jwt, itemid: data.game.idofgame, type: "game"}
|
||||
};
|
||||
const d: ModalSettings = {
|
||||
type: 'component',
|
||||
component: modalComponent
|
||||
}
|
||||
modalStore.trigger(d);
|
||||
}
|
||||
|
||||
async function requestStore(){
|
||||
const res = await fetch(`/games/gameinfo/${data.game.idofgame}/store`)
|
||||
gamepasses = (await res.json()).gameinfo
|
||||
}
|
||||
|
||||
$:if (storeTab === "Store" && gamepasses.length === 0){
|
||||
requestStore()
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Meteorite - {data.game.nameofgame}</title>
|
||||
<meta content="Meteorite!" property="og:title" />
|
||||
<meta content="{data.game.nameofgame} - Join others who are recreating the game world." property="og:description" />
|
||||
<meta content="https://mete0r.xyz" property="og:url" />
|
||||
<meta content="#6f00ff" data-react-helmet="true" name="theme-color" />
|
||||
|
||||
<meta name="twitter:image:src" content="https://mete0r.xyz/assets/gameassets/thumbnail-{data.game.idofgame}.png" />
|
||||
<meta name="twitter:site" content="@Meteorite" />
|
||||
<meta name="twitter:card" content="summary_large_image">
|
||||
<meta name="twitter:title" content="{data.game.nameofgame} - Join others who are recreating the game world." />
|
||||
<meta name="twitter:description" content="Meteorite" />
|
||||
</svelte:head>
|
||||
<div class="max-w-[1400px] m-0 m-auto">
|
||||
<Skybanner customclass="float-left" customMediaClass="hidden xl:block"/>
|
||||
<Skybanner customclass="float-right" customMediaClass="hidden xl:block"/>
|
||||
<div class="bg-surface-700 p-4 flex flex-row flex-wrap space-x-4 relative max-w-[970px] m-0 m-auto">
|
||||
<img class="h-[360px] w-[640px] aspect-video m-auto" alt={data.game.nameofgame} src="http://mete0r.xyz/assets/gameassets/thumbnail-{data.game.idofgame}.png#" style="height:360px;width:640px"/>
|
||||
<div class="grow">
|
||||
<div class=" flex flex-row justify-evenly">
|
||||
<h3 class="font-bold truncate overflow-auto w-64">{data.game.nameofgame}</h3>
|
||||
{#if data?.user?.admin === true || data.game?.useridofowner === data?.user?.userid}
|
||||
<span class="relative">
|
||||
<button class="w-6 pt-2" use:menu={{ menu: 'navigationgame' }}>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-menu"><line x1="3" y1="12" x2="21" y2="12"></line><line x1="3" y1="6" x2="21" y2="6"></line><line x1="3" y1="18" x2="21" y2="18"></line></svg>
|
||||
</button>
|
||||
<nav class="list-nav rounded-none pb-2 pt-2 w-40" data-menu="navigationgame">
|
||||
<ul>
|
||||
<button on:click={shutdown} class="btn variant-filled-primary !rounded-md w-full btn-sm text-xs">Shut Down All Servers</button>
|
||||
<button on:click={advertise} class="btn variant-filled-primary !rounded-md w-full btn-sm text-xs">Advertise</button>
|
||||
</ul>
|
||||
</nav>
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<h5 class="text-base truncate w-64">By <a href="/users/{data.game.useridofowner}">{data.creatorusername}</a></h5>
|
||||
<div class="pt-10 sm:pt-40 px-6">
|
||||
{#if data.user}
|
||||
{#if data.useragent.includes("Android") === true || data.useragent.includes("iPhone") === true && data.game.version != "2020"}
|
||||
<a data-sveltekit-reload class="btn variant-filled-primary rounded-md w-full btn-lg" href="/games/start?placeid={data.game.idofgame}">Play</a>
|
||||
{:else if data.useragent.includes("Android") === true || data.useragent.includes("iPhone") === true}
|
||||
<a rel="external" class="btn variant-filled-primary rounded-md w-full btn-lg" href="robloxmobile://placeID={data.game.idofgame}">Play</a>
|
||||
{:else}
|
||||
<button on:click={launch} class="btn variant-filled-primary rounded-md w-full btn-lg">Play</button>
|
||||
{/if}
|
||||
|
||||
{:else}
|
||||
<a class="btn variant-filled-primary rounded-md w-full btn-lg" href="/">Register</a>
|
||||
|
||||
{/if}
|
||||
</div>
|
||||
<div class="flex flex-row pt-4 justify-evenly">
|
||||
<div class="flex flex-row space-x-2">
|
||||
<button><svg on:mouseover={() => {likefill = "lime"}} on:mouseleave={() => {likefill = "none"}} xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill={likefill} stroke="lime" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-thumbs-up"><path d="M14 9V5a3 3 0 0 0-3-3l-4 9v11h11.28a2 2 0 0 0 2-1.7l1.38-9a2 2 0 0 0-2-2.3zM7 22H4a2 2 0 0 1-2-2v-7a2 2 0 0 1 2-2h3"></path></svg></button>
|
||||
<h5 class="!text-sm pt-2">0</h5>
|
||||
</div>
|
||||
<div class="flex flex-row space-x-2">
|
||||
<button class=""><svg on:mouseover={() => {dislikefill = "red"}} on:mouseleave={() => {dislikefill = "none"}} xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill={dislikefill} stroke="red" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-thumbs-up"><path d="M10 15v4a3 3 0 0 0 3 3l4-9V2H5.72a2 2 0 0 0-2 1.7l-1.38 9a2 2 0 0 0 2 2.3zm7-13h2.67A2.31 2.31 0 0 1 22 4v7a2.31 2.31 0 0 1-2.33 2H17"></path></svg></button>
|
||||
<h5 class="!text-sm pt-2">0</h5>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-surface-700 mt-4 flex flex-row justify-around flex-wrap relative max-w-[970px] m-0 m-auto">
|
||||
<TabGroup hover="" class="w-full">
|
||||
<Tab bind:group={storeTab} flex="grow" value="About">About</Tab>
|
||||
<Tab bind:group={storeTab} flex="grow" value="Store">Store</Tab>
|
||||
<Tab bind:group={storeTab} flex="grow" value="Players">Players</Tab>
|
||||
</TabGroup>
|
||||
|
||||
</div>
|
||||
<h3 class="pt-4 max-w-[970px] m-0 m-auto">{storeTab}</h3>
|
||||
{#if storeTab === "About"}
|
||||
|
||||
<div class="bg-surface-700 p-4 flex flex-row flex-wrap relative max-w-[970px] m-0 m-auto">
|
||||
<h5 class="break-words text-base whitespace-pre-line">{data.game?.descrption??"No Description"}</h5>
|
||||
|
||||
<div class="w-full flex flex-row justify-around text-center">
|
||||
<div class="">
|
||||
<p class="font-bold">Playing</p>
|
||||
<p>{data.game.numberofplayers}</p>
|
||||
</div>
|
||||
|
||||
<div class="">
|
||||
<p class="font-bold">Visits</p>
|
||||
<p>{data.game?.visits??"0"}</p>
|
||||
</div>
|
||||
|
||||
<div class="">
|
||||
<p class="font-bold">Created</p>
|
||||
<p>{data.game.creationdate}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<h3 class="pt-4 max-w-[970px] m-0 m-auto">Commentary</h3>
|
||||
<Commentcard AssociatedAssetType={"game"} AssociatedAssetId={data.game.idofgame} jwt={data.jwt} width="970"/>
|
||||
|
||||
{/if}
|
||||
|
||||
{#if storeTab === "Players"}
|
||||
<div class="bg-surface-700 p-4 flex flex-row flex-wrap relative max-w-[970px] m-0 m-auto">
|
||||
<div class="flex flex-row flex-wrap">
|
||||
{#if data.game.players}
|
||||
{#each data.game.players as {userid, name}}
|
||||
<div class="relative">
|
||||
{#if data?.user?.admin === true || data.game?.useridofowner === data?.user?.userid}
|
||||
<button on:click={() => {evictplayer(userid)}} class="btn variant-filled-primary rounded-md btn-sm w-8 h-6 absolute right-0 top-0 z-10">Kick</button>
|
||||
{/if}
|
||||
<a href="/users/{userid}" class="relative">
|
||||
<img title="{name}" alt="{name}" class="h-20" src="/api/thumbnailrender/?id={userid}"/></a></div>
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if storeTab === "Store"}
|
||||
<div class="bg-surface-700 p-4 flex flex-row flex-wrap relative max-w-[970px] m-0 m-auto">
|
||||
<div class="flex flex-row flex-wrap">
|
||||
{#if gamepasses}
|
||||
{#each gamepasses as {Name, Price, ItemId, Hidden, Sales}}
|
||||
<Itemcard itemname={Name} itemid={ItemId} price={Price} sales={Sales}/>
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
import { error, redirect } from '@sveltejs/kit';
|
||||
import type { PageLoad } from './$types';
|
||||
|
||||
export const load = (async ({ fetch, params }) => {
|
||||
const res = await fetch(`http://mete0r.xyz/games/gameinfo/${params.slug}`)
|
||||
const data = await res.json()
|
||||
|
||||
if (params.name != data.gameinfo.nameofgame.replace(/[^a-zA-Z ]/g, "").replaceAll(' ', '-')){
|
||||
throw redirect(301,'/games/'+params.slug+'/'+data.gameinfo.nameofgame.replace(/[^a-zA-Z ]/g, "").replaceAll(' ', '-'))
|
||||
}
|
||||
|
||||
if (data.error === false){
|
||||
return {
|
||||
game: data.gameinfo,
|
||||
creatorusername: data.gameinfo.owner.username
|
||||
}
|
||||
}
|
||||
throw error(404, 'Not found');
|
||||
}) satisfies PageLoad;
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
<script lang="ts">
|
||||
import Gamecard from "../../../../components/gamecard.svelte"
|
||||
let gamearray: any[] = []
|
||||
let currentcursor = 1
|
||||
import type { PageData } from './$types';
|
||||
import { onMount } from 'svelte';
|
||||
let scrollTimer: NodeJS.Timeout | null, lastScrollFireTime = 0;
|
||||
|
||||
export let data: PageData;
|
||||
|
||||
let search = data.slug
|
||||
$:{
|
||||
search = data.slug
|
||||
}
|
||||
$:data,fetchfirst()
|
||||
|
||||
async function fetchfirst(){
|
||||
const response = await fetch('http://mete0r.xyz/games/search',{method: "POST", body: JSON.stringify({cursor: 0, searchquery: search}),headers: {"content-type": "application/json"}});
|
||||
const data = await response.json()
|
||||
if (!data.error && data != "[]"){
|
||||
//populararray = populararray.concat(data)
|
||||
if (gamearray){
|
||||
gamearray.length = 0
|
||||
gamearray = gamearray
|
||||
}
|
||||
gamearray = data
|
||||
gamearray = gamearray
|
||||
//console.log(gamearrays)
|
||||
//console.log(populararray)
|
||||
//console.log(gamearrays)
|
||||
}
|
||||
}
|
||||
|
||||
async function addToArray(){
|
||||
const response = await fetch('http://mete0r.xyz/games/search',{method: "POST", body: JSON.stringify({cursor: currentcursor, searchquery: search}),headers: {"content-type": "application/json"}});
|
||||
const data = await response.json()
|
||||
if (!data.error && data != "[]"){
|
||||
//populararray = populararray.concat(data)
|
||||
gamearray = gamearray.concat(data)
|
||||
//console.log(gamearrays)
|
||||
//console.log(populararray)
|
||||
currentcursor += 1
|
||||
//console.log(gamearrays)
|
||||
}
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="space-y-4 max-w-[1800px] m-0 m-auto">
|
||||
<h2 class="">Results for {search}: </h2>
|
||||
<div class="flex flex-row flex-wrap gap-2">
|
||||
{#each gamearray as {nameofgame, idofgame, version, visits, numberofplayers, useridofowner}}
|
||||
<Gamecard gamename={nameofgame} playercount={numberofplayers} version={version} useridofowner={useridofowner} visits={visits} idofgame={idofgame} useragent={data.useragent} />
|
||||
{/each}
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
export const load = ({ params }) => {
|
||||
return {
|
||||
slug: params.slug
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
<script lang="ts">
|
||||
import type { PageData } from "../$types";
|
||||
import Groupbar from "../../components/groups/groupbar.svelte";
|
||||
let selectedgroup = "Lambda Media Group"
|
||||
|
||||
export let data: PageData;
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="sm:flex sm:flex-row gap-2 max-w-[1400px] m-0 m-auto">
|
||||
{#if data.groups}
|
||||
<Groupbar selectedgroup={selectedgroup} grouplist={data.groups}/>
|
||||
{/if}
|
||||
|
||||
<div class="col-span-5">
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
import type { PageLoad } from './$types';
|
||||
import { url } from "$lib/url"
|
||||
|
||||
export const load = (async ({ fetch, parent }) => {
|
||||
let data = await parent()
|
||||
|
||||
const response = await fetch(url+'/api/groups',{method: "POST",headers: {"content-type": "application/json",'Authorization': data.jwt,}});
|
||||
const groups = await response.json()
|
||||
|
||||
return {
|
||||
groups
|
||||
}
|
||||
|
||||
}) satisfies PageLoad;
|
||||
|
|
@ -0,0 +1,122 @@
|
|||
<script lang="ts">
|
||||
import { Avatar, menu } from "@skeletonlabs/skeleton";
|
||||
import type { PageData } from "../$types";
|
||||
import Groupbar from "../../../components/groups/groupbar.svelte";
|
||||
import Commentcard from "../../../components/commentcard.svelte";
|
||||
import { url } from "$lib/url"
|
||||
let selectedgroup = "Lambda Media Group"
|
||||
let currentPage = 1;
|
||||
let maxiumumPage = 1;
|
||||
let users: []
|
||||
|
||||
export let data: PageData;
|
||||
let groupid = data.group.groupid
|
||||
|
||||
async function updateMembers(){
|
||||
const response = await fetch(url+'/api/groups/'+groupid+'/members',{method: "POST", body: JSON.stringify({page: currentPage, rank: 1}),headers: {"content-type": "application/json"}});
|
||||
const data = await response.json()
|
||||
if (!data.error){
|
||||
if (users){
|
||||
users.length = 0
|
||||
users = users
|
||||
}
|
||||
users = data.data
|
||||
users = users
|
||||
maxiumumPage = data.pages
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
updateMembers()
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="sm:flex sm:flex-row gap-2 max-w-[1400px] m-0 m-auto">
|
||||
<Groupbar selectedgroup={selectedgroup} grouplist={data.groups}/>
|
||||
|
||||
<div class="col-span-5 mt-8 w-full space-y-2">
|
||||
<div class="bg-surface-700 p-4">
|
||||
|
||||
<div class="">
|
||||
<div class="flex flex-row gap-x-4">
|
||||
<Avatar width="w-36" rounded="rounded-none" src="/assets/groupicons/icon-0.png" />
|
||||
|
||||
<div>
|
||||
<h2 class="font-bold">{data.group.Name}</h2>
|
||||
<h5 class="!text-sm">By <a href="/users/{data.group.owner.userid}">{data.group.owner.username}</a></h5>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="w-full space-y-2">
|
||||
<h5 class="font-bold !text-base">Description</h5>
|
||||
<h5>{data.group.Description}</h5>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<h4 class="font-bold">Shout</h4>
|
||||
|
||||
<div class="">
|
||||
|
||||
<div class="bg-surface-700 p-4 flex flex-row gap-x-2">
|
||||
<a href="/users/0"><Avatar width="w-12" src="/api/thumbnailrender?id=0&type=headshot" /></a>
|
||||
|
||||
<div>
|
||||
<h5 class="!text-sm font-semibold">SushiWasNotHere</h5>
|
||||
<h5 class="!text-sm">Hello, world!</h5>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="bg-surface-700 p-4 flex flex-row gap-x-2">
|
||||
<textarea class="rounded-md grow input input-bordered input-primary" placeholder="Shout here!!" />
|
||||
<button class="btn mb-6 variant-filled-primary rounded-md ">Shout</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row justify-between">
|
||||
<h4 class="font-bold">Members</h4>
|
||||
|
||||
<div class="flex flex-row gap-x-2">
|
||||
<button on:click={() => {}} class="btn btn-sm bg-surface-600 rounded-md"><</button>
|
||||
<h5 class="">1 / 1</h5>
|
||||
<button on:click={() => {}} class="btn btn-sm bg-surface-600 rounded-md">></button>
|
||||
|
||||
<button class="ring-surface-500 hover:ring-surface-300 ring-2 rounded p-1 flex flex-row gap-x-3 relative">
|
||||
<h5 class="!text-sm hover:text-white">Members</h5>
|
||||
<h5 class="!text-sm hover:text-white">(1)</h5>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" class="w-4 "><line x1="12" y1="5" x2="12" y2="19"></line><polyline points="19 12 12 19 5 12"></polyline></svg>
|
||||
|
||||
</button>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="bg-surface-700 p-4 flex flex-row gap-x-2">
|
||||
{#if users}
|
||||
|
||||
{#each users as {userid, username}}
|
||||
<a class="unstyled" href="/users/{userid}"><div class="w-20">
|
||||
<Avatar width="w-20" src="/api/thumbnailrender?id={userid}&type=headshot" />
|
||||
<h5 class="!text-base truncate">{username}</h5>
|
||||
</div></a>
|
||||
{/each}
|
||||
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<h4 class="font-bold">Wall</h4>
|
||||
<Commentcard PostText="Post" PlaceholderText="Say something..."/>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
import type { PageLoad } from './$types';
|
||||
import { url } from "$lib/url"
|
||||
import { error } from '@sveltejs/kit';
|
||||
|
||||
export const load = (async ({ fetch, parent, params }) => {
|
||||
let data = await parent()
|
||||
|
||||
const response = await fetch(url+'/api/groups',{method: "POST",headers: {"content-type": "application/json",'Authorization': data.jwt,}});
|
||||
const groups = await response.json()
|
||||
|
||||
const responsegroup = await fetch(url+'/api/groups/'+params.slug,{method: "POST",headers: {"content-type": "application/json",'Authorization': data.jwt,}});
|
||||
const group = await responsegroup.json()
|
||||
|
||||
if (!group.error){
|
||||
return {
|
||||
groups,
|
||||
group: group.data
|
||||
}
|
||||
}
|
||||
throw error(404, 'Not found');
|
||||
}) satisfies PageLoad;
|
||||
|
|
@ -0,0 +1,117 @@
|
|||
<script lang="ts">
|
||||
import { goto } from "$app/navigation";
|
||||
import { FileDropzone } from "@skeletonlabs/skeleton";
|
||||
import type { PageData } from "../$types";
|
||||
let groupname:string
|
||||
let groupdescription:string
|
||||
let files: FileList;
|
||||
let publicgroup = true
|
||||
let disabled = true
|
||||
import Rocks from "../../../components/rocks.svelte";
|
||||
|
||||
export let data: PageData;
|
||||
let jwt = data.jwt
|
||||
|
||||
$:if (groupname && files && data.user.coins >= 100){
|
||||
disabled = false
|
||||
}else{
|
||||
disabled = true
|
||||
}
|
||||
|
||||
const toBase64 = (file: Blob) => new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.readAsDataURL(file);
|
||||
reader.onload = () => resolve(reader.result);
|
||||
reader.onerror = error => reject(error);
|
||||
})
|
||||
|
||||
async function create(){
|
||||
const formData = new FormData();
|
||||
formData.append("groupicon", files[0])
|
||||
formData.append("groupname", groupname)
|
||||
formData.append("description", groupdescription??"...")
|
||||
formData.append("publicgroup",publicgroup.toString())
|
||||
|
||||
|
||||
const req = await fetch("/api/groups/create", {
|
||||
method: "post",
|
||||
body: formData,
|
||||
headers: {
|
||||
'Authorization': jwt,
|
||||
},
|
||||
});
|
||||
|
||||
const res = await req.json();
|
||||
|
||||
if (!res.error){
|
||||
goto("/groups")
|
||||
}else{
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
let base64:any
|
||||
async function conv(){
|
||||
base64 = await toBase64(files[0])
|
||||
}
|
||||
$:if (files){
|
||||
conv()
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="max-w-[1200px] m-0 m-auto">
|
||||
|
||||
<div class="flex flex-col gap-y-2">
|
||||
<h2 class="font-bold">Create a Group</h2>
|
||||
|
||||
<label>
|
||||
<span class="block text-sm">Group Name</span>
|
||||
<input bind:value={groupname} maxlength={20} type="text" class="input input-bordered input-primary w-full rounded-md" placeholder="Name of group" required>
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<span class="block text-sm">Group Description</span>
|
||||
<textarea bind:value={groupdescription} maxlength={500} class="input input-bordered input-primary w-full h-64 rounded-md" placeholder="Description" required ></textarea>
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<span class="block text-sm">Icon</span>
|
||||
<input class="w-full" accept="image/png" bind:files type="file" />
|
||||
{#if base64}
|
||||
<img alt="" class="w-32 aspect-square" src={base64}/>
|
||||
{/if}
|
||||
</label>
|
||||
|
||||
<h4 class="font-bold">Settings</h4>
|
||||
|
||||
<div class="space-y-4 w-full bg-surface-700 rounded-md p-4">
|
||||
<label class="flex items-center space-x-2">
|
||||
<input bind:group={publicgroup} class="radio" type="radio" checked name="radio-setting" value={true} />
|
||||
<p>Anyone can join</p>
|
||||
</label>
|
||||
|
||||
<label class="flex items-center space-x-2">
|
||||
<input bind:group={publicgroup} class="radio" type="radio" name="radio-setting" value={false} />
|
||||
<p>Manual Approval</p>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row gap-2 justify-end">
|
||||
|
||||
<a href="/groups" class="btn variant-ringed-surface rounded-md btn-sm">Cancel</a>
|
||||
<button class="btn variant-filled-primary rounded-md btn-sm" disabled={disabled} on:click={create}>
|
||||
<Rocks width="w-4"/>
|
||||
100
|
||||
</button>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
|
@ -0,0 +1,136 @@
|
|||
<script lang="ts">
|
||||
import { Avatar } from "@skeletonlabs/skeleton";
|
||||
import Gamecard from "../../components/gamecard.svelte"
|
||||
import type { PageData } from './$types';
|
||||
let avatar: string;
|
||||
import { avatarstore } from "$lib/avatarstore"
|
||||
import Statusbubble from "../../components/statusbubble.svelte";
|
||||
import Skybanner from "../../components/skybanner.svelte";
|
||||
import { invalidate } from "$app/navigation";
|
||||
import RelativeTime from '@yaireo/relative-time'
|
||||
const relativeTime = new RelativeTime()
|
||||
//import Bannerad from "../../components/bannerad.svelte";
|
||||
avatarstore.subscribe(value => {
|
||||
avatar = value
|
||||
})
|
||||
|
||||
export let data: PageData;
|
||||
let friends = data.friendsdata
|
||||
let FeedDisabled = true
|
||||
let sharevalue:string
|
||||
let jwt = data.jwt
|
||||
$:if (!sharevalue){
|
||||
FeedDisabled = true
|
||||
}else{
|
||||
FeedDisabled = false
|
||||
}
|
||||
let feedmessage = {error: false, message: ""}
|
||||
async function share(){
|
||||
const response = await fetch('/api/feed/share', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': jwt,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
sharevalue
|
||||
})
|
||||
})
|
||||
const data = await response.json()
|
||||
if (!data.error){
|
||||
sharevalue = ""
|
||||
invalidate("/api/feed/fetch")
|
||||
feedmessage.error = false
|
||||
}else{
|
||||
feedmessage.error = true
|
||||
feedmessage.message = data.error
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<div class="space-y-4 max-w-[1500px] m-0 m-auto space-x-4">
|
||||
<Skybanner customclass="float-left mr-10"/>
|
||||
<Skybanner customclass="float-right ml-10"/>
|
||||
<div class="grow space-y-2">
|
||||
|
||||
<div class="flex flex-col space-y-2 sm:flex-row sm:space-y-14 sm:space-x-2">
|
||||
<a class="unstyled" href="/users/{data.user.userid}"><Avatar src={avatar+"&type=headshot"} width="w-40" /></a>
|
||||
{#if data.user.membership === "BuildersClub"}
|
||||
<img src="/assets/images/BC_Icon.svg" class="h-12 w-[89.5px]" alt={data.user.membership}>
|
||||
{:else if data.user.membership === "TurboBuildersClub"}
|
||||
<img src="/assets/images/TBC_Icon.png" class="h-12 w-[89.5px]" alt={data.user.membership}>
|
||||
{:else if data.user.membership === "OutrageousBuildersClub"}
|
||||
<img src="/assets/images/OBC_Icon.svg" class="h-12 w-[89.5px]" alt={data.user.membership}>
|
||||
{/if}
|
||||
<h2 class="">Hello, {data.user.username}!</h2>
|
||||
</div>
|
||||
|
||||
<h2 class="">Friends ({data.user?.friends?.length??0})</h2>
|
||||
|
||||
<div class="grid grid-flow-col grid-rows-none overflow-x-scroll overflow-y-hidden auto-cols-max bg-surface-700 rounded-md snap-mandatory snap-x h-40">
|
||||
{#if data.friendsdata}
|
||||
{#each friends as { userid, username, status }}
|
||||
<a class='unstyled' href="/users/{userid}">
|
||||
<div class="snap-center ">
|
||||
<header class="card-header relative"><Avatar width="w-24" src="/api/thumbnailrender/?id={userid}&type=headshot"/>
|
||||
<Statusbubble size="4" status={status.status} userid={userid} gameid={status.status?.id} />
|
||||
</header>
|
||||
<footer class="card-footer truncate w-32 text-center">{username}</footer>
|
||||
|
||||
</div>
|
||||
</a>
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<h2 class="">Recently Played</h2>
|
||||
|
||||
<div class="flex flex-row flex-wrap md:grid md:grid-flow-col md:grid-rows-none overflow-x-scroll overflow-y-hidden auto-cols-max gap-4 bg-surface-500/5 snap-mandatory snap-x sm:h-60">
|
||||
{#if data.recentlyplayed}
|
||||
{#each [...data.recentlyplayed].reverse() as { nameofgame, idofgame, version, visits, numberofplayers, useridofowner, owner }}
|
||||
<Gamecard gamename={nameofgame} playercount={numberofplayers} version={version} useridofowner={useridofowner} visits={visits} idofgame={idofgame} useragent={data.useragent} username={owner.username} />
|
||||
{/each}
|
||||
{/if}
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<div class="max-w-[1150px] m-0 m-auto flex flex-row gap-x-6 justify-around">
|
||||
|
||||
<div class="bg-surface-800 w-full p-4 space-y-2 h-[600px] overflow-auto">
|
||||
<h2 class="">My Feed</h2>
|
||||
<div class="flex flex-row w-full space-x-4">
|
||||
<textarea on:click={()=>{feedmessage.error = false}} class="rounded-md grow input input-bordered input-primary" maxlength={50} bind:value={sharevalue} placeholder="What are you up to?" />
|
||||
<button on:click={share} class="btn mt-6 variant-filled-primary rounded-md" disabled={FeedDisabled}>Share</button>
|
||||
</div>
|
||||
{#if feedmessage.error}
|
||||
<h5 class="!text-xs text-error-600">{feedmessage.message}</h5>
|
||||
{/if}
|
||||
<div class="w-full flex flex-col overflow-auto flex-wrap gap-y-5 pt-4">
|
||||
{#if data.feed}
|
||||
{#each [...data.feed].reverse() as {content, posterid, date, userdata}}
|
||||
<div class="flex flex-row gap-x-6">
|
||||
<a class="unstyled" href="/users/{posterid}"><Avatar src={"/api/thumbnailrender/?id="+posterid+"&type=headshot"} width="w-14" /></a>
|
||||
<div>
|
||||
<a class='truncate !text-base' href="/users/{posterid}">{userdata.username}</a>
|
||||
<h5 class="!text-base">"{content}"</h5>
|
||||
<h5 class="!text-xs">Posted {relativeTime.from(new Date(date))}</h5>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-surface-800 w-full p-4">
|
||||
<h2 class="">Blog</h2>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
import type { PageLoad } from './$types';
|
||||
import { url } from "$lib/url"
|
||||
|
||||
export const load = (async ({ fetch, parent }) => {
|
||||
let data = await parent()
|
||||
|
||||
const response = await fetch(url+'/api/auth/recentgames',{method: "POST",headers: {"content-type": "application/json",'Authorization': data.jwt,}});
|
||||
const gamedata = await response.json()
|
||||
|
||||
const responsefriends = await fetch(url+'/api/auth/requestfriends',{method: "POST",headers: {"content-type": "application/json",'Authorization': data.jwt,}});
|
||||
const friendsdata = await responsefriends.json()
|
||||
|
||||
const feedreponse = await fetch(url+'/api/feed/fetch',{method: "POST",headers: {"content-type": "application/json",'Authorization': data.jwt,}});
|
||||
const feed = await feedreponse.json()
|
||||
|
||||
return {
|
||||
recentlyplayed: gamedata,
|
||||
friendsdata,
|
||||
feed: feed.data
|
||||
}
|
||||
|
||||
}) satisfies PageLoad;
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
<h1>Privacy Policy</h1>
|
||||
<strong>Your privacy is extremely important to us at Meteorite.</strong>
|
||||
<br>
|
||||
<br>
|
||||
<p>How we store your account information when register</p>
|
||||
<div class="ps-4">
|
||||
<p>Your username</p>
|
||||
<p>Hashed password</p>
|
||||
<p>Discord Userid if linked</p>
|
||||
<p>Last time you've claimed your 24 hour stipend</p>
|
||||
<p>Last time you've visited the website</p>
|
||||
</div>
|
||||
<p><a href="https://www.cloudflare.com/privacypolicy/">Cloudflare</a> and <a href="https://www.hcaptcha.com/privacy">HCaptcha</a> are an integral part of our websites operation. As such you will be using them while on our website.</p>
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
<h1>Terms of Service</h1>
|
||||
<strong>Actually read these because it's important.</strong>
|
||||
<br>
|
||||
<br>
|
||||
<p>Having created or logged into an account. You agree to these terms.</p>
|
||||
<div class="ps-4">
|
||||
<p>You are 13 or older.</p>
|
||||
<p>Don't try to attack our services in anyway.</p>
|
||||
<p>Don't attempt to crack/attack our login apis to get into user accounts.</p>
|
||||
<p>No illegal content is allowed here.</p>
|
||||
<p>If your reading this you've probably already read the privacy policy if you haven't go read it right now.</p>
|
||||
</div>
|
||||
<p><a href="https://www.cloudflare.com/website-terms/">Cloudflare</a> and <a href="https://www.hcaptcha.com/terms">HCaptcha</a> are an integral part of our websites operation. As such you will be using them while on our website.</p>
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
import { error, redirect } from '@sveltejs/kit';
|
||||
import type { PageServerLoad } from './$types';
|
||||
|
||||
export const load = (async ({ locals }) => {
|
||||
if (!locals.user){
|
||||
throw redirect(303,'/home')
|
||||
}
|
||||
if (locals.user.moderationstatus && locals.user.moderationstatus?.status.toUpperCase() != "OK"){
|
||||
return
|
||||
}
|
||||
throw redirect(303,'/home')
|
||||
}) satisfies PageServerLoad;
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
<script lang="ts">
|
||||
import type { PageData } from "./$types";
|
||||
|
||||
|
||||
|
||||
export let data: PageData;
|
||||
|
||||
</script>
|
||||
|
||||
<div class="bg-surface-700 max-w-[800px] p-6 border-2 border-primary-500 m-0 m-auto">
|
||||
|
||||
<h2>{data.user.moderationstatus.status}</h2>
|
||||
|
||||
<div class="p-4 space-y-4">
|
||||
|
||||
<h5 class="text-base">Our content monitors have determined your behavior on Meteorite is in violation of our Terms of Service.</h5>
|
||||
|
||||
<h5 class="text-base flex flex-row gap-2">Moderator Note: <h5 class="text-base font-bold">{data.user.moderationstatus.Reason}</h5></h5>
|
||||
{#if data.user.moderationstatus.ExpiresIn === "2100-01-01"}
|
||||
<h5 class="text-base">You may reactivate after never.</h5>
|
||||
{:else if data.user.moderationstatus.ExpiresIn === "2000-01-01"}
|
||||
<h5 class="text-base">This expires after you refresh.</h5>
|
||||
{:else}
|
||||
<h5 class="text-base">You may reactivate after {data.user.moderationstatus.ExpiresIn}.</h5>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
|
@ -0,0 +1,318 @@
|
|||
<script lang="ts">
|
||||
import type { PageData } from './$types';
|
||||
import { RadioGroup, RadioItem } from '@skeletonlabs/skeleton';
|
||||
import { writable, type Writable } from 'svelte/store';
|
||||
import { Modal, modalStore } from '@skeletonlabs/skeleton';
|
||||
import type { ModalSettings, ModalComponent } from '@skeletonlabs/skeleton';
|
||||
import changePasswordModal from "../../components/settingsmodals/changepassword.svelte"
|
||||
import _2faModal from "../../components/settingsmodals/_2fa.svelte"
|
||||
import { SlideToggle } from '@skeletonlabs/skeleton';
|
||||
import { page } from '$app/stores'
|
||||
import { coinstore } from "$lib/coinstore"
|
||||
import { toastStore } from '@skeletonlabs/skeleton';
|
||||
import type { ToastSettings } from '@skeletonlabs/skeleton';
|
||||
let error = $page.url.searchParams.get('error')
|
||||
|
||||
let settingfilter: string;
|
||||
settingfilter = "info"
|
||||
let _2faenabled = false
|
||||
let bio: string
|
||||
let customcss: string
|
||||
let message = {error: false, message: ""}
|
||||
|
||||
export let data: PageData;
|
||||
|
||||
customcss = data.user?.css??""
|
||||
|
||||
const jwt = data.jwt
|
||||
|
||||
const t: ToastSettings = {
|
||||
message: 'resp',
|
||||
// Optional: Presets for primary | secondary | tertiary | warning
|
||||
preset: 'primary',
|
||||
// Optional: The auto-hide settings
|
||||
autohide: true,
|
||||
timeout: 2000
|
||||
};
|
||||
|
||||
if(data.user._2faenabled === true){
|
||||
_2faenabled = true
|
||||
}
|
||||
|
||||
async function passwordchange(){
|
||||
const modalComponent: ModalComponent = {
|
||||
// Pass a reference to your custom component
|
||||
ref: changePasswordModal,
|
||||
props: { jwt: data.jwt},
|
||||
};
|
||||
const d: ModalSettings = {
|
||||
type: 'component',
|
||||
// NOTE: title, body, response, etc are supported!
|
||||
component: modalComponent
|
||||
};
|
||||
modalStore.trigger(d);
|
||||
}
|
||||
async function attempt2fa(){
|
||||
const _2faresp = await fetch("/settings/2fa",{headers: {
|
||||
'Authorization': data.jwt,
|
||||
}})
|
||||
const _2fajson = await _2faresp.json()
|
||||
if (!_2fajson.error){
|
||||
console.log(_2fajson)
|
||||
const modalComponent: ModalComponent = {
|
||||
// Pass a reference to your custom component
|
||||
ref: _2faModal,
|
||||
props: { jwt: data.jwt, qrcode: _2fajson.qrcode},
|
||||
};
|
||||
const d: ModalSettings = {
|
||||
type: 'component',
|
||||
// NOTE: title, body, response, etc are supported!
|
||||
component: modalComponent
|
||||
};
|
||||
modalStore.trigger(d);
|
||||
|
||||
}
|
||||
}
|
||||
$:if (_2faenabled === false && data.user._2faenabled === true){
|
||||
// cant djisable 2fa mfs LOL
|
||||
_2faenabled = true
|
||||
}
|
||||
$:if (_2faenabled === true && data.user._2faenabled === false){
|
||||
// if they are trying to enable 2fa for the first time
|
||||
_2faenabled = false
|
||||
attempt2fa()
|
||||
|
||||
}
|
||||
|
||||
async function biochange(){
|
||||
const biochangeresp = await fetch("/settings/setbio", {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': jwt,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
bio:bio
|
||||
})
|
||||
})
|
||||
const biochangejson = await biochangeresp.json()
|
||||
if (biochangejson.error){
|
||||
message.message = biochangejson.error
|
||||
message.error = true
|
||||
}else{
|
||||
message.message = biochangejson.message
|
||||
message.error = false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
async function csschange(){
|
||||
const csschangeresponse = await fetch("/settings/changecss", {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': jwt,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
customcss
|
||||
})
|
||||
})
|
||||
const csschange = await csschangeresponse.json()
|
||||
|
||||
if (csschange.error){
|
||||
message.message = csschange.error
|
||||
message.error = true
|
||||
}else{
|
||||
message.message = csschange.message
|
||||
message.error = false
|
||||
}
|
||||
}
|
||||
|
||||
async function buyclassic(){
|
||||
const buyresp = await fetch("/api/updateusermembership/buymembership", {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': jwt,
|
||||
}
|
||||
})
|
||||
const buyjson = await buyresp.json()
|
||||
|
||||
t.message = buyjson.error??buyjson.message
|
||||
|
||||
if (buyjson.error){
|
||||
t.preset = "error"
|
||||
}else{
|
||||
t.preset = "primary"
|
||||
coinstore.update(n => n - 200)
|
||||
}
|
||||
toastStore.trigger(t)
|
||||
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<div class="bg-surface-700 p-4 flex flex-row flex-wrap space-x-2 space-y-4 relative max-w-[970px] m-0 m-auto">
|
||||
|
||||
<h2 class="font-bold w-full">My Settings</h2>
|
||||
|
||||
<div class="flex flex-row sm:flex-col sm:space-y-4">
|
||||
<button on:click={() => {settingfilter="info"}} class="btn rounded-none btn-base {settingfilter === 'info' ? 'border-l-2' : ''}">
|
||||
Account Info
|
||||
</button>
|
||||
<button on:click={() => {settingfilter="security"}} class="btn rounded-none btn-base {settingfilter === 'security' ? 'border-l-2' : ''}">
|
||||
Security
|
||||
</button>
|
||||
<button on:click={() => {settingfilter="bc"}} class="btn rounded-none btn-base {settingfilter === 'bc' ? 'border-l-2' : ''}">
|
||||
Builders Club
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{#if settingfilter === "info"}
|
||||
<div class="grow">
|
||||
|
||||
<h4>Account Info</h4>
|
||||
|
||||
<div class="flex flex-row justify-between">
|
||||
<div class="flex flex-row space-x-2 grow">
|
||||
<h5 class="text-tertiary-600 text-base">Username: </h5>
|
||||
<h5 class="text-base">{data.user.username}</h5>
|
||||
</div>
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-edit shrink"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"></path></svg>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row justify-between">
|
||||
<div class="flex flex-row space-x-2 grow">
|
||||
<h5 class="text-tertiary-600 text-base">Password: </h5>
|
||||
<h5 class="text-base">*******</h5>
|
||||
</div>
|
||||
|
||||
<svg on:click={passwordchange} xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-edit shrink"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"></path></svg>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row justify-between">
|
||||
<div class="flex flex-row space-x-2 grow">
|
||||
<h5 class="text-tertiary-600 text-base">Discord: </h5>
|
||||
{#if error === "alreadyused"}
|
||||
<h5 class="text-base">{"Error: This discord account has already been linked!"}</h5>
|
||||
{:else if error === "toonew"}
|
||||
<h5 class="text-base">{"Error: This discord account is too new! (4weeks)"}</h5>
|
||||
{/if}
|
||||
<h5 class="text-base">{data.user?.discordid??"Not Linked"}</h5>
|
||||
</div>
|
||||
{#if !data.user.discordid}
|
||||
<a class="unstyled" href="https://discord.com/api/oauth2/authorize?client_id=1008206768989544449&redirect_uri=http%3A%2F%2Fmete0r.xyz%2Fsettings%2Fauthenticate&response_type=code&scope=identify"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-edit shrink"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"></path></svg></a>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="w-full flex flex-row gap-2">
|
||||
<h5 class="text-tertiary-600 text-base">Bio: </h5>
|
||||
<input bind:value={bio} type="text" placeholder={data.user?.bio??"Bio"} maxlength="100" class="input input-bordered input-primary rounded-md grow" required>
|
||||
|
||||
<button on:click={biochange} class="btn variant-filled-primary rounded-md h-12 btn-sm">
|
||||
Change
|
||||
</button>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="w-full flex flex-row gap-2">
|
||||
<h5 class="text-tertiary-600 text-base w-16">Custom Profile CSS: </h5>
|
||||
<textarea bind:value={customcss} maxlength={5000} placeholder={"Custom CSS"} class="input h-64 input-bordered input-primary rounded-md grow" required></textarea>
|
||||
|
||||
<button on:click={csschange} class="btn variant-filled-primary rounded-md h-12 btn-sm">
|
||||
Apply
|
||||
</button>
|
||||
|
||||
</div>
|
||||
<h5 class="text-tertiary-600 text-base">If you wish to use Images. Prepend your image url with https://wsrv.nl/?url=</h5>
|
||||
|
||||
<h5 class="!text-xs mt-6 {message.error === true ? 'text-error-600' : 'text-success-600'}">{message.message??""}</h5>
|
||||
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if settingfilter === "security"}
|
||||
|
||||
<div class="grow">
|
||||
|
||||
<h4>2 Step Verification</h4>
|
||||
|
||||
<div class="flex flex-row justify-between">
|
||||
<div class="flex flex-row space-x-2 grow">
|
||||
<h5 class="text-tertiary-600 text-base">Authenticator App (Very Secure)</h5>
|
||||
</div>
|
||||
|
||||
<SlideToggle active="bg-success-600" bind:checked={_2faenabled}></SlideToggle>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
{/if}
|
||||
|
||||
{#if settingfilter === "bc"}
|
||||
|
||||
<div class="grow space-y-2">
|
||||
<h4>Builders Club</h4>
|
||||
|
||||
<div class="flex flex-row justify-around">
|
||||
|
||||
<div class="flex flex-col space-y-4 w-24">
|
||||
<h3 class="text-center">Classic</h3>
|
||||
<img alt="Classic" src="/assets/images/buildersclub/classic.png"/>
|
||||
<button on:click={buyclassic} class="btn variant-filled-primary rounded-md h-12 btn-sm">
|
||||
200 Rocks
|
||||
</button>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col space-y-4 w-24">
|
||||
<h3 class="text-center">Turbo</h3>
|
||||
<img alt="Classic" src="/assets/images/buildersclub/turbo.png"/>
|
||||
<a href="https://discord.gg/5r6ZjG57kU" class="btn variant-filled-primary rounded-md h-12 btn-sm">
|
||||
Boost
|
||||
</a>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col space-y-4 w-24">
|
||||
<h3 class="text-center">Outrageous</h3>
|
||||
<img alt="Classic" src="/assets/images/buildersclub/outrageous.png"/>
|
||||
<a href="https://ko-fi.com/meteoriterevival" class="btn variant-filled-primary rounded-md h-12 btn-sm">
|
||||
Donate
|
||||
</a>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
{/if}
|
||||
|
||||
</div>
|
||||
|
||||
{#if settingfilter === "bc"}
|
||||
<div class="bg-surface-700 p-4 flex flex-col gap-y-2 relative max-w-[970px] m-0 m-auto sm:pl-40">
|
||||
<h4 class="font-bold">How does Builders Club work?</h4>
|
||||
|
||||
<h5 class="!text-sm text-blue-300">Classic - Obtain with 200 rocks a lifetime purchase.</h5>
|
||||
|
||||
<div class="list-disc pl-4">
|
||||
<li class="!text-sm">Increases your daily stipend to 60 rocks.</li>
|
||||
</div>
|
||||
|
||||
<h5 class="!text-sm text-orange-300">Turbo - Obtain by boosting the discord server.</h5>
|
||||
|
||||
<div class="list-disc pl-4">
|
||||
<li class="!text-sm">Increases your daily stipend to 90 rocks.</li>
|
||||
</div>
|
||||
|
||||
<h5 class="!text-sm text-red-400">Outrageous - Obtain by donating <a href="https://ko-fi.com/meteoriterevival">here.</a></h5>
|
||||
|
||||
<div class="list-disc pl-4">
|
||||
<li class="!text-sm">Increases your daily stipend to 150 rocks.</li>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{/if}
|
||||
|
|
@ -0,0 +1,311 @@
|
|||
<script lang="ts">
|
||||
import { Avatar, Tab, TabGroup } from "@skeletonlabs/skeleton";
|
||||
import type { PageData } from './$types';
|
||||
import Statusbubble from "../../../components/statusbubble.svelte";
|
||||
import Gamecard from "../../../components/gamecard.svelte";
|
||||
import { invalidate } from "$app/navigation";
|
||||
|
||||
|
||||
export let data: PageData;
|
||||
let jwt = data.jwt
|
||||
|
||||
let gameid = data.profile.status.id
|
||||
let gamename = ""
|
||||
let canfriend = true
|
||||
let friendbutton = "Add Friend"
|
||||
let friends = data.profile?.friends
|
||||
let storeTab = 'About'
|
||||
let creations: any[] = []
|
||||
let editAbout = false
|
||||
let about:string
|
||||
//$:console.log(friends)
|
||||
async function saveabout(){
|
||||
const result = await fetch('/settings/aboutme', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': jwt,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
about
|
||||
})
|
||||
})
|
||||
const aboutresult = await result.json()
|
||||
if (!aboutresult.error){
|
||||
editAbout = false
|
||||
data.profile.aboutme = about
|
||||
}
|
||||
}
|
||||
|
||||
$:{
|
||||
if (friends){
|
||||
friends = data.profile?.friends
|
||||
friends = friends.sort((x: { offline: any; }) => !x.offline ? -1 : 1)
|
||||
}
|
||||
}
|
||||
|
||||
async function requestGames(){
|
||||
const res = await fetch(`/api/userinfo/${data.profile.userid}/creations`)
|
||||
creations = await res.json()
|
||||
console.log(creations)
|
||||
}
|
||||
|
||||
$:if (storeTab === "Games" && creations.length === 0){
|
||||
requestGames()
|
||||
}
|
||||
|
||||
async function requestGameName(){
|
||||
const res = await fetch(`/games/gameinfo/${gameid}`)
|
||||
const data = await res.json()
|
||||
if (data.error === false){
|
||||
gamename = data.gameinfo.nameofgame
|
||||
}
|
||||
}
|
||||
$:if (data.profile.status.id){
|
||||
gameid = data.profile.status.id
|
||||
requestGameName()
|
||||
}
|
||||
let equippedcount = data.profile?.inventory?.filter((c:any) => c.Equipped === true && !c.Hidden)?.length??0
|
||||
|
||||
if (data.alreadyFriends === true){
|
||||
friendbutton = "Sent!"
|
||||
canfriend = false
|
||||
}
|
||||
if (data.otherUserWantsToBeFriends === true){
|
||||
friendbutton = "Accept"
|
||||
}
|
||||
|
||||
async function requestFriend(){
|
||||
const result = await fetch('/api/friends/request-friendship', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': jwt,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
recipientUserId: data.profile.userid.toString()
|
||||
})
|
||||
})
|
||||
const requestresult = await result.json()
|
||||
console.log(requestresult)
|
||||
if (!requestresult.error){
|
||||
if (requestresult?.message === "Friend request sent!"){
|
||||
friendbutton = "Sent!"
|
||||
canfriend = false
|
||||
}
|
||||
if (requestresult?.message === "You are now friends :D"){
|
||||
friendbutton = "Friends"
|
||||
canfriend = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (data.profile.friends){
|
||||
if (data.profile.friends.some((userid: { userid: any; }) => userid.userid == data.user.userid) === true){
|
||||
// already friends
|
||||
canfriend = false
|
||||
friendbutton = "Friends"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Meteorite - {data.profile.username}</title>
|
||||
{#if data.profile.css}
|
||||
{@html `<`+`style>${data.profile.css}</style>`}
|
||||
{/if}
|
||||
</svelte:head>
|
||||
<div class="max-w-[970px] m-0 m-auto ">
|
||||
{#if data.profile.userid === 18}
|
||||
<audio id="my-audio" loop autoplay>
|
||||
<source src="https://cdn.mete0r.xyz/file/meteorite/bittersweet.m4a" type="audio/x-m4a">
|
||||
</audio>
|
||||
<script type="text/javascript">
|
||||
|
||||
var isAudioPlayed = false;
|
||||
|
||||
|
||||
|
||||
function playAudio() {
|
||||
|
||||
isAudioPlayed = true;
|
||||
const myAudio = document.getElementById("my-audio");
|
||||
myAudio.play();
|
||||
}
|
||||
|
||||
|
||||
document.body.onclick = ()=>{
|
||||
if(isAudioPlayed) return ;
|
||||
playAudio();
|
||||
}
|
||||
</script>
|
||||
{/if}
|
||||
|
||||
<div>
|
||||
|
||||
<div class="bg-surface-700 p-4 flex flex-row flex-wrap space-x-2 relative justify-between">
|
||||
<div class="m-auto relative">
|
||||
<Avatar width="block md:hidden w-28 m-auto" src="/api/thumbnailrender/?id={data.profile.userid+"&type=headshot&a="+Date.now()}"/>
|
||||
<Statusbubble customclass="block md:hidden" size="5" status={data.profile.status?.status} gameid={data.profile.status?.id}/>
|
||||
</div>
|
||||
<div class="relative">
|
||||
<Avatar width="hidden md:block w-32" src="/api/thumbnailrender/?id={data.profile.userid+"&type=headshot&a="+Date.now()}"/>
|
||||
<Statusbubble customclass="hidden md:block" size="5" status={data.profile.status?.status} gameid={data.profile.status?.id}/>
|
||||
</div>
|
||||
<div class="text-center sm:text-left grow">
|
||||
<div class='flex flex-row'>
|
||||
<h2>{data.profile.username}</h2>
|
||||
|
||||
{#if data.profile.membership === "BuildersClub"}
|
||||
<img src="/assets/images/BC_Icon.svg" class="h-12 w-[89.5px]" alt={data.user.membership}>
|
||||
{:else if data.profile.membership === "TurboBuildersClub"}
|
||||
<img src="/assets/images/TBC_Icon.png" class="h-12 w-[89.5px]" alt={data.user.membership}>
|
||||
{:else if data.profile.membership === "OutrageousBuildersClub"}
|
||||
<img src="/assets/images/OBC_Icon.svg" class="h-12 w-[89.5px]" alt={data.user.membership}>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<h5 class="!text-xs truncate">"{data.profile.bio??"Set your bio in settings!"}"</h5>
|
||||
{#if data.profile.status.status.includes('Playing') === true}
|
||||
<a class="unstyled" href="/games/{gameid}"><h5 class="!text-base truncate text-green-500 hover:underline">Playing {gamename}</h5></a>
|
||||
{/if}
|
||||
|
||||
<div class="flex flex-row text-center pt-2">
|
||||
|
||||
<div class="flex flex-row space-x-8 grow">
|
||||
<div class="truncate">
|
||||
<h5>Friends</h5>
|
||||
<h5>{data.profile?.friends?.length??0}</h5>
|
||||
</div>
|
||||
<div class="truncate">
|
||||
<h5>Followers</h5>
|
||||
<h5>{data.profile?.followers??0}</h5>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{#if data.user.userid != data.profile.userid}
|
||||
<button on:click={requestFriend} class="btn variant-filled-primary rounded-md h-12 btn-sm" disabled={!canfriend}>
|
||||
{friendbutton}
|
||||
</button>
|
||||
{:else}
|
||||
<button class="btn variant-filled-primary rounded-md h-12 btn-sm" disabled>
|
||||
Add Friend
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="bg-surface-700 mt-4 flex flex-row justify-evenly relative w-full">
|
||||
<TabGroup hover="" class="w-full">
|
||||
<Tab bind:group={storeTab} flex="grow" value="About">About</Tab>
|
||||
<Tab bind:group={storeTab} flex="grow" value="Games">Games</Tab>
|
||||
</TabGroup>
|
||||
|
||||
</div>
|
||||
{#if storeTab === "About"}
|
||||
<div class="flex flex-row pt-4 gap-x-1">
|
||||
<h3 class="">About</h3>
|
||||
{#if data.user.userid === data.profile.userid}
|
||||
<svg on:click={() => {editAbout = true,about=data.profile.aboutme}} on:keydown xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="mt-2 hover:stroke-white"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"></path></svg>
|
||||
{/if}
|
||||
</div>
|
||||
{#if editAbout}
|
||||
<textarea class="rounded-md grow input input-bordered input-primary" bind:value={about} placeholder="Tell the Meteorite community what you think!" maxlength={200} />
|
||||
|
||||
<div class="flex flex-row justify-end gap-x-2">
|
||||
<button on:click={() => (editAbout = false, about="")} class="btn btn-sm variant-filled-primary rounded-md ">Cancel</button>
|
||||
<button on:click={saveabout} class="btn btn-sm variant-filled-primary rounded-md ">Save</button>
|
||||
</div>
|
||||
|
||||
{/if}
|
||||
<h5 class="break-words max-h-24 overflow-auto whitespace-pre-line !text-base {editAbout === true ? 'hidden': ''}">{data.profile.aboutme??""}</h5>
|
||||
|
||||
<h3 class="pt-4">Currently Wearing</h3>
|
||||
|
||||
<div class="bg-surface-700 grid grid-cols-2 space-x-2 relative">
|
||||
<Avatar width="w-[300px] block md:hidden col-span-2 justify-self-center" background="" alt={data.profile.username} src="/api/thumbnailrender/?id={data.profile.userid+"&a="+Date.now()}"/>
|
||||
|
||||
<Avatar width="w-[300px] hidden md:block justify-self-center" background="" alt={data.profile.username} src="/api/thumbnailrender/?id={data.profile.userid+"&a="+Date.now()}"/>
|
||||
<!-- if a user has less than 8 items in there inventory we space it differently -->
|
||||
<div class="bg-primary-700 max-h-[300px] col-span-2 md:col-span-1 px-2 py-2 md:p-8 gap-2 {equippedcount <= 8 ? 'md:gap-y-10' : ''} overflow-x-scroll md:overflow-x-hidden md:overflow-y-scroll grid md:grid-cols-4 grid-flow-col md:grid-rows-none md:grid-flow-row auto-rows-max auto-cols-max">
|
||||
{#if data.profile.inventory}
|
||||
{#each data.profile.inventory as {Type, ItemId, ItemName,Equipped,Hidden}}
|
||||
{#if Equipped === true && !Hidden}
|
||||
<a href="/catalog/{ItemId}/{ItemName.replace(/[^a-zA-Z ]/g, "").replaceAll(' ', '-')}"><img class="bg-surface-800 p-2 rounded-md w-28" alt={ItemName} src='/api/thumbnailrender/asset/?id={ItemId}'/></a>
|
||||
{/if}
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<h3 class="pt-4">Friends</h3>
|
||||
|
||||
<div class="grid grid-flow-col grid-rows-none overflow-x-scroll overflow-y-hidden auto-cols-max bg-surface-700 rounded-md snap-mandatory snap-x h-40">
|
||||
{#if data.profile.friends}
|
||||
{#each friends as { userid, username },i}
|
||||
<a class='unstyled' href="/users/{userid}">
|
||||
<div class="snap-center ">
|
||||
<header class="card-header relative"><Avatar width="w-24" src="/api/thumbnailrender/?id={userid}&type=headshot"/>
|
||||
</header>
|
||||
<footer class="card-footer truncate w-32 text-center">{username}</footer>
|
||||
|
||||
</div>
|
||||
</a>
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<h3 class="pt-4">Badges</h3>
|
||||
|
||||
<div class="bg-surface-700 p-4 grid grid-flow-col auto-cols-max overflow-x-scroll h-40 sm:h-60">
|
||||
{#if data.profile.admin === true}
|
||||
<div class="card rounded-md card-glass-surface snap-center card-hover group w-20 sm:w-40 relative">
|
||||
<Avatar width="" rounded="rounded-none" src="/assets/images/logosmall.png" />
|
||||
<p class="truncate w-auto">Administrator</p>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<h3 class="pt-4">Statistics</h3>
|
||||
|
||||
<div class="bg-surface-700 p-4 flex flex-row justify-evenly h-20 text-center">
|
||||
|
||||
<div class="">
|
||||
<p class="font-bold">Join Date</p>
|
||||
<p>{data.profile.joindate}</p>
|
||||
</div>
|
||||
|
||||
<div class="">
|
||||
<p class="font-bold">Last Online</p>
|
||||
<p>{data.profile.lastonline}</p>
|
||||
</div>
|
||||
|
||||
<div class="">
|
||||
<p class="font-bold">Place Visits</p>
|
||||
<p>{data.visits}</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if storeTab === "Games"}
|
||||
|
||||
<h3 class="pt-4">Games</h3>
|
||||
|
||||
<div class="flex flex-row flex-wrap gap-2 pt-4">
|
||||
{#each creations as {nameofgame, idofgame, version, visits, numberofplayers, useridofowner}}
|
||||
<Gamecard gamename={nameofgame} playercount={numberofplayers} version={version} useridofowner={useridofowner} visits={visits} idofgame={idofgame} useragent={data.useragent} />
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
</div>
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
import { error } from '@sveltejs/kit';
|
||||
import type { PageLoad } from './$types';
|
||||
|
||||
export const load = (async ({ fetch, params, parent }) => {
|
||||
let data = await parent()
|
||||
let alreadyFriends = false
|
||||
let otherUserWantsToBeFriends = false
|
||||
const res = await fetch(`http://mete0r.xyz/api/userinfo/${params.slug}`)
|
||||
const datauser = await res.json()
|
||||
|
||||
const resvisits = await fetch(`http://mete0r.xyz/api/userinfo/${params.slug}/visits`)
|
||||
const datavisits = await resvisits.json()
|
||||
|
||||
|
||||
|
||||
|
||||
const result = await fetch('http://mete0r.xyz/api/friends/has-sent-request', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': data.jwt,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
recipientUserId: datauser.userinfo.userid.toString()
|
||||
})
|
||||
})
|
||||
const requestresult = await result.json()
|
||||
if (!requestresult.error){
|
||||
if (requestresult?.message === true){
|
||||
alreadyFriends = true
|
||||
}
|
||||
if (requestresult?.message === "Other user wants to be friends."){
|
||||
otherUserWantsToBeFriends = true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
if (datauser.error === false){
|
||||
return {
|
||||
profile: datauser.userinfo,
|
||||
alreadyFriends,
|
||||
otherUserWantsToBeFriends,
|
||||
visits: datavisits.visits
|
||||
}
|
||||
}
|
||||
throw error(404, 'Not found');
|
||||
}) satisfies PageLoad;
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
<script lang="ts">
|
||||
import Usercard from '../../../../components/usercard.svelte';
|
||||
import type { PageData } from './$types';
|
||||
import { url } from "$lib/url"
|
||||
let currentPage = 1;
|
||||
let maxiumumPage = 1;
|
||||
|
||||
export let data: PageData;
|
||||
let search = data.slug
|
||||
$:{
|
||||
search = data.slug
|
||||
currentPage = 1
|
||||
updateUsers()
|
||||
}
|
||||
|
||||
let users: [];
|
||||
async function updateUsers(){
|
||||
|
||||
const response = await fetch(url+'/api/users/search',{method: "POST", body: JSON.stringify({searchquery: search,page: currentPage}),headers: {"content-type": "application/json"}});
|
||||
const data = await response.json()
|
||||
if (!data.error){
|
||||
if (users){
|
||||
users.length = 0
|
||||
users = users
|
||||
}
|
||||
users = data.data
|
||||
users = users
|
||||
maxiumumPage = data.pages
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
function setPage(value: number){
|
||||
if (currentPage-value >= 1 && currentPage-value <= maxiumumPage){
|
||||
currentPage -= value
|
||||
updateUsers()
|
||||
}
|
||||
}
|
||||
updateUsers()
|
||||
</script>
|
||||
|
||||
<div class="space-y-4 max-w-[1200px] m-0 m-auto">
|
||||
<h2 class="">Results for {search}: </h2>
|
||||
<div class="flex flex-col flex-wrap md:grid md:grid-cols-3 md:grid-rows-4 gap-2">
|
||||
{#if users}
|
||||
{#each users as {userid, username}}
|
||||
<Usercard userid={userid} username={username}/>
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
<div class="flex flex-row space-x-2 justify-center">
|
||||
<button on:click={() => {setPage(1)}} class="btn btn-sm bg-surface-600 rounded-md"><</button>
|
||||
<h5 class="">{currentPage} / {maxiumumPage}</h5>
|
||||
<button on:click={() => {setPage(-1)}} class="btn btn-sm bg-surface-600 rounded-md">></button>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
export const load = ({ params }) => {
|
||||
return {
|
||||
slug: params.slug
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 24.08"><defs><style>.cls-1{fill:#00a2ff;}</style></defs><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><g id="BC"><g id="BC-2" data-name="BC"><path class="cls-1" d="M30,8.58h2.1c1.3,0,2.3.39,2.3,1.6a1.37,1.37,0,0,1-1.1,1.4h0c.9.19,1.5.69,1.5,1.5,0,1.29-1.1,1.89-2.5,1.89H30Zm2,2.69c.9,0,1.3-.3,1.3-1s-.4-.8-1.3-.8h-.8v1.8Zm.1,2.91c1,0,1.5-.4,1.5-1.1s-.5-1-1.5-1h-1v2.1Z"/><path class="cls-1" d="M38.6,8.38a3,3,0,0,1,1.9.8l-.6.7a1.66,1.66,0,0,0-1.2-.5c-1.1,0-1.8.89-1.8,2.4s.7,2.4,1.8,2.4a1.82,1.82,0,0,0,1.4-.6l.6.7a2.52,2.52,0,0,1-2,.9c-1.7,0-3-1.2-3-3.4S36.9,8.38,38.6,8.38Z"/><path class="cls-1" d="M47,20.08H25a1,1,0,0,1,0-2H46v-12H25a1,1,0,0,1-1-1,.94.94,0,0,1,1-1H47a.94.94,0,0,1,1,1v14A.94.94,0,0,1,47,20.08Z"/><path class="cls-1" d="M17,4.08a.76.76,0,0,1-.4-.1L12,2.17,7.4,4a1,1,0,0,1-1.3-.6.93.93,0,0,1,.6-1.3l5-2a.78.78,0,0,1,.7,0l5,2a1,1,0,0,1-.4,2Z"/><path class="cls-1" d="M23.4,12.18,22,11.47V5.08a.89.89,0,0,0-.3-.7l-1-1a1,1,0,0,0-1.4,1.4l.7.7v6.6a1,1,0,0,0,.6.89l1.4.7v.41H2v-.4L3.4,13a1,1,0,0,0,.6-.9V5.48l.7-.7a1,1,0,0,0-1.4-1.4l-1,1a.89.89,0,0,0-.3.7v6.39l-1.4.7a1,1,0,0,0-.6.91v2a.94.94,0,0,0,1,1H2.1c.5,4.7,4.5,8,9.9,8s9.4-3.3,9.9-8H23a.94.94,0,0,0,1-1v-2A1,1,0,0,0,23.4,12.18ZM12,22.08c-4.3,0-7.4-2.4-7.9-6H19.9C19.4,19.68,16.3,22.08,12,22.08Z"/></g></g></g></g></svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 23.97"><defs><style>.cls-1{fill:#f68802;}.cls-2{fill:#191919;}</style></defs><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><g id="BC"><g id="OBC"><path class="cls-1" d="M26.3,11.67c0-2.1,1.2-3.4,2.9-3.4s2.9,1.2,2.9,3.4-1.2,3.4-2.9,3.4S26.3,13.77,26.3,11.67Zm4.6,0c0-1.5-.7-2.3-1.7-2.3s-1.7.9-1.7,2.3.7,2.4,1.7,2.4S30.9,13.17,30.9,11.67Z"/><path class="cls-1" d="M33.3,8.47h2.1c1.3,0,2.3.4,2.3,1.6a1.37,1.37,0,0,1-1.1,1.4h0c.9.2,1.5.7,1.5,1.5,0,1.3-1.1,1.9-2.5,1.9H33.3Zm2,2.7c.9,0,1.3-.3,1.3-1s-.4-.8-1.3-.8h-.8v1.8Zm.2,2.9c1,0,1.5-.4,1.5-1.1s-.5-1-1.5-1h-1v2.1Z"/><path class="cls-1" d="M41.9,8.27a3,3,0,0,1,1.9.81l-.6.69a1.66,1.66,0,0,0-1.2-.5c-1.1,0-1.8.91-1.8,2.41s.7,2.4,1.8,2.4a1.82,1.82,0,0,0,1.4-.6l.6.7a2.55,2.55,0,0,1-2,.9c-1.7,0-3-1.2-3-3.4A3,3,0,0,1,41.9,8.27Z"/><path class="cls-2" d="M47,20H25a1,1,0,0,1,0-2H46V6H25a.94.94,0,0,1-1-1,.94.94,0,0,1,1-1H47a.94.94,0,0,1,1,1V19A.94.94,0,0,1,47,20Z"/><path class="cls-1" d="M17,4a.76.76,0,0,1-.4-.1L12,2.07,7.4,4a1,1,0,0,1-1.3-.6.93.93,0,0,1,.6-1.3l5-2a.94.94,0,0,1,.7,0l5,2a1,1,0,0,1,.6,1.3A1.1,1.1,0,0,1,17,4Z"/><path class="cls-1" d="M17,8a.76.76,0,0,1-.4-.1L12,6.07,7.4,8a1,1,0,0,1-1.3-.6.93.93,0,0,1,.6-1.3l5-2a.85.85,0,0,1,.7,0l5,2a1,1,0,0,1,.6,1.3A1.1,1.1,0,0,1,17,8Z"/><path class="cls-1" d="M17,12a.76.76,0,0,1-.4-.1L12,10.07,7.4,12a1,1,0,0,1-1.3-.6.93.93,0,0,1,.6-1.3l5-2a.85.85,0,0,1,.7,0l5,2a1,1,0,0,1,.6,1.3A1.1,1.1,0,0,1,17,12Z"/><path class="cls-2" d="M23.4,12.07l-1.4-.7V5a.91.91,0,0,0-.3-.7l-1-1a1,1,0,1,0-1.4,1.4l.7.7V12a1,1,0,0,0,.6.9l1.4.7V14H2v-.4l1.4-.7A1,1,0,0,0,4,12V5.37l.7-.7a1,1,0,1,0-1.4-1.4l-1,1A.91.91,0,0,0,2,5v6.4l-1.4.7A1,1,0,0,0,0,13v2a.94.94,0,0,0,1,1H2.1c.5,4.7,4.5,8,9.9,8s9.4-3.3,9.9-8H23a.94.94,0,0,0,1-1V13A1,1,0,0,0,23.4,12.07ZM12,22c-4.3,0-7.4-2.4-7.9-6H19.9C19.4,19.57,16.3,22,12,22Z"/></g></g></g></g></svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 114 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 69 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 148 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 195 KiB |