Path: blob/main/src/scripts/customization/customTheme/CustomTheme.svelte
8160 views
<script lang="ts">
import Group from "../../../hud/Group.svelte";
import { defaultThemes } from './defaultThemes'
import Delete from 'svelte-material-icons/Delete.svelte';
import PlusCircleOutline from 'svelte-material-icons/PlusCircleOutline.svelte';
import { setValue, getValue } from "../../../persist";
import Button from "../../../hud/components/Button.svelte";
import CreateTheme from "./CreateTheme.svelte";
import { ITheme } from "../../../types";
import socketManager from '../../../network/socketManager';
let { transportType } = socketManager;
let themesString = getValue('customThemes');
let themes: ITheme[] = [];
if(themesString) {
themes = JSON.parse(themesString);
} else {
themes = defaultThemes.map(theme => ({...theme, custom: false}));
}
let questionElement: HTMLElement | null = null;
const selector = '[style^="opacity:"][style*="transform: translateY(0%)"]'
let observer = new MutationObserver((mutations) => {
for(let mutation of mutations) {
for(let node of mutation.addedNodes) {
if(!(node instanceof HTMLElement)) continue;
let found: HTMLElement;
if($transportType === 'colyseus') {
found = node.querySelector(selector);
} else if($transportType === 'blueboat') {
if(node.matches(selector)) {
found = node;
}
}
if(!found) continue;
questionElement = found;
applyTheme();
}
}
})
const attachObserver = () => {
observer.observe(document.body, {
childList: true,
subtree: true
})
}
if(!document.body) {
window.addEventListener('DOMContentLoaded', attachObserver);
} else {
attachObserver();
}
let selectedTheme = themes[parseInt(getValue('selectedTheme', '0'))];
let themeEnabled = getValue('themeEnabled') === 'true';
function updateEnabled() {
if(themeEnabled) {
setValue('themeEnabled', 'true');
setTheme(selectedTheme);
} else {
setValue('themeEnabled', 'false');
removeTheme();
}
}
function setTheme(theme: ITheme) {
selectedTheme = theme;
setValue('selectedTheme', themes.indexOf(theme).toString());
if(themeEnabled) applyTheme();
}
function applyTheme() {
if(!questionElement || !themeEnabled) return;
let questionDisplay = questionElement.firstChild.firstChild.firstChild.firstChild as HTMLElement;
questionDisplay.style.background = selectedTheme.question.background;
questionDisplay.style.color = selectedTheme.question.text;
for(let i = 0; i < questionElement.children[1].children.length; i++) {
let option = questionElement.children[1].children[i];
let optionDisplay = option.firstChild as HTMLElement;
optionDisplay.style.background = selectedTheme.palette[i].background;
optionDisplay.style.color = selectedTheme.palette[i].text;
}
}
function removeTheme() {
if(!questionElement) return;
let questionDisplay = questionElement.firstChild.firstChild.firstChild.firstChild as HTMLElement;
questionDisplay.style.background = '';
questionDisplay.style.color = '';
for(let i = 0; i < questionElement.children[1].children.length; i++) {
let option = questionElement.children[1].children[i];
let optionDisplay = option.firstChild as HTMLElement;
optionDisplay.style.background = '';
optionDisplay.style.color = '';
}
}
let createOpen = false;
function onSubmit(message: CustomEvent<ITheme | null>) {
createOpen = false;
if(!message.detail) return;
selectedTheme = message.detail;
themes = [message.detail, ...themes];
setValue('selectedTheme', '0');
setValue('customThemes', JSON.stringify(themes));
applyTheme();
}
function deleteTheme(theme: ITheme) {
let res = confirm('Are you sure you want to delete this theme?');
if(!res) return;
let index = themes.indexOf(theme);
themes.splice(index, 1);
setValue('customThemes', JSON.stringify(themes));
if(theme === selectedTheme) {
selectedTheme = themes[0];
setValue('selectedTheme', '0');
applyTheme();
}
themes = themes; // rerender
}
</script>
{#if createOpen}
<CreateTheme on:submit={onSubmit} />
{/if}
<Group name="Custom Theme">
<div class="themeEnabled">
<div>Use Custom Theme?</div>
<input type="checkbox" bind:checked={themeEnabled} on:change={updateEnabled} />
</div>
<Button on:click={() => createOpen = true}>
<div class="createTheme">
New Theme
<PlusCircleOutline width={30} height={30} />
</div>
</Button>
{#each themes as theme, i}
<!-- svelte-ignore a11y-click-events-have-key-events a11y-no-static-element-interactions -->
<div class="theme" style="background-color:{theme.question.background};color:{theme.question.text}"
class:selected={selectedTheme === theme} on:click={() => setTheme(theme)}>
<div class="title">
Theme {i + 1}
{#if theme.custom}
<div on:click|stopPropagation={() => deleteTheme(theme)}>
<Delete width={25} height={25} />
</div>
{/if}
</div>
<div class="options">
{#each theme.palette as color, j}
<div class="option" style="background-color:{color.background};color:{color.text};">
{j + 1}
</div>
{/each}
</div>
</div>
{/each}
</Group>
<style>
.createTheme {
display: flex;
align-items: center;
justify-content: center;
gap: 5px;
}
.title {
position: relative;
width: 100%;
text-align: center;
display: flex;
justify-content: center;
}
.title > div {
position: absolute;
right: 0;
top: 0;
}
.theme {
display: flex;
flex-direction: column;
align-items: center;
margin: 10px;
}
.theme.selected {
/* perhaps a little overkill */
border: 5px solid black;
outline: 3px solid white;
}
.options {
display: flex;
flex-direction: row;
align-items: center;
width: 100%;
}
.option {
flex-grow: 1;
text-align: center;
margin: 5px;
}
.themeEnabled {
display: flex;
align-items: center;
justify-content: center;
margin-left: 10px;
margin-right: 10px;
}
.themeEnabled > div {
margin-right: 10px;
}
.themeEnabled > input {
width: 20px;
height: 20px;
}
</style>