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; this.changed = true;
} }
}, },
mounted() {
console.log(this.server);
},
computed: { computed: {
googleDriveLinked() { googleDriveLinked() {
return this.$store.getters["settingsModule/settings"].GDriveLinked; 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> <template>
<div class="container"> <div class="container">
<div class="editing" v-if="editing"> <make-public v-if="showMakePublic" @back="closeButton" :name="name" :themeID="selectedThemeID" />
<div class="bar"> <Editor
<input v-else-if="editing"
class="theme-name" :themeName="name"
v-model="name" :themeCode="code"
type="text" :savingStatus="saving"
placeholder="Theme name" @close="closeButton"
/> @save="saveButton"
<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>
<div class="managing" v-else> <div class="managing" v-else>
<div class="bar"> <div class="bar">
<div class="button"> <div class="button">
@ -42,131 +22,106 @@
</div> </div>
<spinner v-if="!themes" /> <spinner v-if="!themes" />
<div v-if="themes" class="themes-list"> <div v-if="themes" class="themes-list">
<div class="theme" v-for="theme in themes" :key="theme.id"> <theme-template
<div v-for="theme in themes"
class="name" :theme="theme"
:class="{ selected: selectedThemeID === theme.id }" :selectedThemeID="selectedThemeID"
@click="themeClick(theme.id)" @clicked="themeClick(theme.id, theme.name)"
> @apply="applyButton(theme.id)"
{{ theme.name }} @edit="editButton(theme.id)"
</div> @makePublic="makePublicButton(theme.id)"
<div class="context" v-if="theme.id == selectedThemeID"> @delete="deleteButton(theme.id)"
<div class="button" @click="applyButton(theme.id)"> :key="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>
</div> </div>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
// import "prismjs"; import ThemeTemplate from "./MyThemeTemplate";
// import "prismjs/themes/prism-twilight.css"; import Editor from "./themesEditor";
// const PrismEditor = () => import("vue-prism-editor"); import MakePublic from "./MyThemesMakePublic";
// 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 Spinner from "@/components/Spinner"; import Spinner from "@/components/Spinner";
import config from "@/config.js"; import config from "@/config.js";
import ThemeService from "@/services/ThemeService"; import ThemeService from "@/services/ThemeService";
export default { export default {
components: { components: {
codemirror, Spinner,
Spinner Editor,
ThemeTemplate,
MakePublic,
}, },
data() { data() {
return { return {
name: "",
code: "",
saving: false, saving: false,
selectedThemeID: null, selectedThemeID: null,
themes: null, themes: null,
editing: null, editing: null,
name: "", showMakePublic: false,
code: `/* Start writing your styles*/\n`,
cmOptions: {
// codemirror options
tabSize: 2,
mode: "text/css",
theme: "base16-dark",
lineNumbers: true,
line: true
}
}; };
}, },
methods: { methods: {
async fetchThemes() { async fetchThemes() {
this.themes = null; this.themes = null;
// fetch themes // fetch themes
const {ok, result, error} = await ThemeService.getThemes(); const { ok, result, error } = await ThemeService.getThemes();
if (ok) { if (ok) {
this.themes = result.data; this.themes = result.data;
} }
}, },
createButton() { createButton() {
this.name = "Untitled" this.name = "Untitled";
this.code = `/* Start writing your styles*/\n`; this.code = `/* Start writing your styles*/\n`;
this.editing = true; this.editing = true;
}, },
closeButton() { closeButton() {
this.showMakePublic = false;
this.editing = false; this.editing = false;
this.fetchThemes(); this.fetchThemes();
}, },
themeClick(id) { themeClick(id, name) {
if (this.selectedThemeID === id) { if (this.selectedThemeID === id) {
this.selectedThemeID = null; this.selectedThemeID = null;
this.name = null;
} else { } else {
this.selectedThemeID = id; this.selectedThemeID = id;
this.name = name;
} }
}, },
onCmReady(cm) { async saveButton({ name, css }) {
cm.on("keypress", () => { if (this.saving) {
cm.showHint({ completeSingle: false }); return;
}); }
},
async saveButton() {
if (this.saving) {return}
this.saving = true; this.saving = true;
const css = this.code; this.name = name;
const name = this.name; if (typeof this.editing === "string") {
if (typeof this.editing === 'string'){
const response = await ThemeService.update({ name, css }, this.editing); const response = await ThemeService.update({ name, css }, this.editing);
this.applyButton(this.editing) this.applyButton(this.editing);
} else { } else {
const response = await ThemeService.save({ name, css }); const response = await ThemeService.save({ name, css });
this.editing = response.result.data.id; this.editing = response.result.data.id;
this.applyButton(response.result.data.id) this.applyButton(response.result.data.id, css);
} }
this.saving = false; this.saving = false;
}, },
async applyButton(id) { async applyButton(id, css) {
const {ok, result, error} = await ThemeService.getTheme(id); if (css) {
if (ok) { this.code = css;
this.code = result.data.css; } else {
const { ok, result, error } = await ThemeService.getTheme(id);
if (ok) {
this.code = result.data.css;
}
} }
// save to local storage. // save to local storage.
localStorage.setItem('appliedThemeId', id); localStorage.setItem("appliedThemeId", id);
const styleEl = document.createElement("style"); const styleEl = document.createElement("style");
styleEl.classList.add('theme-' + id); styleEl.classList.add("theme-" + id);
styleEl.id = "theme"; styleEl.id = "theme";
styleEl.innerHTML = this.code; styleEl.innerHTML = this.code;
@ -179,25 +134,28 @@ export default {
}, },
async editButton(id) { async editButton(id) {
// fetch theme // fetch theme
const {ok, result, error} = await ThemeService.getTheme(id); const { ok, result, error } = await ThemeService.getTheme(id);
if (ok) { if (ok) {
const {name, css} = result.data; const { name, css } = result.data;
this.name = name; this.name = name;
this.code = css; this.code = css;
this.editing = id; this.editing = id;
} }
}, },
async deleteButton (id) { async deleteButton(id) {
localStorage.removeItem('appliedThemeId'); localStorage.removeItem("appliedThemeId");
const themeEl = document.getElementById('theme'); const themeEl = document.getElementById("theme");
if (themeEl && themeEl.classList.contains('theme-' + id)) { if (themeEl && themeEl.classList.contains("theme-" + id)) {
document.getElementById('theme').outerHTML = '' document.getElementById("theme").outerHTML = "";
} }
const {ok, result, error} = await ThemeService.delete(id); const { ok, result, error } = await ThemeService.delete(id);
if (ok) { if (ok) {
this.themes = this.themes.filter(t => t.id !== id); this.themes = this.themes.filter(t => t.id !== id);
} }
}, },
makePublicButton() {
this.showMakePublic = true;
}
}, },
async mounted() { async mounted() {
this.fetchThemes(); 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 { .themes-list {
display: flex; display: flex;
flex-direction: column; 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> </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; left: 0;
width: 3px; width: 3px;
background: #ffffff5e; background: #ffffff5e;
transition: 0.2s;
} }
&.selected::before { &.selected::before {
content: ""; content: "";

View file

@ -58,7 +58,7 @@
<i class="material-icons">{{tabs[selectedTab].icon}}</i> <i class="material-icons">{{tabs[selectedTab].icon}}</i>
{{tabs[selectedTab].name}} {{tabs[selectedTab].name}}
</div> </div>
<div class="coming-soon" v-if="selectedTab > 0"> <div class="coming-soon" v-if="selectedTab > 1">
<div class="icon"> <div class="icon">
<i class="material-icons">explore</i> <i class="material-icons">explore</i>
</div> </div>
@ -72,20 +72,21 @@
<script> <script>
import { bus } from "@/main"; import { bus } from "@/main";
import Servers from "./Explore/servers"; import Servers from "./Explore/servers";
import Themes from "./Explore/themes";
import ServerService from "@/services/ServerService"; import ServerService from "@/services/ServerService";
import Navigation from "@/components/app/Navigation"; import Navigation from "@/components/app/Navigation";
import MyMiniInformation from "@/components/app/MyMiniInformation.vue"; import MyMiniInformation from "@/components/app/MyMiniInformation.vue";
export default { export default {
components: { Servers, Navigation, MyMiniInformation }, components: { Servers, Themes, Navigation, MyMiniInformation },
data() { data() {
return { return {
showLeftPanel: false, showLeftPanel: false,
selectedTab: 0, selectedTab: 0,
tabs: [ tabs: [
// {icon: "home", name: "home", component: ""}, // {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: "face", name: "Emoji Packs", component: "" },
{ icon: "brush", name: "Themes", component: "" },
{ icon: "message", name: "Message Styles", component: "" } { icon: "message", name: "Message Styles", component: "" }
], ],
nertiviaServerID: "6572915451527958528", nertiviaServerID: "6572915451527958528",
@ -168,6 +169,7 @@ export default {
.material-icons { .material-icons {
margin-right: 5px; margin-right: 5px;
} }
position: relative;
display: flex; display: flex;
align-content: center; align-content: center;
align-items: center; align-items: center;
@ -183,6 +185,15 @@ export default {
background: #053240; 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 { .header {
display: flex; 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'; import {instance, wrapper} from './Api';
export default { export default {
// servers
getServersList (params) { getServersList (params) {
return wrapper(instance().get(`explore/servers${params || ''}`)) return wrapper(instance().get(`explore/servers${params || ''}`))
}, },
@ -17,4 +18,22 @@ export default {
return wrapper(instance().post(`explore/servers`, data)) 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 = [ 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, version: 8.8,
title: "Themes!", title: "Themes!",
shortTitle: "", shortTitle: "",
date: "29/11/2019", date: "29/11/2019",
headColor: "#007792",
new: [ 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!)", "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 Spinner from "./../components/Spinner.vue";
import MainNav from "./../components/app/MainNav.vue"; import MainNav from "./../components/app/MainNav.vue";
import ThemeService from '../services/ThemeService'; import ThemeService from '../services/ThemeService';
import ExploreService from '../services/exploreService';
const ElectronFrameButtons = () => const ElectronFrameButtons = () =>
import("@/components/ElectronJS/FrameButtons.vue"); import("@/components/ElectronJS/FrameButtons.vue");
@ -128,12 +129,26 @@ export default {
if (!themeAppliedID) {return;} if (!themeAppliedID) {return;}
const {ok, result, error} = await ThemeService.getTheme(themeAppliedID); let exploreThemes = await ExploreService.applyTheme(themeAppliedID);
if (!ok) { return; } 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'); const styleEl = document.createElement('style');
styleEl.id = "theme" styleEl.id = "theme"
styleEl.classList.add('theme-' + result.data.id) styleEl.classList.add('theme-' + id)
styleEl.innerHTML = result.data.css styleEl.innerHTML = css
document.head.innerHTML += styleEl.outerHTML; document.head.innerHTML += styleEl.outerHTML;
} }
}, },