added public theme service

This commit is contained in:
supertiger1234 2019-12-07 11:28:24 +00:00
parent c827a425ba
commit bce31bb9dd
12 changed files with 1023 additions and 213 deletions

View file

@ -166,9 +166,6 @@ export default {
this.changed = true;
}
},
mounted() {
console.log(this.server);
},
computed: {
googleDriveLinked() {
return this.$store.getters["settingsModule/settings"].GDriveLinked;

View file

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

View file

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

View file

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

View file

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

View file

@ -104,6 +104,7 @@ export default {
left: 0;
width: 3px;
background: #ffffff5e;
transition: 0.2s;
}
&.selected::before {
content: "";

View file

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

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

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

View file

@ -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`));
}
}

View file

@ -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!)",
],

View file

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