Compare commits
1 Commits
Author | SHA1 | Date |
---|---|---|
|
2dbeb6ee9c |
|
@ -48,7 +48,7 @@
|
||||||
|
|
||||||
docker = dockerTools.buildImage {
|
docker = dockerTools.buildImage {
|
||||||
name = "memejoin-svelte";
|
name = "memejoin-svelte";
|
||||||
tag = "0.1.2_1";
|
tag = "0.1.0";
|
||||||
copyToRoot = buildEnv {
|
copyToRoot = buildEnv {
|
||||||
name = "image-root";
|
name = "image-root";
|
||||||
paths = [ nodejs yarnPkg ];
|
paths = [ nodejs yarnPkg ];
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -2,56 +2,34 @@
|
||||||
import { intros, member } from './store.ts';
|
import { intros, member } from './store.ts';
|
||||||
import { member_can, Permission } from './permissions.ts';
|
import { member_can, Permission } from './permissions.ts';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { uploadIntro } from './api.js';
|
|
||||||
|
|
||||||
let enteredUploadTitle = '';
|
|
||||||
let selectedUploadGuild = null;
|
|
||||||
let selectedFile = null;
|
|
||||||
let uploadPromise = null;
|
|
||||||
|
|
||||||
let enteredUrl = '';
|
let enteredUrl = '';
|
||||||
let enteredTitle = '';
|
let enteredTitle = '';
|
||||||
let selectedGuild = null;
|
let selectedGuild = null;
|
||||||
let downloadPromise = null;
|
|
||||||
|
|
||||||
let canDownloadAny = false;
|
let canDownloadAny = false;
|
||||||
|
let downloadPromise = null;
|
||||||
|
|
||||||
let allowedGuildList = [];
|
let allowedGuildList = [];
|
||||||
|
|
||||||
$: allowedGuildList = $member.guilds
|
$: allowedGuildList = $member.guilds
|
||||||
.filter((guild) => member_can(guild.permissions, Permission.CanDownload))
|
.filter((guild) => member_can(guild.permissions, Permission.CanDownload))
|
||||||
.map((guild) => guild);
|
.map((guild) => guild.name);
|
||||||
|
|
||||||
$: canDownloadAny = allowedGuildList.length > 0;
|
$: canDownloadAny = allowedGuildList.length > 0;
|
||||||
|
|
||||||
const download = () => {
|
const download = () => {
|
||||||
if (!!selectedGuild) {
|
if (!!selectedGuild) {
|
||||||
downloadPromise = (async () => {
|
downloadPromise = (async () => {
|
||||||
await intros.addIntro(selectedGuild.id, enteredUrl, enteredTitle, $member.token);
|
await intros.addIntro(selectedGuild, enteredUrl, enteredTitle, $member.token);
|
||||||
await intros.fetchIntros($member.guilds);
|
await intros.fetchIntros($member.guilds);
|
||||||
})();
|
})();
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const upload = () => {
|
|
||||||
// TODO: limit to 1 file
|
|
||||||
if (!!selectedUploadGuild) {
|
|
||||||
uploadPromise = (async () => {
|
|
||||||
await uploadIntro(selectedUploadGuild.id, enteredUploadTitle, selectedFile[0], $member.token);
|
|
||||||
await intros.fetchIntros($member.guilds);
|
|
||||||
})();
|
|
||||||
} else {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if canDownloadAny}
|
{#if canDownloadAny}
|
||||||
<div id="cardContent">
|
<div>
|
||||||
<div id="nestedCardContent">
|
|
||||||
<h3>Download New Intro</h3>
|
<h3>Download New Intro</h3>
|
||||||
{#if !!downloadPromise}
|
{#if !!downloadPromise}
|
||||||
{#await downloadPromise}
|
{#await downloadPromise}
|
||||||
|
@ -66,9 +44,7 @@
|
||||||
{:else}
|
{:else}
|
||||||
<select bind:value={selectedGuild}>
|
<select bind:value={selectedGuild}>
|
||||||
{#each allowedGuildList as guild}
|
{#each allowedGuildList as guild}
|
||||||
<option value={guild}>
|
<option value={guild}>{guild}</option>
|
||||||
{guild.name}
|
|
||||||
</option>
|
|
||||||
{/each}
|
{/each}
|
||||||
</select>
|
</select>
|
||||||
<input bind:value={enteredTitle} placeholder='enter intro title'>
|
<input bind:value={enteredTitle} placeholder='enter intro title'>
|
||||||
|
@ -76,51 +52,17 @@
|
||||||
<button on:click={download}>Download</button>
|
<button on:click={download}>Download</button>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div id="nestedCardContent">
|
|
||||||
<h3>Upload New Intro</h3>
|
|
||||||
{#if !!uploadPromise}
|
|
||||||
{#await uploadPromise}
|
|
||||||
<p>uploading...</p>
|
|
||||||
{:then result}
|
|
||||||
<p>Uploaded</p>
|
|
||||||
<button on:click={() => {uploadPromise = null}}>Add another</button>
|
|
||||||
{:catch err}
|
|
||||||
<p style='color: red'>{err}</p>
|
|
||||||
<button on:click={() => {uploadPromise = null}}>Ok</button>
|
|
||||||
{/await}
|
|
||||||
{:else}
|
|
||||||
<select bind:value={selectedUploadGuild} placeholder='Choose Guild'>
|
|
||||||
{#each allowedGuildList as guild}
|
|
||||||
<option value={guild}>{guild.name}</option>
|
|
||||||
{/each}
|
|
||||||
</select>
|
|
||||||
<input bind:value={enteredUploadTitle} placeholder='enter intro title'>
|
|
||||||
<label for="fileSelect" id="uploadLabel">
|
|
||||||
{#if !!selectedFile}
|
|
||||||
{selectedFile[0].name}
|
|
||||||
{:else}
|
|
||||||
Select Intro
|
|
||||||
{/if}
|
|
||||||
<input id="fileSelect" type="file" accept="audio/*, video/*" bind:files={selectedFile}>
|
|
||||||
</label>
|
|
||||||
<button on:click={upload}>Upload</button>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
div#uploader {
|
div {
|
||||||
display: flex;
|
display: flex;
|
||||||
position: relative;
|
width: 80%;
|
||||||
width: 85%;
|
|
||||||
height: 85%;
|
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background-color: #2a2a4a;
|
background-color: #2a2a4a;
|
||||||
padding: 16px;
|
padding: 1.5em;
|
||||||
box-shadow: 1px 3px 4px 1px #1f1f36;
|
box-shadow: 1px 3px 4px 1px #1f1f36;
|
||||||
margin: 16px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
h3 {
|
h3 {
|
||||||
|
@ -128,47 +70,14 @@
|
||||||
margin-bottom: 2em;
|
margin-bottom: 2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
select {
|
|
||||||
appearance: none;
|
|
||||||
background-color: #393963;
|
|
||||||
color: lightgrey;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
option {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
input, button {
|
input, button {
|
||||||
margin: 0.5em;
|
margin: 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
label#uploadLabel {
|
|
||||||
width: 100%;
|
|
||||||
color: lightgray;
|
|
||||||
text-align: center;
|
|
||||||
border-style: none;
|
|
||||||
border-radius: 4px;
|
|
||||||
background-image: linear-gradient(0deg, #1f1f36, #23233d);
|
|
||||||
padding: 0.5em 0.5em 0.5em;
|
|
||||||
margin: 0 0 16px 0px;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
label#uploadLabel:hover {
|
|
||||||
background-image: linear-gradient(0deg, #34345b, #393963);
|
|
||||||
box-shadow: 1px 3px 15px 1px #1f1f36;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
input#fileSelect {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
input {
|
input {
|
||||||
width: 100%;
|
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
border-color: #323259;
|
border-color: #323259;
|
||||||
|
width: 100%;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: lightgrey;
|
color: lightgrey;
|
||||||
background-image: linear-gradient(0deg, #23233d, #1f1f36);
|
background-image: linear-gradient(0deg, #23233d, #1f1f36);
|
||||||
|
|
|
@ -11,7 +11,6 @@
|
||||||
export let exclude = null;
|
export let exclude = null;
|
||||||
export let include = null;
|
export let include = null;
|
||||||
export let btnLabel = 'Add';
|
export let btnLabel = 'Add';
|
||||||
export let btnDanger = false;
|
|
||||||
export let emptyMsg = null;
|
export let emptyMsg = null;
|
||||||
|
|
||||||
let filteredIntroList = [];
|
let filteredIntroList = [];
|
||||||
|
@ -83,7 +82,6 @@
|
||||||
let selectedIntros = [];
|
let selectedIntros = [];
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div id="nestedCardContent" class="cardLight cardNoShadow">
|
|
||||||
<div id="list">
|
<div id="list">
|
||||||
{#if !!filteredIntroList && filteredIntroList.length > 0}
|
{#if !!filteredIntroList && filteredIntroList.length > 0}
|
||||||
{#each filteredIntroList as intro (intro.index)}
|
{#each filteredIntroList as intro (intro.index)}
|
||||||
|
@ -95,23 +93,5 @@
|
||||||
{:else}
|
{:else}
|
||||||
<p style="color: red">{emptyMsg}</p>
|
<p style="color: red">{emptyMsg}</p>
|
||||||
{/if}
|
{/if}
|
||||||
|
<button on:click={onConfirm}>{btnLabel}</button>
|
||||||
</div>
|
</div>
|
||||||
<button style="background-image: linear-gradient(0deg, {btnDanger ? '#a81111' : '#1f1f36'}, {btnDanger ? '#db1616' : '#23233d'});"on:click={onConfirm}>{btnLabel}</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
div#list {
|
|
||||||
display: flex;
|
|
||||||
width: 85%;
|
|
||||||
border-radius: 4px;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
background-color: #323259;
|
|
||||||
padding: 1em;
|
|
||||||
overflow-x: hidden;
|
|
||||||
overflow-y: auto;
|
|
||||||
margin: 16px;
|
|
||||||
height: 256px;
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
|
||||||
|
|
|
@ -1,60 +1,26 @@
|
||||||
<script>
|
<script>
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { member, intros } from './store.ts';
|
import { member, intros } from './store.ts';
|
||||||
import { env } from '$env/dynamic/public';
|
|
||||||
|
|
||||||
const authorizeUri =
|
const authorizeUri = "https://discord.com/api/oauth2/authorize?client_id=577634620728934400&redirect_uri=https%3A%2F%2Fspacegirl.nl%2Fmemes%2Fauth&response_type=code&scope=guilds.members.read%20guilds%20identify";
|
||||||
`https://discord.com/api/oauth2/authorize?client_id=577634620728934400&redirect_uri=${encodeURIComponent(env.PUBLIC_APP_BASE_URL + '/auth')}&response_type=code&scope=${encodeURIComponent('guilds.members.read guilds identify')}`;
|
|
||||||
|
|
||||||
let loginPromise = null;
|
onMount(async () => {
|
||||||
let mounted = false;
|
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
loginPromise = (async () => {
|
|
||||||
const token = window.localStorage.getItem('token');
|
const token = window.localStorage.getItem('token');
|
||||||
|
|
||||||
if (!!token) {
|
if (!!token) {
|
||||||
try {
|
|
||||||
await member.pullData(token);
|
await member.pullData(token);
|
||||||
await intros.fetchIntros($member.guilds);
|
await intros.fetchIntros($member.guilds);
|
||||||
|
|
||||||
return true;
|
|
||||||
} catch (err) {
|
|
||||||
console.table(err);
|
|
||||||
if (err.message === "User doesn't exist") {
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
throw err;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
|
|
||||||
mounted = true;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const login = async (username) => {
|
const login = async (username) => {
|
||||||
window.location = authorizeUri;
|
window.location = authorizeUri;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let loginPromise = null;
|
||||||
|
|
||||||
let enteredUsername = '';
|
let enteredUsername = '';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if !!loginPromise}
|
<p style="color:red;">You need to login first</p>
|
||||||
{#await loginPromise}
|
|
||||||
<p>Loading...</p>
|
|
||||||
{:then result}
|
|
||||||
{#if result}
|
|
||||||
<p>Success</p>
|
|
||||||
{:else}
|
|
||||||
<button on:click={() => loginPromise = login(enteredUsername)}>Login</button>
|
<button on:click={() => loginPromise = login(enteredUsername)}>Login</button>
|
||||||
{/if}
|
|
||||||
{:catch}
|
|
||||||
<p style="color: red">An error occurred while contacting the server. Try refreshing</p>
|
|
||||||
{/await}
|
|
||||||
{:else if !mounted}
|
|
||||||
<p>Loading...</p>
|
|
||||||
{/if}
|
|
||||||
|
|
54
src/api.js
54
src/api.js
|
@ -1,54 +0,0 @@
|
||||||
import { env } from '$env/dynamic/public';
|
|
||||||
|
|
||||||
export const apiCall = async (method, endpoint, token, body, contentType) => {
|
|
||||||
const headers = (() => {
|
|
||||||
if (!!token) {
|
|
||||||
return { 'token': token };
|
|
||||||
}
|
|
||||||
|
|
||||||
return {}
|
|
||||||
})();
|
|
||||||
|
|
||||||
return (await
|
|
||||||
fetch(
|
|
||||||
`${env.PUBLIC_API_URL}/${endpoint}`,
|
|
||||||
{ method: method, headers: { 'Content-Type': contentType, ...headers }, body: body })
|
|
||||||
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const authenticate = async (code) => {
|
|
||||||
return await apiCall('GET', `auth?code=${code}`, null);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getMe = async (token) => {
|
|
||||||
return await apiCall('GET', 'me', token);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getGuildIntros = async (guild, token) => {
|
|
||||||
return await apiCall('GET', `intros/${guild}`, token);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const addGuildIntro = async (guild, url, title, token) => {
|
|
||||||
return await apiCall('GET', `intros/${guild}/add?url=${encodeURIComponent(url)}&name=${encodeURIComponent(title)}`, token);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const chooseIntro = async (guild, channel, selectedIntros, token) => {
|
|
||||||
for (const intro of selectedIntros) {
|
|
||||||
await apiCall('POST', `intros/${guild}/${channel}/${intro}`, token);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const removeIntro = async (guild, channel, selectedIntros, token) => {
|
|
||||||
for (const intro of selectedIntros) {
|
|
||||||
await apiCall('POST', `intros/${guild}/${channel}/${intro}/remove`, token);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const uploadIntro = async (guild, name, file, token) => {
|
|
||||||
await apiCall('POST', `intros/${guild}/upload?name=${encodeURIComponent(name)}`, token, file);
|
|
||||||
}
|
|
||||||
|
|
||||||
export const deleteIntro = async (guild, selectedIntros, token) => {
|
|
||||||
await apiCall('DELETE', `intros/${guild}/delete`, token, JSON.stringify(selectedIntros), 'application/json');
|
|
||||||
}
|
|
57
src/app.html
57
src/app.html
|
@ -4,7 +4,6 @@
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
||||||
<meta name="viewport" content="width=device-width" />
|
<meta name="viewport" content="width=device-width" />
|
||||||
<title>MemeJoin - Dashboard</title>
|
|
||||||
%sveltekit.head%
|
%sveltekit.head%
|
||||||
</head>
|
</head>
|
||||||
<style>
|
<style>
|
||||||
|
@ -13,49 +12,48 @@
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background-color: #313052;
|
background-color: #313052;
|
||||||
width: max-content;
|
font-family: 'Cantarell';
|
||||||
}
|
}
|
||||||
|
|
||||||
h1, h2, h3, h4, h5, p, label, li {
|
h1, h2, h3, h4, h5, p, label, li {
|
||||||
color: lightgrey;
|
color: lightgrey;
|
||||||
}
|
}
|
||||||
|
|
||||||
div#cardContent {
|
div#intros {
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(500px, 1fr));
|
|
||||||
align-items: stretch;
|
|
||||||
justify-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
background-color: #323259;
|
|
||||||
box-shadow: 1px 3px 15px 1px #1f1f36;
|
|
||||||
margin-bottom: 2em;
|
|
||||||
padding: 2em;
|
|
||||||
gap: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
div#nestedCardContent {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
position: relative;
|
|
||||||
width: 85%;
|
|
||||||
height: fit-content;
|
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
background-color: #323259;
|
||||||
|
padding: 0.5em;
|
||||||
|
box-shadow: 1px 3px 15px 1px #1f1f36;
|
||||||
|
}
|
||||||
|
|
||||||
|
div#guild-settings {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
background-color: #2a2a4a;
|
background-color: #2a2a4a;
|
||||||
padding: 16px;
|
margin: 1em;
|
||||||
box-shadow: 1px 3px 4px 1px #1f1f36;
|
box-shadow: 1px 3px 4px 1px #1f1f36;
|
||||||
}
|
}
|
||||||
|
|
||||||
div#nestedCardContent.cardNoShadow{
|
div#channel-settings {
|
||||||
box-shadow: none;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
background-color: #2a2a4a;
|
||||||
|
margin: 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
div#nestedCardContent.cardLight{
|
div#list {
|
||||||
background-color: #323259;
|
display: inline-flex;
|
||||||
|
width: 85%;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
button, #list-item {
|
button, #list-item {
|
||||||
color: lightgray;
|
color: lightgray;
|
||||||
font-family: 'JetBrains Mono';
|
|
||||||
border-style: none;
|
border-style: none;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
background-image: linear-gradient(0deg, #1f1f36, #23233d);
|
background-image: linear-gradient(0deg, #1f1f36, #23233d);
|
||||||
|
@ -63,20 +61,21 @@
|
||||||
margin: 0 0 16px 0px;
|
margin: 0 0 16px 0px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
overflow-wrap: break-word;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
button:hover, #list-item:hover {
|
button:hover, #list-item:hover {
|
||||||
background-image: linear-gradient(0deg, #34345b, #393963);
|
background-image: linear-gradient(0deg, #34345b, #393963);
|
||||||
box-shadow: 1px 3px 15px 1px #1f1f36;
|
box-shadow: 1px 3px 15px 1px #1f1f36;
|
||||||
cursor: pointer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#list-item > input[type="checkbox"]:checked {
|
#list-item > input[type="checkbox"] {
|
||||||
|
display: none;
|
||||||
}
|
}
|
||||||
#list-item:has(input[type="checkbox"]:checked) {
|
#list-item:has(input[type="checkbox"]:checked) {
|
||||||
background-image: linear-gradient(0deg, #40406e, #444475);
|
background-image: linear-gradient(0deg, #40406e, #444475);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<body data-sveltekit-preload-data="hover" style="display: block; margin: 0; width: 100%; overflow-x: hidden; overflow-y: scroll;"><div>%sveltekit.body%</div></body>
|
<body data-sveltekit-preload-data="hover">
|
||||||
|
<div style="display: contents">%sveltekit.body%</div>
|
||||||
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -1,38 +0,0 @@
|
||||||
<script>
|
|
||||||
import { member, intros } from '../store.ts';
|
|
||||||
import { deleteIntro } from '../api.js';
|
|
||||||
import IntroSelector from '../IntroSelector.svelte';
|
|
||||||
|
|
||||||
export let guilds = [];
|
|
||||||
|
|
||||||
let deletePromise = null;
|
|
||||||
|
|
||||||
const apiDeleteIntro = async (guild, selectedIntros) => {
|
|
||||||
await deleteIntro(guild, selectedIntros, $member.token);
|
|
||||||
await member.pullData($member.token);
|
|
||||||
await intros.fetchIntros($member.guilds);
|
|
||||||
};
|
|
||||||
const deleteIntros = (event) => {
|
|
||||||
deletePromise = apiDeleteIntro(event.detail.guild, event.detail.intros);
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{#if !!deletePromise}
|
|
||||||
{#await deletePromise}
|
|
||||||
{:catch err}
|
|
||||||
<p style="color: red">{err}</p>
|
|
||||||
{/await}
|
|
||||||
{/if}
|
|
||||||
<div id="nestedCardContent">
|
|
||||||
{#each guilds as guild}
|
|
||||||
<h4>{guild.name}</h4>
|
|
||||||
<IntroSelector
|
|
||||||
guild={guild.id}
|
|
||||||
exclude={[]}
|
|
||||||
on:confirm={deleteIntros}
|
|
||||||
btnLabel="Delete Intro From Guild"
|
|
||||||
btnDanger={true}
|
|
||||||
emptyMsg="Your guild doesn't have any intros, try adding some below"
|
|
||||||
/>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
|
@ -1,48 +0,0 @@
|
||||||
<script>
|
|
||||||
import { member, intros } from '../store.ts';
|
|
||||||
import IntroSelector from '../IntroSelector.svelte';
|
|
||||||
import { slide } from 'svelte/transition';
|
|
||||||
import DeleteSelector from './DeleteSelector.svelte';
|
|
||||||
import Permissions from './Permissions.svelte';
|
|
||||||
import { member_can, Permission } from '../permissions.ts';
|
|
||||||
|
|
||||||
let dashboardVisible = false;
|
|
||||||
|
|
||||||
let allowedGuildList = [];
|
|
||||||
|
|
||||||
$: allowedGuildList = $member.guilds
|
|
||||||
.filter((guild) => member_can(guild.permissions, Permission.CanDelete))
|
|
||||||
.map((guild) => guild);
|
|
||||||
|
|
||||||
$: isModerator = allowedGuildList.length > 0;
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{#if isModerator}
|
|
||||||
<h3>Wow you're a moderator, here is a cool dashboard for you to use</h3>
|
|
||||||
<div id="cardContent" class="noGrid">
|
|
||||||
<div id="nestedCardContent" class="cardLight cardNoShadow">
|
|
||||||
{#if dashboardVisible}
|
|
||||||
<div id="nestedCardContent" class="cardLight cardNoShadow" transition:slide>
|
|
||||||
<button on:click={() => dashboardVisible = false}>
|
|
||||||
Hide dashboard ^
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<DeleteSelector guilds={allowedGuildList}/>
|
|
||||||
<!--<Permissions />-->
|
|
||||||
</div>
|
|
||||||
{:else}
|
|
||||||
<button on:click={() => dashboardVisible = true}>
|
|
||||||
Show dashboard v
|
|
||||||
</button>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<style>
|
|
||||||
h3 {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
<h3>Permissions</h3>
|
|
|
@ -1,7 +1,6 @@
|
||||||
export const Permission = {
|
export const Permission = {
|
||||||
None: 0,
|
None: 0,
|
||||||
CanDownload: 1,
|
CanDownload: 1,
|
||||||
CanDelete: 2,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const member_can = (permissions, perm) => {
|
export const member_can = (permissions, perm) => {
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
<script>
|
|
||||||
import './global.css';
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<slot></slot>
|
|
|
@ -1,61 +1,76 @@
|
||||||
<script>
|
<script>
|
||||||
import { env } from '$env/dynamic/public';
|
|
||||||
import { member, intros } from '../store.ts';
|
import { member, intros } from '../store.ts';
|
||||||
import { member_can } from '../permissions.ts';
|
import { member_can } from '../permissions.ts';
|
||||||
import { chooseIntro, removeIntro } from '../api.js';
|
|
||||||
import Login from '../Login.svelte';
|
import Login from '../Login.svelte';
|
||||||
import IntroSelector from '../IntroSelector.svelte';
|
import IntroSelector from '../IntroSelector.svelte';
|
||||||
import IntroDownloader from '../IntroDownloader.svelte';
|
import IntroDownloader from '../IntroDownloader.svelte';
|
||||||
import ModDashboard from '../components/ModDashboard.svelte';
|
|
||||||
|
|
||||||
let addIntroPromise = null;
|
let addIntroPromise = null;
|
||||||
let removeIntroPromise = null;
|
let removeIntroPromise = null;
|
||||||
|
|
||||||
const apiAddIntro = async (guild, channel, username, selectedIntros) => {
|
const apiAddIntro = async (guild, channel, username, selectedIntros) => {
|
||||||
await chooseIntro(guild, channel, selectedIntros, $member.token);
|
for (const intro of selectedIntros) {
|
||||||
|
const response = await fetch(
|
||||||
|
`https://${process.env.API_URL}/memes/api/intros/${guild}/${channel}/${intro}`,
|
||||||
|
{ method: 'POST', headers: {"token": $member.token} }
|
||||||
|
);
|
||||||
|
if (!response.ok) {
|
||||||
|
const body = await response.json();
|
||||||
|
throw new Error(`${body}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
await member.pullData($member.token);
|
await member.pullData($member.token);
|
||||||
};
|
};
|
||||||
|
|
||||||
const apiRemoveIntro = async (guild, channel, username, selectedIntros) => {
|
const apiRemoveIntro = async (guild, channel, username, selectedIntros) => {
|
||||||
await removeIntro(guild, channel, selectedIntros, $member.token);
|
for (const intro of selectedIntros) {
|
||||||
|
const response = await fetch(
|
||||||
|
`https://${process.env.API_URL}/memes/api/intros/${guild}/${channel}/${intro}/remove`,
|
||||||
|
{ method: 'POST', headers: {"token": $member.token} }
|
||||||
|
);
|
||||||
|
if (!response.ok) {
|
||||||
|
const body = await response.json();
|
||||||
|
throw new Error(`${body}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
await member.pullData($member.token);
|
await member.pullData($member.token);
|
||||||
};
|
};
|
||||||
|
|
||||||
const addIntros = (event) => {
|
const addIntros = (event) => {
|
||||||
addIntroPromise = apiAddIntro(event.detail.guild, event.detail.channel, $member.username, event.detail.intros);
|
addIntroPromise = apiAddIntro(event.detail.guild, event.detail.channel, $member.username, event.detail.intros);
|
||||||
|
|
||||||
}
|
}
|
||||||
const removeIntros = (event) => {
|
const removeIntros = (event) => {
|
||||||
removeIntroPromise = apiRemoveIntro(event.detail.guild, event.detail.channel, $member.username, event.detail.intros);
|
removeIntroPromise = apiRemoveIntro(event.detail.guild, event.detail.channel, $member.username, event.detail.intros);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if !!$member}
|
|
||||||
<div id="mainContent">
|
|
||||||
<h1>MemeJoin - A bot for user intros</h1>
|
<h1>MemeJoin - A bot for user intros</h1>
|
||||||
<p style='text-align:center;'>{$member.username}</p>
|
|
||||||
|
|
||||||
<ModDashboard />
|
{#if !!$member}
|
||||||
<IntroDownloader />
|
<p>{$member.username}</p>
|
||||||
<h3>Guild Settings</h3>
|
|
||||||
<div id="cardContent">
|
<h3>Your Intros</h3>
|
||||||
|
<div id="intros">
|
||||||
{#each $member.guilds as guild}
|
{#each $member.guilds as guild}
|
||||||
<div id="nestedCardContent" class="cardLight cardNoShadow">
|
|
||||||
<h4>{guild.name}</h4>
|
<h4>{guild.name}</h4>
|
||||||
<div id="nestedCardContent">
|
<IntroDownloader />
|
||||||
|
<div id="guild-settings">
|
||||||
{#each guild.channels as channel}
|
{#each guild.channels as channel}
|
||||||
<div id="nestedCardContent" class="cardNoShadow">
|
<div id="channel-settings">
|
||||||
<h4>{channel.name}</h4>
|
<h4>{channel.name}</h4>
|
||||||
{#await addIntroPromise then result}
|
{#await addIntroPromise then result}
|
||||||
{:catch err}
|
{:catch err}
|
||||||
<p style='color: red'>Failed to add intro {err}</p>
|
<p style='color: red'>Failed to add intro</p>
|
||||||
{/await}
|
{/await}
|
||||||
{#await removeIntroPromise then result}
|
{#await removeIntroPromise then result}
|
||||||
{:catch err}
|
{:catch err}
|
||||||
<p style='color: red'>Failed to remove intro</p>
|
<p style='color: red'>Failed to remove intro</p>
|
||||||
{/await}
|
{/await}
|
||||||
<h3>Your Current Intros</h3>
|
|
||||||
<IntroSelector
|
<IntroSelector
|
||||||
guild={guild.id}
|
guild={guild.name}
|
||||||
channel={channel.name}
|
channel={channel.name}
|
||||||
include={channel.intros.map((x) => x.index)}
|
include={channel.intros.map((x) => x.index)}
|
||||||
on:confirm={removeIntros}
|
on:confirm={removeIntros}
|
||||||
|
@ -63,61 +78,21 @@
|
||||||
emptyMsg="You don't have any intros, try adding one"
|
emptyMsg="You don't have any intros, try adding one"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<h3>Select Intros</h3>
|
<h3>Add Intros</h3>
|
||||||
<IntroSelector
|
<IntroSelector
|
||||||
guild={guild.id}
|
guild={guild.name}
|
||||||
channel={channel.name}
|
channel={channel.name}
|
||||||
exclude={channel.intros.map((x) => x.index)}
|
exclude={channel.intros.map((x) => x.index)}
|
||||||
on:confirm={addIntros}
|
on:confirm={addIntros}
|
||||||
btnLabel="Choose"
|
|
||||||
emptyMsg="There are no intros"
|
emptyMsg="There are no intros"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
{:else}
|
{:else}
|
||||||
<Login />
|
<Login />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<style>
|
|
||||||
h1, h2, h3 {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
div#mainContent {
|
|
||||||
display: grid;
|
|
||||||
top: 10em;
|
|
||||||
margin: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
div#guild {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
div#guild-settings {
|
|
||||||
display: flex;
|
|
||||||
width: 85%;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: center;
|
|
||||||
background-color: #2a2a4a;
|
|
||||||
margin: 1em;
|
|
||||||
box-shadow: 1px 3px 4px 1px #1f1f36;
|
|
||||||
}
|
|
||||||
|
|
||||||
div#channel-settings {
|
|
||||||
display: flex;
|
|
||||||
width: 85%;
|
|
||||||
height: 95%;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
background-color: #2a2a4a;
|
|
||||||
margin: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
|
||||||
|
|
|
@ -1,29 +1,26 @@
|
||||||
<script>
|
<script>
|
||||||
import { env } from '$env/dynamic/public';
|
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { member, intros } from '../../store.ts';
|
import { member, intros } from '../../store.ts';
|
||||||
import { authenticate } from '../../api.js';
|
|
||||||
|
|
||||||
const code = $page.url.searchParams.get('code');
|
const code = $page.url.searchParams.get('code');
|
||||||
|
|
||||||
let loginFailed = false;
|
let loginFailed = false;
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
const response = await authenticate(code);
|
const response = await fetch(`https://${process.env.API_URL}/memes/api/auth?code=${code}`);
|
||||||
const body = await response.json();
|
const body = await response.json();
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
loginFailed = true
|
loginFailed = true
|
||||||
} else {
|
} else {
|
||||||
window.localStorage.setItem('token', body.token);
|
|
||||||
|
|
||||||
await member.pullData(body.token);
|
await member.pullData(body.token);
|
||||||
await intros.fetchIntros($member.guilds);
|
await intros.fetchIntros($member.guilds);
|
||||||
|
|
||||||
|
window.localStorage.setItem('token', body.token);
|
||||||
|
|
||||||
goto(`${env.PUBLIC_APP_BASE_URL}`)
|
goto('/')
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
@font-face {
|
|
||||||
font-family: 'JetBrains Mono';
|
|
||||||
src: url('/fonts/JetBrainsMono-Medium.woff2') format('woff');
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
font-family: 'JetBrains Mono';
|
|
||||||
}
|
|
25
src/store.ts
25
src/store.ts
|
@ -1,6 +1,5 @@
|
||||||
import { env } from '$env/dynamic/public';
|
|
||||||
import { readable, writable } from 'svelte/store';
|
import { readable, writable } from 'svelte/store';
|
||||||
import { getMe, getGuildIntros, addGuildIntro } from './api.js';
|
|
||||||
|
|
||||||
function createMemberStore() {
|
function createMemberStore() {
|
||||||
const { subscribe, set, update } = writable(null)
|
const { subscribe, set, update } = writable(null)
|
||||||
|
@ -10,13 +9,14 @@ function createMemberStore() {
|
||||||
set: set,
|
set: set,
|
||||||
addIntro: (intro: IntroIndex) => { update((n) => n.intros.push(intro)); return intro },
|
addIntro: (intro: IntroIndex) => { update((n) => n.intros.push(intro)); return intro },
|
||||||
pullData: async (token) => {
|
pullData: async (token) => {
|
||||||
const response = await getMe(token);
|
const response = (await (await fetch(
|
||||||
|
`https://${process.env.API_URL}/memes/api/me`,
|
||||||
|
{ headers: {"token": token} })).json())
|
||||||
|
|
||||||
if (!response.ok) {
|
if (response === "NoUserFound") {
|
||||||
throw new Error(await response.text());
|
return;
|
||||||
} else {
|
} else {
|
||||||
const body = await response.json();
|
set({ token: token, ...response.Me })
|
||||||
set({ token: token, ...body.Me })
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,8 @@ function createIntroStore() {
|
||||||
return {
|
return {
|
||||||
subscribe: subscribe,
|
subscribe: subscribe,
|
||||||
addIntro: async (guild, url, title, token) => {
|
addIntro: async (guild, url, title, token) => {
|
||||||
const response = await addGuildIntro(guild, url, title, token);
|
const response = await fetch(`https://${process.env.API_URL}/memes/api/intros/${guild}/add/${encodeURIComponent(url)}?name=${encodeURIComponent(title)}`,
|
||||||
|
{ method: 'GET', headers: { 'token': token } });
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(await response.body);
|
throw new Error(await response.body);
|
||||||
|
@ -38,13 +39,11 @@ function createIntroStore() {
|
||||||
let intros = new Map();
|
let intros = new Map();
|
||||||
|
|
||||||
for (const guild of guilds) {
|
for (const guild of guilds) {
|
||||||
const response = await getGuildIntros(guild.id, null);
|
const response = (await (await fetch(`https://${process.env.API_URL}/memes/api/intros/${guild.name}`)).json())
|
||||||
const body = await response.json();
|
|
||||||
|
|
||||||
if (response !== "NoGuildFound") {
|
if (response !== "NoGuildFound") {
|
||||||
let guild_intros = new Map();
|
let guild_intros = new Map();
|
||||||
|
|
||||||
Object.entries(body.Intros).forEach(([index, intro]) => {
|
Object.entries(response.Intros).forEach(([index, intro]) => {
|
||||||
if (!!intro.File) {
|
if (!!intro.File) {
|
||||||
guild_intros.set(index, { name: intro.File.friendlyName, filename: intro.File.filename });
|
guild_intros.set(index, { name: intro.File.friendlyName, filename: intro.File.filename });
|
||||||
} else if (!!intro.Online) {
|
} else if (!!intro.Online) {
|
||||||
|
@ -52,7 +51,7 @@ function createIntroStore() {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
intros.set(guild.id, guild_intros);
|
intros.set(guild.name, guild_intros);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Binary file not shown.
|
@ -2,9 +2,5 @@ import { sveltekit } from '@sveltejs/kit/vite';
|
||||||
import { defineConfig } from 'vite';
|
import { defineConfig } from 'vite';
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
server: {
|
|
||||||
host: "0.0.0.0",
|
|
||||||
port: "8080",
|
|
||||||
},
|
|
||||||
plugins: [sveltekit()]
|
plugins: [sveltekit()]
|
||||||
});
|
});
|
||||||
|
|
Reference in New Issue