This commit is contained in:
SushiDesigner 2023-09-04 21:29:09 -06:00
commit 2c80cbbfb9
112 changed files with 9823 additions and 0 deletions

52
.github/workflows/svelte.js.yml vendored Normal file
View File

@ -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

10
.gitignore vendored Normal file
View File

@ -0,0 +1,10 @@
.DS_Store
node_modules
/build
/.svelte-kit
/package
.env
.env.*
!.env.example
vite.config.js.timestamp-*
vite.config.ts.timestamp-*

1
.npmrc Normal file
View File

@ -0,0 +1 @@
engine-strict=true

39
README.md Normal file
View File

@ -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.

4066
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

37
package.json Normal file
View File

@ -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"
}
}

6
postcss.config.cjs Normal file
View File

@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

15
src/app.d.ts vendored Normal file
View File

@ -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 {}
}

12
src/app.html Normal file
View File

@ -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>

2
src/app.postcss Normal file
View File

@ -0,0 +1,2 @@
/*place global styles here */
html, body { @apply h-full overflow-hidden; }

View File

@ -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">&lt;</button>
<h5 class="">{currentPage} / {maxiumumPage}</h5>
<button on:click={() => {setPage(-1)}} class="btn btn-sm bg-surface-600 rounded-md">&gt;</button>
</div>
</div>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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">&lt;</button>
<h5 class="">{currentPage} / {maxiumumPage}</h5>
<button on:click={() => {setPage(-1)}} class="btn btn-sm bg-surface-600 rounded-md">&gt;</button>
</div>
{/if}
</div>
</div>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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}

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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"}
&gt;
{:else}
&lt;
{/if}
</button>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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}

View File

@ -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>

49
src/hooks.server.ts Normal file
View File

@ -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
}

25
src/lib/auth.ts Normal file
View File

@ -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
}

3
src/lib/avatarstore.ts Normal file
View File

@ -0,0 +1,3 @@
import { writable } from 'svelte/store';
export const avatarstore = writable("");

3
src/lib/coinstore.ts Normal file
View File

@ -0,0 +1,3 @@
import { writable } from 'svelte/store';
export const coinstore = writable(0);

3
src/lib/url.ts Normal file
View File

@ -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

15
src/routes/+error.svelte Normal file
View File

@ -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>

View File

@ -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;

309
src/routes/+layout.svelte Normal file
View File

@ -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>

139
src/routes/+page.svelte Normal file
View File

@ -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}} />

View File

@ -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>

View File

@ -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>

View File

@ -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">&lt;</button>
<h5 class="">{currentPage} / {maxiumumPage}</h5>
<button on:click={() => {setPage(-1)}} class="btn btn-sm bg-surface-600 rounded-md">&gt;</button>
</div>
</div>
</div>

View File

@ -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;

View File

@ -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;

View File

@ -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"/>

View File

@ -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;

View File

@ -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">&lt;</button>
<h5 class="">{currentPage} / {maxiumumPage}</h5>
<button on:click={() => {setPage(-1)}} class="btn btn-sm bg-surface-600 rounded-md">&gt;</button>
</div>
</div>
</div>

View File

@ -0,0 +1,5 @@
export const load = ({ params }) => {
return {
slug: params.slug
}
}

View File

@ -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>

View File

@ -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>

View File

@ -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;

View File

@ -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>

21
src/routes/games/+page.ts Normal file
View File

@ -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;

View File

@ -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;

View File

@ -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>

View File

@ -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;

View File

@ -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>

View File

@ -0,0 +1,5 @@
export const load = ({ params }) => {
return {
slug: params.slug
}
}

View File

@ -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>

View File

@ -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;

View File

@ -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">&lt;</button>
<h5 class="">1 / 1</h5>
<button on:click={() => {}} class="btn btn-sm bg-surface-600 rounded-md">&gt;</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>

View File

@ -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;

View File

@ -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>

View File

@ -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>

22
src/routes/home/+page.ts Normal file
View File

@ -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;

View File

@ -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>

View File

@ -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>

View File

@ -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;

View File

@ -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>

View File

@ -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}

View File

View File

@ -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>

View File

@ -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;

View File

@ -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">&lt;</button>
<h5 class="">{currentPage} / {maxiumumPage}</h5>
<button on:click={() => {setPage(-1)}} class="btn btn-sm bg-surface-600 rounded-md">&gt;</button>
</div>
</div>

View File

@ -0,0 +1,5 @@
export const load = ({ params }) => {
return {
slug: params.slug
}
}

View File

@ -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

View File

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 195 KiB

Some files were not shown because too many files have changed in this diff Show More