mirror of
https://github.com/danbulant/Nertivia-Client
synced 2026-06-20 14:51:33 +00:00
themes
This commit is contained in:
parent
b98d77c17a
commit
a3d5c1ba9d
7 changed files with 404 additions and 2 deletions
19
package-lock.json
generated
19
package-lock.json
generated
|
|
@ -2826,6 +2826,11 @@
|
|||
"integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=",
|
||||
"dev": true
|
||||
},
|
||||
"codemirror": {
|
||||
"version": "5.49.2",
|
||||
"resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.49.2.tgz",
|
||||
"integrity": "sha512-dwJ2HRPHm8w51WB5YTF9J7m6Z5dtkqbU9ntMZ1dqXyFB9IpjoUFDj80ahRVEoVanfIp6pfASJbOlbWdEf8FOzQ=="
|
||||
},
|
||||
"collection-visit": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz",
|
||||
|
|
@ -3793,6 +3798,11 @@
|
|||
"integrity": "sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw==",
|
||||
"dev": true
|
||||
},
|
||||
"diff-match-patch": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.4.tgz",
|
||||
"integrity": "sha512-Uv3SW8bmH9nAtHKaKSanOQmj2DnlH65fUpcrMdfdaOxUG02QQ4YGZ8AE7kKOMisF7UqvOlGKVYWRvezdncW9lg=="
|
||||
},
|
||||
"diffie-hellman": {
|
||||
"version": "5.0.3",
|
||||
"resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz",
|
||||
|
|
@ -11736,6 +11746,15 @@
|
|||
"resolved": "https://registry.npmjs.org/vue/-/vue-2.6.10.tgz",
|
||||
"integrity": "sha512-ImThpeNU9HbdZL3utgMCq0oiMzAkt1mcgy3/E6zWC/G6AaQoeuFdsl9nDhTDU3X1R6FK7nsIUuRACVcjI+A2GQ=="
|
||||
},
|
||||
"vue-codemirror": {
|
||||
"version": "4.0.6",
|
||||
"resolved": "https://registry.npmjs.org/vue-codemirror/-/vue-codemirror-4.0.6.tgz",
|
||||
"integrity": "sha512-ilU7Uf0mqBNSSV3KT7FNEeRIxH4s1fmpG4TfHlzvXn0QiQAbkXS9lLfwuZpaBVEnpP5CSE62iGJjoliTuA8poQ==",
|
||||
"requires": {
|
||||
"codemirror": "^5.41.0",
|
||||
"diff-match-patch": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"vue-eslint-parser": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-2.0.3.tgz",
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@
|
|||
"v-clipboard": "^2.2.2",
|
||||
"validator": "^11.1.0",
|
||||
"vue": "^2.5.17",
|
||||
"vue-codemirror": "^4.0.6",
|
||||
"vue-headful": "^2.0.1",
|
||||
"vue-mq": "^1.0.1",
|
||||
"vue-recaptcha": "^1.1.1",
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ import isElectron from '@/utils/ElectronJS/isElectron';
|
|||
const MyProfile = () => import("./SettingsPanels/MyProfile.vue");
|
||||
const ManageEmojis = () => import("./SettingsPanels/ManageEmojis.vue");
|
||||
const MessageDesign = () => import("./SettingsPanels/MessageDesign.vue");
|
||||
const MyThemes = () => import("./SettingsPanels/MyThemes.vue");
|
||||
const Notifications = () => import("./SettingsPanels/Notifications.vue");
|
||||
const AppSettings = () => import("./SettingsPanels/appSettings");
|
||||
|
||||
|
|
@ -47,6 +48,7 @@ export default {
|
|||
MyProfile,
|
||||
ManageEmojis,
|
||||
MessageDesign,
|
||||
MyThemes,
|
||||
Notifications,
|
||||
AppSettings
|
||||
},
|
||||
|
|
@ -66,6 +68,12 @@ export default {
|
|||
icon: "palette",
|
||||
component: "message-design"
|
||||
},
|
||||
{
|
||||
name: "My Themes",
|
||||
tabName: "My Themes BETA",
|
||||
icon: "palette",
|
||||
component: "my-themes"
|
||||
},
|
||||
{
|
||||
name: "Manage Emojis",
|
||||
tabName: "Manage Emojis",
|
||||
|
|
|
|||
330
src/components/app/Popouts/Popouts/SettingsPanels/MyThemes.vue
Normal file
330
src/components/app/Popouts/Popouts/SettingsPanels/MyThemes.vue
Normal file
|
|
@ -0,0 +1,330 @@
|
|||
<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
|
||||
</div>
|
||||
<div class="button" @click="closeButton">
|
||||
<div class="material-icons">clear</div>
|
||||
Close
|
||||
</div>
|
||||
<div class="button end" @click="applyButton">Apply</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="bar">
|
||||
<div class="button">
|
||||
<div class="material-icons">explore</div>
|
||||
Explore
|
||||
</div>
|
||||
<div class="button" @click="createButton">
|
||||
<div class="material-icons">add</div>
|
||||
Create
|
||||
</div>
|
||||
</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>
|
||||
</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 Spinner from "@/components/Spinner";
|
||||
|
||||
import config from "@/config.js";
|
||||
import ThemeService from "@/services/ThemeService";
|
||||
export default {
|
||||
components: {
|
||||
codemirror,
|
||||
Spinner
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
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
|
||||
}
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
async fetchThemes() {
|
||||
this.themes = null;
|
||||
// fetch themes
|
||||
const {ok, result, error} = await ThemeService.getThemes();
|
||||
if (ok) {
|
||||
this.themes = result.data;
|
||||
}
|
||||
},
|
||||
createButton() {
|
||||
this.name = "Untitled"
|
||||
this.code = `/* Start writing your styles*/\n`;
|
||||
this.editing = true;
|
||||
},
|
||||
closeButton() {
|
||||
this.editing = false;
|
||||
this.fetchThemes();
|
||||
},
|
||||
themeClick(id) {
|
||||
if (this.selectedThemeID === id) {
|
||||
this.selectedThemeID = null;
|
||||
} else {
|
||||
this.selectedThemeID = id;
|
||||
}
|
||||
},
|
||||
onCmReady(cm) {
|
||||
cm.on("keypress", () => {
|
||||
cm.showHint({ completeSingle: false });
|
||||
});
|
||||
},
|
||||
async saveButton() {
|
||||
const css = this.code;
|
||||
const name = this.name;
|
||||
if (typeof this.editing === 'string'){
|
||||
const response = await ThemeService.update({ name, css }, this.editing);
|
||||
} else {
|
||||
const response = await ThemeService.save({ name, css });
|
||||
}
|
||||
},
|
||||
async applyButton(id) {
|
||||
if (id) {
|
||||
const {ok, result, error} = await ThemeService.getTheme(id);
|
||||
if (ok) {
|
||||
this.code = result.data.css;
|
||||
}
|
||||
// save to local storage.
|
||||
localStorage.setItem('appliedThemeId', id);
|
||||
}
|
||||
const currentStyle = document.getElementById("theme");
|
||||
if (currentStyle) {
|
||||
currentStyle.innerHTML = this.code;
|
||||
} else {
|
||||
const styleEl = document.createElement("style");
|
||||
styleEl.id = "theme";
|
||||
styleEl.innerHTML = this.code;
|
||||
document.head.innerHTML += styleEl.outerHTML;
|
||||
}
|
||||
},
|
||||
async editButton(id) {
|
||||
// fetch theme
|
||||
const {ok, result, error} = await ThemeService.getTheme(id);
|
||||
if (ok) {
|
||||
const {name, css} = result.data;
|
||||
this.name = name;
|
||||
this.code = css;
|
||||
this.editing = id;
|
||||
}
|
||||
},
|
||||
async deleteButton (id) {
|
||||
const {ok, result, error} = await ThemeService.delete(id);
|
||||
if (ok) {
|
||||
this.themes = this.themes.filter(t => t.id !== id);
|
||||
}
|
||||
},
|
||||
},
|
||||
async mounted() {
|
||||
this.fetchThemes();
|
||||
},
|
||||
computed: {
|
||||
user() {
|
||||
return this.$store.getters.user;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.container {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
flex-direction: column;
|
||||
}
|
||||
.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 {
|
||||
height: 100%;
|
||||
}
|
||||
.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>
|
||||
19
src/services/ThemeService.js
Normal file
19
src/services/ThemeService.js
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import {instance, wrapper} from './Api';
|
||||
|
||||
export default {
|
||||
getTheme (id) {
|
||||
return wrapper(instance().get(`themes/${id}`));
|
||||
},
|
||||
getThemes () {
|
||||
return wrapper(instance().get('themes/'));
|
||||
},
|
||||
save (data) {
|
||||
return wrapper(instance().post(`themes/`, data));
|
||||
},
|
||||
update (data, id) {
|
||||
return wrapper(instance().patch(`themes/${id}`, data));
|
||||
},
|
||||
delete (id) {
|
||||
return wrapper(instance().delete(`themes/${id}`));
|
||||
},
|
||||
}
|
||||
|
|
@ -9,12 +9,22 @@ const prototype = {
|
|||
};
|
||||
|
||||
const config = [
|
||||
{
|
||||
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!)",
|
||||
],
|
||||
|
||||
},
|
||||
{
|
||||
version: 8.7,
|
||||
title: "12 hour clock mode",
|
||||
shortTitle: "",
|
||||
date: "24/11/2019",
|
||||
headColor: "#007792",
|
||||
new: [
|
||||
"Added 12 hour clock mode."
|
||||
],
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ import changelog from "@/utils/changelog.js";
|
|||
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';
|
||||
|
||||
const ElectronFrameButtons = () =>
|
||||
import("@/components/ElectronJS/FrameButtons.vue");
|
||||
|
|
@ -121,6 +122,18 @@ export default {
|
|||
const height = dimensions.height;
|
||||
this.$refs.app.style.height = height + "px";
|
||||
this.$refs.app.style.width = width + "px";
|
||||
},
|
||||
async setTheme() {
|
||||
const themeAppliedID = localStorage.getItem('appliedThemeId');
|
||||
|
||||
|
||||
if (!themeAppliedID) {return;}
|
||||
const {ok, result, error} = await ThemeService.getTheme(themeAppliedID);
|
||||
if (!ok) { return; }
|
||||
const styleEl = document.createElement('style');
|
||||
styleEl.id = "theme"
|
||||
styleEl.innerHTML = result.data.css
|
||||
document.head.innerHTML += styleEl.outerHTML;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
|
|
@ -128,7 +141,7 @@ export default {
|
|||
this.resizeEvent(dimensions);
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
async mounted() {
|
||||
const currentTab = localStorage.getItem("currentTab");
|
||||
if (currentTab) {
|
||||
this.$store.dispatch("setCurrentTab", parseInt(currentTab));
|
||||
|
|
@ -143,6 +156,8 @@ export default {
|
|||
bus.$on("title:change", title => {
|
||||
this.title = title;
|
||||
});
|
||||
this.setTheme();
|
||||
|
||||
},
|
||||
|
||||
computed: {
|
||||
|
|
|
|||
Loading…
Reference in a new issue