mirror of
https://github.com/danbulant/Nertivia-Client
synced 2026-05-22 21:59:04 +00:00
added public theme service
This commit is contained in:
parent
c827a425ba
commit
bce31bb9dd
12 changed files with 1023 additions and 213 deletions
|
|
@ -166,9 +166,6 @@ export default {
|
|||
this.changed = true;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
console.log(this.server);
|
||||
},
|
||||
computed: {
|
||||
googleDriveLinked() {
|
||||
return this.$store.getters["settingsModule/settings"].GDriveLinked;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,106 @@
|
|||
<template>
|
||||
<div class="theme">
|
||||
<div
|
||||
class="name"
|
||||
:class="{ selected: selectedThemeID === theme.id }"
|
||||
@click="themeClick()"
|
||||
>
|
||||
{{ theme.name }}
|
||||
</div>
|
||||
<div class="context" v-if="theme.id == selectedThemeID">
|
||||
<div class="button" @click="applyButton">
|
||||
<div class="material-icons">check</div>
|
||||
<div class="btn-name">Apply</div>
|
||||
</div>
|
||||
<div class="button" @click="editButton">
|
||||
<div class="material-icons">edit</div>
|
||||
<div class="btn-name">Edit</div>
|
||||
</div>
|
||||
<div class="button" @click="makePublicButton">
|
||||
<div class="material-icons">public</div>
|
||||
<div class="btn-name">Manage Public</div>
|
||||
</div>
|
||||
<div class="button delete-button" @click="deleteButton">
|
||||
<div class="material-icons">delete</div>
|
||||
<div class="btn-name">Delete</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ['theme', 'selectedThemeID'],
|
||||
methods: {
|
||||
themeClick() {
|
||||
this.$emit('clicked')
|
||||
},
|
||||
applyButton() {
|
||||
this.$emit('apply')
|
||||
},
|
||||
editButton(){
|
||||
this.$emit('edit')
|
||||
},
|
||||
deleteButton(){
|
||||
this.$emit('delete')
|
||||
},
|
||||
makePublicButton() {
|
||||
this.$emit('makePublic')
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.theme {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
widows: 100%;
|
||||
min-height: 30px;
|
||||
flex-shrink: 0;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
color: rgb(230, 230, 230);
|
||||
transition: 0.2s;
|
||||
.name {
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
height: 100%;
|
||||
min-height: 30px;
|
||||
padding-left: 5px;
|
||||
flex: 1;
|
||||
align-items: center;
|
||||
transition: 0.2s;
|
||||
&:hover {
|
||||
color: white;
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
&.selected {
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
}
|
||||
.context {
|
||||
display: flex;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
.button {
|
||||
display: flex;
|
||||
margin: 5px;
|
||||
align-content: center;
|
||||
align-items: center;
|
||||
opacity: 0.8;
|
||||
transition: 0.2s;
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
.material-icons {
|
||||
margin-right: 5px;
|
||||
}
|
||||
.delete-button {
|
||||
margin: auto;
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,34 +1,14 @@
|
|||
<template>
|
||||
<div class="container">
|
||||
<div class="editing" v-if="editing">
|
||||
<div class="bar">
|
||||
<input
|
||||
class="theme-name"
|
||||
v-model="name"
|
||||
type="text"
|
||||
placeholder="Theme name"
|
||||
/>
|
||||
<div class="button" @click="saveButton">
|
||||
<div class="material-icons">save</div>
|
||||
Save & Apply
|
||||
</div>
|
||||
<div class="button" @click="closeButton">
|
||||
<div class="material-icons">clear</div>
|
||||
Close
|
||||
</div>
|
||||
</div>
|
||||
<!-- <prism-editor class="editor" v-model="code" language="css"></prism-editor> -->
|
||||
<codemirror
|
||||
class="editor"
|
||||
v-model="code"
|
||||
:options="cmOptions"
|
||||
@ready="onCmReady"
|
||||
/>
|
||||
<div class="notice">
|
||||
<div class="material-icons">warning</div>
|
||||
<div class="notice-message">Warning: Pasting someone elses code could be dangerous.</div>
|
||||
</div>
|
||||
</div>
|
||||
<make-public v-if="showMakePublic" @back="closeButton" :name="name" :themeID="selectedThemeID" />
|
||||
<Editor
|
||||
v-else-if="editing"
|
||||
:themeName="name"
|
||||
:themeCode="code"
|
||||
:savingStatus="saving"
|
||||
@close="closeButton"
|
||||
@save="saveButton"
|
||||
/>
|
||||
<div class="managing" v-else>
|
||||
<div class="bar">
|
||||
<div class="button">
|
||||
|
|
@ -42,131 +22,106 @@
|
|||
</div>
|
||||
<spinner v-if="!themes" />
|
||||
<div v-if="themes" class="themes-list">
|
||||
<div class="theme" v-for="theme in themes" :key="theme.id">
|
||||
<div
|
||||
class="name"
|
||||
:class="{ selected: selectedThemeID === theme.id }"
|
||||
@click="themeClick(theme.id)"
|
||||
>
|
||||
{{ theme.name }}
|
||||
</div>
|
||||
<div class="context" v-if="theme.id == selectedThemeID">
|
||||
<div class="button" @click="applyButton(theme.id)">
|
||||
<div class="material-icons">check</div>
|
||||
<div class="btn-name">Apply</div>
|
||||
</div>
|
||||
<div class="button" @click="editButton(theme.id)">
|
||||
<div class="material-icons">edit</div>
|
||||
<div class="btn-name">Edit</div>
|
||||
</div>
|
||||
<div class="button delete-button" @click="deleteButton(theme.id)">
|
||||
<div class="material-icons">delete</div>
|
||||
<div class="btn-name">Delete</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<theme-template
|
||||
v-for="theme in themes"
|
||||
:theme="theme"
|
||||
:selectedThemeID="selectedThemeID"
|
||||
@clicked="themeClick(theme.id, theme.name)"
|
||||
@apply="applyButton(theme.id)"
|
||||
@edit="editButton(theme.id)"
|
||||
@makePublic="makePublicButton(theme.id)"
|
||||
@delete="deleteButton(theme.id)"
|
||||
:key="theme.id"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// import "prismjs";
|
||||
// import "prismjs/themes/prism-twilight.css";
|
||||
// const PrismEditor = () => import("vue-prism-editor");
|
||||
// language js
|
||||
import "codemirror/mode/css/css.js";
|
||||
|
||||
import "codemirror/addon/hint/show-hint.css";
|
||||
import "codemirror/addon/hint/show-hint.js";
|
||||
import "codemirror/addon/hint/css-hint";
|
||||
// theme css
|
||||
import "codemirror/theme/base16-dark.css";
|
||||
import "codemirror/lib/codemirror.css";
|
||||
import { codemirror } from "vue-codemirror";
|
||||
|
||||
import ThemeTemplate from "./MyThemeTemplate";
|
||||
import Editor from "./themesEditor";
|
||||
import MakePublic from "./MyThemesMakePublic";
|
||||
import Spinner from "@/components/Spinner";
|
||||
|
||||
import config from "@/config.js";
|
||||
import ThemeService from "@/services/ThemeService";
|
||||
export default {
|
||||
components: {
|
||||
codemirror,
|
||||
Spinner
|
||||
Spinner,
|
||||
Editor,
|
||||
ThemeTemplate,
|
||||
MakePublic,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
name: "",
|
||||
code: "",
|
||||
saving: false,
|
||||
selectedThemeID: null,
|
||||
themes: null,
|
||||
editing: null,
|
||||
name: "",
|
||||
code: `/* Start writing your styles*/\n`,
|
||||
cmOptions: {
|
||||
// codemirror options
|
||||
tabSize: 2,
|
||||
mode: "text/css",
|
||||
theme: "base16-dark",
|
||||
lineNumbers: true,
|
||||
line: true
|
||||
}
|
||||
showMakePublic: false,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
async fetchThemes() {
|
||||
this.themes = null;
|
||||
// fetch themes
|
||||
const {ok, result, error} = await ThemeService.getThemes();
|
||||
const { ok, result, error } = await ThemeService.getThemes();
|
||||
if (ok) {
|
||||
this.themes = result.data;
|
||||
}
|
||||
},
|
||||
createButton() {
|
||||
this.name = "Untitled"
|
||||
this.name = "Untitled";
|
||||
this.code = `/* Start writing your styles*/\n`;
|
||||
this.editing = true;
|
||||
},
|
||||
closeButton() {
|
||||
this.showMakePublic = false;
|
||||
this.editing = false;
|
||||
this.fetchThemes();
|
||||
},
|
||||
themeClick(id) {
|
||||
themeClick(id, name) {
|
||||
if (this.selectedThemeID === id) {
|
||||
this.selectedThemeID = null;
|
||||
this.name = null;
|
||||
} else {
|
||||
this.selectedThemeID = id;
|
||||
this.name = name;
|
||||
}
|
||||
},
|
||||
onCmReady(cm) {
|
||||
cm.on("keypress", () => {
|
||||
cm.showHint({ completeSingle: false });
|
||||
});
|
||||
},
|
||||
async saveButton() {
|
||||
if (this.saving) {return}
|
||||
async saveButton({ name, css }) {
|
||||
if (this.saving) {
|
||||
return;
|
||||
}
|
||||
this.saving = true;
|
||||
const css = this.code;
|
||||
const name = this.name;
|
||||
if (typeof this.editing === 'string'){
|
||||
this.name = name;
|
||||
if (typeof this.editing === "string") {
|
||||
const response = await ThemeService.update({ name, css }, this.editing);
|
||||
this.applyButton(this.editing)
|
||||
this.applyButton(this.editing);
|
||||
} else {
|
||||
const response = await ThemeService.save({ name, css });
|
||||
this.editing = response.result.data.id;
|
||||
this.applyButton(response.result.data.id)
|
||||
this.applyButton(response.result.data.id, css);
|
||||
}
|
||||
this.saving = false;
|
||||
},
|
||||
async applyButton(id) {
|
||||
const {ok, result, error} = await ThemeService.getTheme(id);
|
||||
if (ok) {
|
||||
this.code = result.data.css;
|
||||
async applyButton(id, css) {
|
||||
if (css) {
|
||||
this.code = css;
|
||||
} else {
|
||||
const { ok, result, error } = await ThemeService.getTheme(id);
|
||||
if (ok) {
|
||||
this.code = result.data.css;
|
||||
}
|
||||
}
|
||||
// save to local storage.
|
||||
localStorage.setItem('appliedThemeId', id);
|
||||
localStorage.setItem("appliedThemeId", id);
|
||||
|
||||
const styleEl = document.createElement("style");
|
||||
styleEl.classList.add('theme-' + id);
|
||||
styleEl.classList.add("theme-" + id);
|
||||
styleEl.id = "theme";
|
||||
styleEl.innerHTML = this.code;
|
||||
|
||||
|
|
@ -179,25 +134,28 @@ export default {
|
|||
},
|
||||
async editButton(id) {
|
||||
// fetch theme
|
||||
const {ok, result, error} = await ThemeService.getTheme(id);
|
||||
const { ok, result, error } = await ThemeService.getTheme(id);
|
||||
if (ok) {
|
||||
const {name, css} = result.data;
|
||||
const { name, css } = result.data;
|
||||
this.name = name;
|
||||
this.code = css;
|
||||
this.editing = id;
|
||||
}
|
||||
},
|
||||
async deleteButton (id) {
|
||||
localStorage.removeItem('appliedThemeId');
|
||||
const themeEl = document.getElementById('theme');
|
||||
if (themeEl && themeEl.classList.contains('theme-' + id)) {
|
||||
document.getElementById('theme').outerHTML = ''
|
||||
async deleteButton(id) {
|
||||
localStorage.removeItem("appliedThemeId");
|
||||
const themeEl = document.getElementById("theme");
|
||||
if (themeEl && themeEl.classList.contains("theme-" + id)) {
|
||||
document.getElementById("theme").outerHTML = "";
|
||||
}
|
||||
const {ok, result, error} = await ThemeService.delete(id);
|
||||
const { ok, result, error } = await ThemeService.delete(id);
|
||||
if (ok) {
|
||||
this.themes = this.themes.filter(t => t.id !== id);
|
||||
}
|
||||
},
|
||||
makePublicButton() {
|
||||
this.showMakePublic = true;
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
this.fetchThemes();
|
||||
|
|
@ -245,102 +203,8 @@ export default {
|
|||
}
|
||||
}
|
||||
}
|
||||
.editing {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
.theme-name {
|
||||
height: 20px;
|
||||
margin: 0;
|
||||
}
|
||||
.editor {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
.notice {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
align-content: center;
|
||||
background: rgb(255, 54, 54);
|
||||
height: 40px;
|
||||
flex-shrink: 0;
|
||||
padding-left: 5px;
|
||||
.notice-message {
|
||||
margin-left: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.themes-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
.theme {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
widows: 100%;
|
||||
min-height: 30px;
|
||||
flex-shrink: 0;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
color: rgb(230, 230, 230);
|
||||
transition: 0.2s;
|
||||
.name {
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
height: 100%;
|
||||
min-height: 30px;
|
||||
padding-left: 5px;
|
||||
flex: 1;
|
||||
align-items: center;
|
||||
transition: 0.2s;
|
||||
&:hover {
|
||||
color: white;
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
&.selected {
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
}
|
||||
.context {
|
||||
display: flex;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
.button {
|
||||
display: flex;
|
||||
margin: 5px;
|
||||
align-content: center;
|
||||
align-items: center;
|
||||
opacity: 0.8;
|
||||
transition: 0.2s;
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
.delete-button {
|
||||
margin: auto;
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
.CodeMirror-hints {
|
||||
background-color: #33332f !important;
|
||||
border-color: #232321 !important;
|
||||
}
|
||||
.CodeMirror-hint {
|
||||
color: #666 !important;
|
||||
}
|
||||
.CodeMirror-hint-active {
|
||||
background-color: transparent !important;
|
||||
color: #6cb5d9 !important;
|
||||
}
|
||||
.CodeMirror {
|
||||
height: 100% !important;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,300 @@
|
|||
<template>
|
||||
<div class="make-public">
|
||||
<Spinner v-if="details === null" />
|
||||
<div v-else class="main-container" ref="scroll">
|
||||
<div class="notice">
|
||||
<strong>Note:</strong> Making your theme public requires reviewing. This
|
||||
may take some days to review.
|
||||
</div>
|
||||
<ErrorList v-if="errors" :errors="errors" />
|
||||
<div class="container">
|
||||
<div class="block">
|
||||
Theme Status
|
||||
<div v-if="details === false"><strong>Not published</strong></div>
|
||||
<div class="warn" v-else-if="details.updatedCss"><strong>Update Getting Reviewed.</strong></div>
|
||||
<div class="warn" v-else-if="details.approved === false"><strong>Getting Reviewed.</strong></div>
|
||||
<div class="valid" v-else><strong>Theme is public!</strong></div>
|
||||
</div>
|
||||
<div class="block">
|
||||
Name:
|
||||
<div>
|
||||
<strong>{{ name }}</strong>
|
||||
</div>
|
||||
</div>
|
||||
<div class="block">
|
||||
Theme Preview
|
||||
<div
|
||||
class="preview"
|
||||
:style="{
|
||||
backgroundImage: `url(${update.screenshot || (details ? `${screenshotDomain}${details.screenshot}` : '')})`
|
||||
}"
|
||||
/>
|
||||
<div class="button" @click="$refs.screenshotBrowser.click()">
|
||||
Upload
|
||||
</div>
|
||||
<input
|
||||
ref="screenshotBrowser"
|
||||
type="file"
|
||||
accept=".jpeg, .jpg, .png, .gif"
|
||||
class="hidden"
|
||||
@change="screenshotBrowseChange"
|
||||
/>
|
||||
</div>
|
||||
<div class="input">
|
||||
<div class="title" v-if="update.description === undefined && !details">
|
||||
Description (0/150)
|
||||
</div>
|
||||
<div class="title" v-if="update.description !== undefined">
|
||||
Description (<span :class="{ warn: update.description.length > 150}">{{
|
||||
update.description.length
|
||||
}}</span
|
||||
>/150)
|
||||
</div>
|
||||
<div class="title" v-if="update.description === undefined && details">
|
||||
Description (<span :class="{ warn: details.description.length > 150}">{{
|
||||
details.description.length
|
||||
}}</span
|
||||
>/150)
|
||||
</div>
|
||||
<textarea placeholder="Description" :default-value.prop="details.description || ''" @input="descriptionInput" ></textarea>
|
||||
</div>
|
||||
<div class="buttons">
|
||||
<div class="button" @click="backButton">Back</div>
|
||||
<div class="button" v-if="details" @click="updateButton">{{submitClicked ? 'Updating...' : 'Update'}}</div>
|
||||
<div class="button" @click="sendForReviewButton" v-else>{{submitClicked ? 'Sending...' : 'Send For Review'}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Spinner from "@/components/Spinner";
|
||||
import ErrorList from '@/components/app/errorsListTemplate';
|
||||
import path from "path";
|
||||
import config from "@/config.js";
|
||||
import exploreService from "@/services/exploreService";
|
||||
export default {
|
||||
components: { Spinner, ErrorList },
|
||||
props: ["themeID", "name"],
|
||||
data() {
|
||||
return {
|
||||
update: {},
|
||||
errors: null,
|
||||
success: null,
|
||||
details: null,
|
||||
screenshotDomain: config.domain + "/media/",
|
||||
submitClicked: false,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
async updateButton() {
|
||||
this.errors = null;
|
||||
if (this.submitClicked === true) return;
|
||||
this.submitClicked = true;
|
||||
|
||||
const {result, ok, error} = await exploreService.updateTheme(this.themeID, this.update);
|
||||
if (!ok) {
|
||||
if (error.response.data.message) {
|
||||
this.errors = [{msg: error.response.data.message}];
|
||||
} else {
|
||||
this.errors = error.response.data.errors;
|
||||
}
|
||||
} else {
|
||||
this.details = result.data;
|
||||
}
|
||||
this.submitClicked = false;
|
||||
this.$refs.scroll.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
|
||||
},
|
||||
async sendForReviewButton() {
|
||||
this.errors = null;
|
||||
if (this.submitClicked === true) return;
|
||||
this.submitClicked = true;
|
||||
|
||||
const {result, ok, error} = await exploreService.addTheme(this.themeID, this.update);
|
||||
if (!ok) {
|
||||
if (error.response.data.message) {
|
||||
this.errors = [{msg: error.response.data.message}];
|
||||
} else {
|
||||
this.errors = error.response.data.errors;
|
||||
}
|
||||
} else {
|
||||
this.details = result.data;
|
||||
}
|
||||
this.submitClicked = false;
|
||||
this.$refs.scroll.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
|
||||
},
|
||||
screenshotBrowseChange(event) {
|
||||
if (!this.googleDriveLinked) {
|
||||
event.target.value = "";
|
||||
return this.$store.dispatch("setPopoutVisibility", {
|
||||
name: "GDLinkMenu",
|
||||
visibility: true
|
||||
});
|
||||
}
|
||||
const file = event.target.files[0];
|
||||
|
||||
const _this = this;
|
||||
const maxSize = 2092000;
|
||||
if (file.size > maxSize) {
|
||||
return this.$store.dispatch(
|
||||
"setGenericMessage",
|
||||
"Image is larger than 2MB"
|
||||
);
|
||||
}
|
||||
event.target.value = "";
|
||||
const allowedFormats = [".png", ".jpeg", ".gif", ".jpg"];
|
||||
if (!allowedFormats.includes(path.extname(file.name).toLowerCase())) {
|
||||
return this.$store.dispatch(
|
||||
"setGenericMessage",
|
||||
"That file format is not allowed!"
|
||||
);
|
||||
}
|
||||
let reader = new FileReader();
|
||||
reader.readAsDataURL(file);
|
||||
|
||||
reader.onload = function() {
|
||||
_this.$set(_this.update, "screenshot", reader.result);
|
||||
};
|
||||
reader.onerror = function(error) {
|
||||
console.log("Error: ", error);
|
||||
return this.$store.dispatch(
|
||||
"setGenericMessage",
|
||||
"Something went wrong. Try again later."
|
||||
);
|
||||
};
|
||||
},
|
||||
backButton() {
|
||||
this.$emit("back");
|
||||
},
|
||||
async fetchDetails() {
|
||||
const {ok, error, result} = await exploreService.getTheme(this.themeID);
|
||||
if (!ok && !error.response) {
|
||||
return;
|
||||
}
|
||||
if (error){
|
||||
this.details = false;
|
||||
return;
|
||||
}
|
||||
this.details = result.data;
|
||||
},
|
||||
descriptionInput(event) {
|
||||
this.$set(this.update, 'description', event.target.value);
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
this.fetchDetails();
|
||||
},
|
||||
computed: {
|
||||
googleDriveLinked() {
|
||||
return this.$store.getters["settingsModule/settings"].GDriveLinked;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.make-public {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
}
|
||||
.main-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
overflow: auto;
|
||||
}
|
||||
.notice {
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
padding: 5px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
padding-top: 5px;
|
||||
}
|
||||
|
||||
.input {
|
||||
background-color: #044050;
|
||||
padding: 10px;
|
||||
margin: 5px;
|
||||
align-self: flex-start;
|
||||
height: 155px;
|
||||
width: 300px;
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
flex-direction: column;
|
||||
.title {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
textarea {
|
||||
background: #032d38;
|
||||
resize: none;
|
||||
outline: none;
|
||||
margin-top: 2px;
|
||||
margin-bottom: 0;
|
||||
padding: 10px;
|
||||
border: none;
|
||||
color: white;
|
||||
flex: 1;
|
||||
}
|
||||
.warn {
|
||||
color: rgb(255, 67, 67);
|
||||
}
|
||||
}
|
||||
.block {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: #044050;
|
||||
max-width: 300px;
|
||||
margin: 5px;
|
||||
padding: 10px;
|
||||
}
|
||||
.buttons {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
margin-bottom: 10px;
|
||||
align-self: center;
|
||||
}
|
||||
.button {
|
||||
user-select: none;
|
||||
cursor: pointer;
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
padding: 10px;
|
||||
align-self: center;
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
transition: 0.2s;
|
||||
&:hover {
|
||||
background: rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
}
|
||||
.preview {
|
||||
height: 150px;
|
||||
width: 300px;
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
flex-shrink: 0;
|
||||
margin-top: 5px;
|
||||
margin-bottom: 5px;
|
||||
background-position: center;
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
.valid {
|
||||
color: green;
|
||||
}
|
||||
.warn {
|
||||
color: orange;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,162 @@
|
|||
<template>
|
||||
<div class="editing">
|
||||
<div class="bar">
|
||||
<input
|
||||
class="theme-name"
|
||||
v-model="name"
|
||||
type="text"
|
||||
placeholder="Theme name"
|
||||
/>
|
||||
<div class="button" @click="saveButton">
|
||||
<div class="material-icons">save</div>
|
||||
{{savingStatus ? 'Saving...' : 'Save & Apply'}}
|
||||
</div>
|
||||
<div class="button" @click="closeButton">
|
||||
<div class="material-icons">clear</div>
|
||||
Close
|
||||
</div>
|
||||
</div>
|
||||
<!-- <prism-editor class="editor" v-model="code" language="css"></prism-editor> -->
|
||||
<codemirror
|
||||
class="editor"
|
||||
v-model="code"
|
||||
:options="cmOptions"
|
||||
@ready="onCmReady"
|
||||
/>
|
||||
<div class="notice">
|
||||
<div class="material-icons">warning</div>
|
||||
<div class="notice-message">
|
||||
Warning: Pasting someone elses code could be dangerous.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import "codemirror/mode/css/css.js";
|
||||
|
||||
import "codemirror/addon/hint/show-hint.css";
|
||||
import "codemirror/addon/hint/show-hint.js";
|
||||
import "codemirror/addon/hint/css-hint";
|
||||
// theme css
|
||||
import "codemirror/theme/base16-dark.css";
|
||||
import "codemirror/lib/codemirror.css";
|
||||
import { codemirror } from "vue-codemirror";
|
||||
|
||||
export default {
|
||||
components: { codemirror },
|
||||
props: ['themeName', 'themeCode', 'savingStatus'],
|
||||
data() {
|
||||
return {
|
||||
name: '',
|
||||
code: `/* Start writing your styles*/\n`,
|
||||
cmOptions: {
|
||||
// codemirror options
|
||||
tabSize: 2,
|
||||
mode: "text/css",
|
||||
theme: "base16-dark",
|
||||
lineNumbers: true,
|
||||
line: true
|
||||
}
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
if (this.themeName) {
|
||||
this.name = this.themeName;
|
||||
}
|
||||
if (this.themeCode) {
|
||||
this.code = this.themeCode
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onCmReady(cm) {
|
||||
cm.on("keypress", () => {
|
||||
cm.showHint({ completeSingle: false });
|
||||
});
|
||||
},
|
||||
saveButton() {
|
||||
this.$emit("save", {name: this.name, css: this.code});
|
||||
},
|
||||
closeButton() {
|
||||
this.$emit("close");
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.bar {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
height: 40px;
|
||||
flex-shrink: 0;
|
||||
.button {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
align-content: center;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
user-select: none;
|
||||
cursor: pointer;
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
&.end {
|
||||
margin: auto;
|
||||
margin-right: 0;
|
||||
}
|
||||
&:hover {
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
.material-icons {
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.editing {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
.theme-name {
|
||||
height: 20px;
|
||||
margin: 0;
|
||||
}
|
||||
.editor {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
.notice {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
align-content: center;
|
||||
background: rgb(255, 54, 54);
|
||||
height: 40px;
|
||||
flex-shrink: 0;
|
||||
padding-left: 5px;
|
||||
.notice-message {
|
||||
margin-left: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
.CodeMirror-hints {
|
||||
background-color: #33332f !important;
|
||||
border-color: #232321 !important;
|
||||
}
|
||||
.CodeMirror-hint {
|
||||
color: #666 !important;
|
||||
}
|
||||
.CodeMirror-hint-active {
|
||||
background-color: transparent !important;
|
||||
color: #6cb5d9 !important;
|
||||
}
|
||||
.CodeMirror {
|
||||
height: 100% !important;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -104,6 +104,7 @@ export default {
|
|||
left: 0;
|
||||
width: 3px;
|
||||
background: #ffffff5e;
|
||||
transition: 0.2s;
|
||||
}
|
||||
&.selected::before {
|
||||
content: "";
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@
|
|||
<i class="material-icons">{{tabs[selectedTab].icon}}</i>
|
||||
{{tabs[selectedTab].name}}
|
||||
</div>
|
||||
<div class="coming-soon" v-if="selectedTab > 0">
|
||||
<div class="coming-soon" v-if="selectedTab > 1">
|
||||
<div class="icon">
|
||||
<i class="material-icons">explore</i>
|
||||
</div>
|
||||
|
|
@ -72,20 +72,21 @@
|
|||
<script>
|
||||
import { bus } from "@/main";
|
||||
import Servers from "./Explore/servers";
|
||||
import Themes from "./Explore/themes";
|
||||
import ServerService from "@/services/ServerService";
|
||||
import Navigation from "@/components/app/Navigation";
|
||||
import MyMiniInformation from "@/components/app/MyMiniInformation.vue";
|
||||
export default {
|
||||
components: { Servers, Navigation, MyMiniInformation },
|
||||
components: { Servers, Themes, Navigation, MyMiniInformation },
|
||||
data() {
|
||||
return {
|
||||
showLeftPanel: false,
|
||||
selectedTab: 0,
|
||||
tabs: [
|
||||
// {icon: "home", name: "home", component: ""},
|
||||
{ icon: "rss_feed", name: "Servers", component: "servers" },
|
||||
{ icon: "rss_feed", name: "Servers", component: "Servers" },
|
||||
{ icon: "brush", name: "Themes", component: "Themes" },
|
||||
{ icon: "face", name: "Emoji Packs", component: "" },
|
||||
{ icon: "brush", name: "Themes", component: "" },
|
||||
{ icon: "message", name: "Message Styles", component: "" }
|
||||
],
|
||||
nertiviaServerID: "6572915451527958528",
|
||||
|
|
@ -168,6 +169,7 @@ export default {
|
|||
.material-icons {
|
||||
margin-right: 5px;
|
||||
}
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-content: center;
|
||||
align-items: center;
|
||||
|
|
@ -183,6 +185,15 @@ export default {
|
|||
background: #053240;
|
||||
}
|
||||
}
|
||||
.item:nth-child(2)::before {
|
||||
content: 'NEW';
|
||||
font-size: 14px;
|
||||
background: rgb(255, 55, 55);
|
||||
border-radius: 2px;
|
||||
padding: 2px;
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
}
|
||||
}
|
||||
.header {
|
||||
display: flex;
|
||||
|
|
|
|||
87
src/components/app/Tabs/Explore/themes.vue
Normal file
87
src/components/app/Tabs/Explore/themes.vue
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
<template>
|
||||
<div class="themes-tab">
|
||||
<div class="items-main-container">
|
||||
<div class="note">Themes below are safe and manually reviewed.</div>
|
||||
<div class="items-container">
|
||||
<spinner class="spinner" v-if="!publicThemes" :size="80"/>
|
||||
<div class="items">
|
||||
<theme-template v-for="theme in publicThemes" :key="theme.id" :theme="theme"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import searchHeader from "./searchHeader";
|
||||
import themeTemplate from './themesTemplate';
|
||||
import exploreService from '@/services/exploreService';
|
||||
import Spinner from "@/components/Spinner";
|
||||
|
||||
export default {
|
||||
components: { searchHeader, themeTemplate, Spinner },
|
||||
data() {
|
||||
return {
|
||||
publicThemes: null,
|
||||
params: '?verified=true'
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async getThemesList() {
|
||||
this.publicThemes = null;
|
||||
const {ok, result, error} = await exploreService.getThemes();
|
||||
if (ok) {
|
||||
this.publicThemes = result.data;
|
||||
}
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.getThemesList();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.themes-tab {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
.items-main-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: auto;
|
||||
|
||||
|
||||
}
|
||||
.items-container {
|
||||
display: block;
|
||||
align-content: center;
|
||||
align-items: center;
|
||||
max-width: 1066px;
|
||||
margin: auto;
|
||||
width: 100%;
|
||||
.items {
|
||||
margin-top: 40px;
|
||||
display: grid;
|
||||
grid-gap: 16px;
|
||||
grid-template-columns: repeat(auto-fill, 250px);
|
||||
grid-auto-rows: min-content;
|
||||
align-items: stretch;
|
||||
justify-content: space-evenly;
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
.spinner {
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.note {
|
||||
background: rgba(52, 116, 255, 0.658);
|
||||
padding: 5px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
</style>
|
||||
238
src/components/app/Tabs/Explore/themesTemplate.vue
Normal file
238
src/components/app/Tabs/Explore/themesTemplate.vue
Normal file
|
|
@ -0,0 +1,238 @@
|
|||
<template>
|
||||
<div class="item">
|
||||
<div class="top">
|
||||
<div
|
||||
@click="bannerImageClicked"
|
||||
class="background-dark"
|
||||
:style="{backgroundImage: `url(${bannerDomain + theme.screenshot}${'?type=png'})`}"
|
||||
/>
|
||||
</div>
|
||||
<div class="bottom">
|
||||
<div class="name">
|
||||
<div class="name-container">
|
||||
<span class="inner-name">{{theme.theme.name}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="description">{{theme.description}}</div>
|
||||
<div class="buttons">
|
||||
<!-- <div class="stars-button">
|
||||
<div class="material-icons">star</div>
|
||||
{{theme.stars}}
|
||||
</div> -->
|
||||
<div class="button un-apply" v-if="appliedTheme === theme.id" @click="unApplyButton">
|
||||
<span>Unapply</span>
|
||||
</div>
|
||||
<div v-else class="button" @click="applyButton">
|
||||
<span>Apply</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { bus } from "@/main";
|
||||
import Spinner from "@/components/Spinner";
|
||||
import config from "@/config.js";
|
||||
import exploreService from '@/services/exploreService';
|
||||
export default {
|
||||
props: ["theme"],
|
||||
data() {
|
||||
return {
|
||||
joinClicked: false,
|
||||
bannerDomain: config.domain + "/media/",
|
||||
appliedTheme: null,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
async applyButton() {
|
||||
// get css
|
||||
const {ok, error, result} = await exploreService.applyTheme(this.theme.id);
|
||||
if (ok) {
|
||||
const css = result.data.css;
|
||||
const id = result.data.id;
|
||||
// save to local storage.
|
||||
localStorage.setItem("appliedThemeId", id);
|
||||
|
||||
const styleEl = document.createElement("style");
|
||||
styleEl.classList.add("theme-" + id);
|
||||
styleEl.id = "theme";
|
||||
styleEl.innerHTML = css;
|
||||
|
||||
const currentStyle = document.getElementById("theme");
|
||||
if (currentStyle) {
|
||||
currentStyle.outerHTML = styleEl.outerHTML;
|
||||
} else {
|
||||
document.head.innerHTML += styleEl.outerHTML;
|
||||
}
|
||||
this.appliedTheme = id;
|
||||
}
|
||||
},
|
||||
bannerImageClicked() {
|
||||
this.$store.dispatch(
|
||||
"setImagePreviewURL",
|
||||
this.bannerDomain + this.theme.screenshot + '?type=png'
|
||||
);
|
||||
},
|
||||
unApplyButton () {
|
||||
this.appliedTheme = null;
|
||||
localStorage.removeItem("appliedThemeId");
|
||||
document.getElementById("theme").outerHTML = ''
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.appliedTheme = localStorage.getItem("appliedThemeId");
|
||||
},
|
||||
computed: {
|
||||
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.item {
|
||||
position: relative;
|
||||
width: 250px;
|
||||
height: 350px;
|
||||
background: #024253;
|
||||
opacity: 0.9;
|
||||
margin: 5px;
|
||||
border-radius: 4px;
|
||||
flex-shrink: 0;
|
||||
transition: 0.3s;
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
.top {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
height: 150px;
|
||||
justify-content: center;
|
||||
align-content: center;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
position: relative;
|
||||
.background-dark {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
background-position: center;
|
||||
background-size: cover;
|
||||
cursor: pointer;
|
||||
}
|
||||
.avatar {
|
||||
background: rgb(26, 133, 255);
|
||||
height: 80px;
|
||||
width: 80px;
|
||||
border-radius: 50%;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
.bottom {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: #04333F;
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
flex-shrink: 0;
|
||||
.name {
|
||||
margin-top: 9px;
|
||||
font-size: 21px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
z-index: 999;
|
||||
.name-container {
|
||||
display: flex;
|
||||
margin: auto;
|
||||
}
|
||||
.inner-name {
|
||||
max-width: 200px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.material-icons {
|
||||
color: #66e0ff;
|
||||
margin-left: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.description {
|
||||
margin: 10px;
|
||||
flex: 1;
|
||||
opacity: 0.9;
|
||||
overflow: auto;
|
||||
font-size: 14px;
|
||||
word-break: break-word;
|
||||
white-space: pre-wrap;
|
||||
overflow-wrap: anywhere;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
flex-direction: row;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.stars-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
align-content: center;
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
margin-left: 10px;
|
||||
margin-top: 0px;
|
||||
border-radius: 4px;
|
||||
margin-right: 10px;
|
||||
transition: 0.2s;
|
||||
flex: 1;
|
||||
cursor: pointer;
|
||||
background: #ff501bd7;
|
||||
&:hover {
|
||||
background: #ff501b;
|
||||
}
|
||||
.material-icons {
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
.button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
border-radius: 4px;
|
||||
background: rgba(0, 179, 219, 0.8);
|
||||
transition: 0.2s;
|
||||
margin-right: 10px;
|
||||
margin-left: 10px;
|
||||
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background: #00B4DB;
|
||||
}
|
||||
&.selected {
|
||||
background: grey;
|
||||
}
|
||||
&.un-apply {
|
||||
background: rgba(255, 53, 53, 0.808);
|
||||
&:hover {
|
||||
background:rgb(255, 53, 53);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
import {instance, wrapper} from './Api';
|
||||
|
||||
export default {
|
||||
// servers
|
||||
getServersList (params) {
|
||||
return wrapper(instance().get(`explore/servers${params || ''}`))
|
||||
},
|
||||
|
|
@ -17,4 +18,22 @@ export default {
|
|||
return wrapper(instance().post(`explore/servers`, data))
|
||||
},
|
||||
|
||||
// themes
|
||||
getThemes() {
|
||||
return wrapper(instance().get(`explore/themes`));
|
||||
},
|
||||
getTheme(id) {
|
||||
return wrapper(instance().get(`explore/themes/${id}`));
|
||||
},
|
||||
addTheme(id, data) {
|
||||
return wrapper(instance().post(`explore/themes/${id}`, data))
|
||||
},
|
||||
updateTheme(id, data) {
|
||||
return wrapper(instance().patch(`explore/themes/${id}`, data))
|
||||
},
|
||||
applyTheme(id) {
|
||||
return wrapper(instance().get(`explore/themes/${id}/apply`));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -9,12 +9,22 @@ const prototype = {
|
|||
};
|
||||
|
||||
const config = [
|
||||
{
|
||||
version: 8.9,
|
||||
title: "Public Themes!",
|
||||
shortTitle: "",
|
||||
date: "7/21/2019",
|
||||
headColor: "#007792",
|
||||
new: [
|
||||
"You can now make your themes public! Note: Making your theme public will require reviewing which may take few hours or days.",
|
||||
],
|
||||
|
||||
},
|
||||
{
|
||||
version: 8.8,
|
||||
title: "Themes!",
|
||||
shortTitle: "",
|
||||
date: "29/11/2019",
|
||||
headColor: "#007792",
|
||||
new: [
|
||||
"You can now create your own themes in the settings popout using css! (You cannot share themes for now, that feature is coming soon though!)",
|
||||
],
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ import ConnectingScreen from "./../components/app/ConnectingScreen.vue";
|
|||
import Spinner from "./../components/Spinner.vue";
|
||||
import MainNav from "./../components/app/MainNav.vue";
|
||||
import ThemeService from '../services/ThemeService';
|
||||
import ExploreService from '../services/exploreService';
|
||||
|
||||
const ElectronFrameButtons = () =>
|
||||
import("@/components/ElectronJS/FrameButtons.vue");
|
||||
|
|
@ -128,12 +129,26 @@ export default {
|
|||
|
||||
|
||||
if (!themeAppliedID) {return;}
|
||||
const {ok, result, error} = await ThemeService.getTheme(themeAppliedID);
|
||||
if (!ok) { return; }
|
||||
let exploreThemes = await ExploreService.applyTheme(themeAppliedID);
|
||||
let privateThemes;
|
||||
if (!exploreThemes.ok) {
|
||||
privateThemes = await ThemeService.getTheme(themeAppliedID);
|
||||
}
|
||||
let id;
|
||||
let css;
|
||||
if (exploreThemes.ok) {
|
||||
css = exploreThemes.result.data.css;
|
||||
id = exploreThemes.result.data.id;
|
||||
}
|
||||
if (privateThemes) {
|
||||
css = privateThemes.result.data.css;
|
||||
id = privateThemes.result.data.id;
|
||||
}
|
||||
if (!id && !css) {return}
|
||||
const styleEl = document.createElement('style');
|
||||
styleEl.id = "theme"
|
||||
styleEl.classList.add('theme-' + result.data.id)
|
||||
styleEl.innerHTML = result.data.css
|
||||
styleEl.classList.add('theme-' + id)
|
||||
styleEl.innerHTML = css
|
||||
document.head.innerHTML += styleEl.outerHTML;
|
||||
}
|
||||
},
|
||||
|
|
|
|||
Loading…
Reference in a new issue