mirror of https://github.com/Lissy93/dashy.git
🚧 WIP - working on the custom theme feature
This commit is contained in:
parent
c7d91bed94
commit
65aa971099
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 741 KiB After Width: | Height: | Size: 818 KiB |
|
@ -41,6 +41,13 @@
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
### Ground Control
|
||||||
|
> By [@dtctek](https://github.com/dtctek)
|
||||||
|
|
||||||
|
![screenshot-ground-control](https://raw.githubusercontent.com/Lissy93/dashy/master/docs/showcase/7-ground-control-dtctek.png)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Submitting your Dashboard
|
## Submitting your Dashboard
|
||||||
|
|
||||||
#### How to Submit
|
#### How to Submit
|
||||||
|
@ -69,4 +76,4 @@ If you're submitting a pull request, please use a format similar to this:
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 191 KiB |
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "Dashy",
|
"name": "Dashy",
|
||||||
"version": "1.3.7",
|
"version": "1.3.8",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"main": "server",
|
"main": "server",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@ -32,7 +32,7 @@
|
||||||
"vue": "^2.6.10",
|
"vue": "^2.6.10",
|
||||||
"vue-cli-plugin-yaml": "^1.0.2",
|
"vue-cli-plugin-yaml": "^1.0.2",
|
||||||
"vue-js-modal": "^2.0.0-rc.6",
|
"vue-js-modal": "^2.0.0-rc.6",
|
||||||
"vue-material-tabs": "^0.0.7",
|
"vue-material-tabs": "^0.1.2",
|
||||||
"vue-prism-editor": "^1.2.2",
|
"vue-prism-editor": "^1.2.2",
|
||||||
"vue-router": "^3.0.3",
|
"vue-router": "^3.0.3",
|
||||||
"vue-select": "^3.11.2",
|
"vue-select": "^3.11.2",
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
<svg aria-hidden="true" focusable="false" data-prefix="far" data-icon="palette" class="svg-inline--fa fa-palette fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M128 224c-17.7 0-32 14.3-32 32s14.3 32 32 32 32-14.3 32-32-14.4-32-32-32zM418.6 58.1C359.2 9.3 281.3-10 204.6 5 104.9 24.4 24.7 104.2 5.1 203.7c-16.7 84.2 8.1 168.3 67.8 230.6 47.3 49.4 109.7 77.8 167.9 77.8 8.8 0 17.5-.6 26.1-2 24.2-3.7 44.6-18.7 56.1-41.1 12.3-24 12.3-52.7.2-76.6-6.1-12-5.5-26.2 1.8-38 7-11.8 18.7-18.4 32-18.4h72.2c46.4 0 82.8-35.7 82.8-81.3-.2-76.4-34.3-148.1-93.4-196.6zM429.2 288H357c-29.9 0-57.2 15.4-73 41.3-16 26.1-17.3 57.8-3.6 84.9 5.1 10.1 5.1 22.7-.2 32.9-2.6 5-8.7 13.7-20.6 15.6-49.3 7.7-108.9-16.6-152-61.6-48.8-50.9-69-119.4-55.4-188 15.9-80.6 80.8-145.3 161.6-161 62.6-12.3 126.1 3.5 174.3 43.1 48.1 39.5 75.7 97.6 75.9 159.6 0 18.6-15.3 33.2-34.8 33.2zM160 128c-17.7 0-32 14.3-32 32s14.3 32 32 32 32-14.3 32-32-14.4-32-32-32zm96-32.1c-17.7 0-32 14.3-32 32s14.3 32 32 32 32-14.3 32-32c0-17.6-14.3-32-32-32zm96 32.1c-17.7 0-32 14.3-32 32s14.3 32 32 32 32-14.3 32-32-14.3-32-32-32z"></path></svg>
|
After Width: | Height: | Size: 1.1 KiB |
|
@ -1,5 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="theme-selector-section" v-if="themes" >
|
<div class="theme-selector-section" v-if="themes" v-click-outside="closeThemeConfigurator">
|
||||||
|
<div>
|
||||||
<span class="theme-label">Theme</span>
|
<span class="theme-label">Theme</span>
|
||||||
<v-select
|
<v-select
|
||||||
:options="themeNames"
|
:options="themeNames"
|
||||||
|
@ -7,13 +8,26 @@
|
||||||
class="theme-dropdown"
|
class="theme-dropdown"
|
||||||
:tabindex="-2"
|
:tabindex="-2"
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
<IconPalette
|
||||||
|
class="color-button"
|
||||||
|
v-if="selectedTheme === 'custom'"
|
||||||
|
@click="openThemeConfigurator"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
v-if="themeConfiguratorOpen"
|
||||||
|
class="theme-configurator-wrapper"
|
||||||
|
>
|
||||||
|
<p>Custom Theme Configurator</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
import ThemeHelper from '@/utils/ThemeHelper';
|
import { LoadExternalTheme, ApplyLocalTheme, ApplyCustomTheme } from '@/utils/ThemeHelper';
|
||||||
import Defaults, { localStorageKeys } from '@/utils/defaults';
|
import Defaults, { localStorageKeys } from '@/utils/defaults';
|
||||||
|
import IconPalette from '@/assets/interface-icons/config-color-palette.svg';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ThemeSelector',
|
name: 'ThemeSelector',
|
||||||
|
@ -22,21 +36,28 @@ export default {
|
||||||
confTheme: String,
|
confTheme: String,
|
||||||
userThemes: Array,
|
userThemes: Array,
|
||||||
},
|
},
|
||||||
|
components: {
|
||||||
|
IconPalette,
|
||||||
|
},
|
||||||
watch: {
|
watch: {
|
||||||
selectedTheme(newTheme) { this.updateTheme(newTheme); },
|
selectedTheme(newTheme) { this.updateTheme(newTheme); },
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
selectedTheme: this.getInitialTheme(),
|
selectedTheme: this.getInitialTheme(),
|
||||||
themeHelper: new ThemeHelper(),
|
builtInThemes: [...Defaults.builtInThemes, ...this.userThemes],
|
||||||
loading: true,
|
themeHelper: new LoadExternalTheme(),
|
||||||
builtInThemes: this.userThemes.concat(Defaults.builtInThemes),
|
// modalName: modalNames.THEME_MAKER,
|
||||||
|
themeConfiguratorOpen: false,
|
||||||
|
ApplyLocalTheme,
|
||||||
|
ApplyCustomTheme,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
themeNames: function themeNames() {
|
themeNames: function themeNames() {
|
||||||
const externalThemeNames = Object.keys(this.themes);
|
const externalThemeNames = Object.keys(this.themes);
|
||||||
return externalThemeNames.concat(this.builtInThemes);
|
const specialThemes = ['custom'];
|
||||||
|
return [...specialThemes, ...externalThemeNames, ...this.builtInThemes];
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
|
@ -54,12 +75,6 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
/* Sets the theme, by updating data-theme attribute on the html tag */
|
|
||||||
setLocalTheme(newTheme) {
|
|
||||||
const htmlTag = document.getElementsByTagName('html')[0];
|
|
||||||
if (htmlTag.hasAttribute('data-theme')) htmlTag.removeAttribute('data-theme');
|
|
||||||
htmlTag.setAttribute('data-theme', newTheme);
|
|
||||||
},
|
|
||||||
/* Get default theme */
|
/* Get default theme */
|
||||||
getInitialTheme() {
|
getInitialTheme() {
|
||||||
return localStorage[localStorageKeys.THEME] || this.confTheme || Defaults.theme;
|
return localStorage[localStorageKeys.THEME] || this.confTheme || Defaults.theme;
|
||||||
|
@ -67,14 +82,22 @@ export default {
|
||||||
isThemeLocal(themeToCheck) {
|
isThemeLocal(themeToCheck) {
|
||||||
return this.builtInThemes.includes(themeToCheck);
|
return this.builtInThemes.includes(themeToCheck);
|
||||||
},
|
},
|
||||||
|
openThemeConfigurator() {
|
||||||
|
this.themeConfiguratorOpen = true;
|
||||||
|
},
|
||||||
|
closeThemeConfigurator() {
|
||||||
|
this.themeConfiguratorOpen = false;
|
||||||
|
},
|
||||||
/* Updates theme. Checks if the new theme is local or external,
|
/* Updates theme. Checks if the new theme is local or external,
|
||||||
and calls appropirate updating function. Updates local storage */
|
and calls appropirate updating function. Updates local storage */
|
||||||
updateTheme(newTheme) {
|
updateTheme(newTheme) {
|
||||||
if (newTheme === 'Deafault') {
|
if (newTheme === 'custom') {
|
||||||
|
this.ApplyCustomTheme();
|
||||||
|
} else if (newTheme === 'Deafault') {
|
||||||
this.resetToDefault();
|
this.resetToDefault();
|
||||||
this.themeHelper.theme = 'Deafault';
|
this.themeHelper.theme = 'Deafault';
|
||||||
} else if (this.isThemeLocal(newTheme)) {
|
} else if (this.isThemeLocal(newTheme)) {
|
||||||
this.setLocalTheme(newTheme);
|
this.ApplyLocalTheme(newTheme);
|
||||||
} else {
|
} else {
|
||||||
this.themeHelper.theme = newTheme;
|
this.themeHelper.theme = newTheme;
|
||||||
}
|
}
|
||||||
|
@ -95,7 +118,7 @@ export default {
|
||||||
div.vs__dropdown-toggle {
|
div.vs__dropdown-toggle {
|
||||||
border-color: var(--settings-text-color);
|
border-color: var(--settings-text-color);
|
||||||
border-radius: var(--curve-factor);
|
border-radius: var(--curve-factor);
|
||||||
min-width: 10rem;
|
width: 8rem;
|
||||||
height: 1.8rem;
|
height: 1.8rem;
|
||||||
font-size: 0.85rem;
|
font-size: 0.85rem;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
@ -111,6 +134,8 @@ export default {
|
||||||
width: auto;
|
width: auto;
|
||||||
background: var(--background);
|
background: var(--background);
|
||||||
z-index: 5;
|
z-index: 5;
|
||||||
|
max-width: 13rem;
|
||||||
|
overflow-x: hidden;
|
||||||
}
|
}
|
||||||
li.vs__dropdown-option--highlight {
|
li.vs__dropdown-option--highlight {
|
||||||
background: var(--settings-text-color);
|
background: var(--settings-text-color);
|
||||||
|
@ -123,7 +148,7 @@ export default {
|
||||||
|
|
||||||
.theme-selector-section {
|
.theme-selector-section {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: row;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
span.theme-label {
|
span.theme-label {
|
||||||
|
@ -133,4 +158,37 @@ export default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
svg.color-button {
|
||||||
|
path {
|
||||||
|
fill: var(--settings-text-color);
|
||||||
|
}
|
||||||
|
width: 1rem;
|
||||||
|
height: 1rem;
|
||||||
|
padding: 0.2rem;
|
||||||
|
margin: 0.5rem;
|
||||||
|
align-self: flex-end;
|
||||||
|
text-align: center;
|
||||||
|
background: var(--background);
|
||||||
|
border: 1px solid var(--settings-text-color);;
|
||||||
|
border-radius: var(--curve-factor);
|
||||||
|
cursor: pointer;
|
||||||
|
&:hover, &.selected {
|
||||||
|
background: var(--settings-text-color);
|
||||||
|
path { fill: var(--background); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
div.theme-configurator-wrapper {
|
||||||
|
position: absolute;
|
||||||
|
right: 2rem;
|
||||||
|
top: 3rem;
|
||||||
|
width: 30%;
|
||||||
|
height: 50%;
|
||||||
|
padding: 0.5rem;
|
||||||
|
background: var(--config-settings-background);
|
||||||
|
color: var(--config-settings-color);
|
||||||
|
border-radius: var(--curve-factor);
|
||||||
|
box-shadow: 0 8px 10px -2px rgba(0, 0, 0, 0.6), 1px 1px 6px var(--primary);
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import ConfigAccumulator from '@/utils/ConfigAccumalator';
|
import ConfigAccumulator from '@/utils/ConfigAccumalator';
|
||||||
|
|
||||||
import { visibleComponents } from '@/utils/defaults';
|
import { visibleComponents, localStorageKeys, theme as defaultTheme } from '@/utils/defaults';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initiates the Accumulator class and generates a complete config object
|
* Initiates the Accumulator class and generates a complete config object
|
||||||
|
@ -40,3 +40,14 @@ export const componentVisibility = (appConfig) => {
|
||||||
? !usersChoice.hideSplashScreen : visibleComponents.splashScreen,
|
? !usersChoice.hideSplashScreen : visibleComponents.splashScreen,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the users saved theme, first looks for local storage theme,
|
||||||
|
* then looks at user's appConfig, and finally checks the defaults
|
||||||
|
* @returns {string} Name of theme to apply
|
||||||
|
*/
|
||||||
|
export const getTheme = () => {
|
||||||
|
const localTheme = localStorage[localStorageKeys.THEME];
|
||||||
|
const appConfigTheme = config.appConfig.theme;
|
||||||
|
return localTheme || appConfigTheme || defaultTheme;
|
||||||
|
};
|
||||||
|
|
|
@ -1,8 +1,25 @@
|
||||||
|
import ErrorHandler from '@/utils/ErrorHandler';
|
||||||
|
import { getTheme } from '@/utils/ConfigHelpers';
|
||||||
|
|
||||||
|
/* Returns users current theme */
|
||||||
|
export const GetTheme = () => getTheme();
|
||||||
|
|
||||||
|
/* Sets the theme, by updating data-theme attribute on the html tag */
|
||||||
|
export const ApplyLocalTheme = (newTheme) => {
|
||||||
|
const htmlTag = document.getElementsByTagName('html')[0];
|
||||||
|
if (htmlTag.hasAttribute('data-theme')) htmlTag.removeAttribute('data-theme');
|
||||||
|
htmlTag.setAttribute('data-theme', newTheme);
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Sets specific CSS variables, for the users custom theme */
|
||||||
|
export const ApplyCustomTheme = () => { };
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A function for pre-loading, and easy switching of external stylesheets
|
* A function for pre-loading, and easy switching of external stylesheets
|
||||||
* External CSS is preloaded to avoid FOUC
|
* External CSS is preloaded to avoid FOUC
|
||||||
*/
|
*/
|
||||||
const ThemeHelper = function th() {
|
export const LoadExternalTheme = function th() {
|
||||||
|
/* Preload selected external theme */
|
||||||
const preloadTheme = (href) => {
|
const preloadTheme = (href) => {
|
||||||
const link = document.createElement('link');
|
const link = document.createElement('link');
|
||||||
link.rel = 'stylesheet';
|
link.rel = 'stylesheet';
|
||||||
|
@ -18,10 +35,21 @@ const ThemeHelper = function th() {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* Check theme is selected, and it exists */
|
||||||
|
const checkTheme = (themes, name) => {
|
||||||
|
if ((!name) || (name !== 'custom' && !themes[name])) {
|
||||||
|
ErrorHandler(`Theme: '${name || '[not selected]'}' does not exist.`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Disable all but selected theme */
|
||||||
const selectTheme = (themes, name) => {
|
const selectTheme = (themes, name) => {
|
||||||
const t = themes; // To avoid ESLint complaining about mutating a param
|
if (checkTheme(themes, name)) {
|
||||||
if (name && !themes[name]) throw new Error(`Theme: '${name}' does not exist.`);
|
const t = themes; // To avoid ESLint complaining about mutating a param
|
||||||
Object.keys(themes).forEach(n => { t[n].disabled = (n !== name); });
|
Object.keys(themes).forEach(n => { t[n].disabled = (n !== name); });
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const themes = {};
|
const themes = {};
|
||||||
|
@ -32,5 +60,3 @@ const ThemeHelper = function th() {
|
||||||
get theme() { return Object.keys(themes).find(n => !themes[n].disabled); },
|
get theme() { return Object.keys(themes).find(n => !themes[n].disabled); },
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ThemeHelper;
|
|
||||||
|
|
|
@ -70,6 +70,7 @@ module.exports = {
|
||||||
CONF_EDITOR: 'CONF_EDITOR',
|
CONF_EDITOR: 'CONF_EDITOR',
|
||||||
CLOUD_BACKUP: 'CLOUD_BACKUP',
|
CLOUD_BACKUP: 'CLOUD_BACKUP',
|
||||||
REBUILD_APP: 'REBUILD_APP',
|
REBUILD_APP: 'REBUILD_APP',
|
||||||
|
THEME_MAKER: 'THEME_MAKER',
|
||||||
ABOUT_APP: 'ABOUT_APP',
|
ABOUT_APP: 'ABOUT_APP',
|
||||||
},
|
},
|
||||||
topLevelConfKeys: {
|
topLevelConfKeys: {
|
||||||
|
|
|
@ -9,7 +9,8 @@
|
||||||
|
|
||||||
import SideBar from '@/components/Workspace/SideBar';
|
import SideBar from '@/components/Workspace/SideBar';
|
||||||
import WebContent from '@/components/Workspace/WebContent';
|
import WebContent from '@/components/Workspace/WebContent';
|
||||||
import Defaults, { localStorageKeys } from '@/utils/defaults';
|
import Defaults from '@/utils/defaults';
|
||||||
|
import { ApplyLocalTheme, GetTheme } from '@/utils/ThemeHelper';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Workspace',
|
name: 'Workspace',
|
||||||
|
@ -19,6 +20,8 @@ export default {
|
||||||
},
|
},
|
||||||
data: () => ({
|
data: () => ({
|
||||||
url: '', // this.$route.query.url || '',
|
url: '', // this.$route.query.url || '',
|
||||||
|
GetTheme,
|
||||||
|
ApplyLocalTheme,
|
||||||
}),
|
}),
|
||||||
components: {
|
components: {
|
||||||
SideBar,
|
SideBar,
|
||||||
|
@ -29,10 +32,7 @@ export default {
|
||||||
this.url = url;
|
this.url = url;
|
||||||
},
|
},
|
||||||
setTheme() {
|
setTheme() {
|
||||||
const theme = localStorage[localStorageKeys.THEME] || this.confTheme || Defaults.theme;
|
this.ApplyLocalTheme(this.GetTheme());
|
||||||
const htmlTag = document.getElementsByTagName('html')[0];
|
|
||||||
if (htmlTag.hasAttribute('data-theme')) htmlTag.removeAttribute('data-theme');
|
|
||||||
htmlTag.setAttribute('data-theme', theme);
|
|
||||||
},
|
},
|
||||||
initiateFontAwesome() {
|
initiateFontAwesome() {
|
||||||
const fontAwesomeScript = document.createElement('script');
|
const fontAwesomeScript = document.createElement('script');
|
||||||
|
|
|
@ -8899,10 +8899,10 @@ vue-loader@^15.9.2:
|
||||||
vue-hot-reload-api "^2.3.0"
|
vue-hot-reload-api "^2.3.0"
|
||||||
vue-style-loader "^4.1.0"
|
vue-style-loader "^4.1.0"
|
||||||
|
|
||||||
vue-material-tabs@^0.0.7:
|
vue-material-tabs@^0.1.2:
|
||||||
version "0.0.7"
|
version "0.1.5"
|
||||||
resolved "https://registry.yarnpkg.com/vue-material-tabs/-/vue-material-tabs-0.0.7.tgz#5f3fa04ad35384af68582f7c89ad4cecac89207b"
|
resolved "https://registry.yarnpkg.com/vue-material-tabs/-/vue-material-tabs-0.1.5.tgz#255fc0beb27c005eaae61c1534d782a94c30c525"
|
||||||
integrity sha512-02X5paTksYKrGvSRpMdkctRO9qhvJFD5VEGxd0xjOX4sYz6mZSAez0Z/+aYf7Z5ziY+eJ9dMQmxaLn9DVKQRJw==
|
integrity sha512-ZLFRCxaCS3TM8IwnxQA4S2CVj+tBaILb8fQZDg+Ix+9Zu+k16udrdhLU2GWvJEVPLspuCzKhCEXW4cIg59YkVw==
|
||||||
|
|
||||||
vue-prism-editor@^1.2.2:
|
vue-prism-editor@^1.2.2:
|
||||||
version "1.2.2"
|
version "1.2.2"
|
||||||
|
|
Loading…
Reference in New Issue