mirror of
https://github.com/Lissy93/dashy.git
synced 2025-07-21 04:35:15 +02:00
🚧 WIP Built the form for theme color configurator
This commit is contained in:
parent
78bb0afe30
commit
2f04a5ec6c
1
src/assets/interface-icons/config-cancel.svg
Normal file
1
src/assets/interface-icons/config-cancel.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg aria-hidden="true" focusable="false" data-prefix="far" data-icon="window-close" class="svg-inline--fa fa-window-close fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M464 32H48C21.5 32 0 53.5 0 80v352c0 26.5 21.5 48 48 48h416c26.5 0 48-21.5 48-48V80c0-26.5-21.5-48-48-48zm0 394c0 3.3-2.7 6-6 6H54c-3.3 0-6-2.7-6-6V86c0-3.3 2.7-6 6-6h404c3.3 0 6 2.7 6 6v340zM356.5 194.6L295.1 256l61.4 61.4c4.6 4.6 4.6 12.1 0 16.8l-22.3 22.3c-4.6 4.6-12.1 4.6-16.8 0L256 295.1l-61.4 61.4c-4.6 4.6-12.1 4.6-16.8 0l-22.3-22.3c-4.6-4.6-4.6-12.1 0-16.8l61.4-61.4-61.4-61.4c-4.6-4.6-4.6-12.1 0-16.8l22.3-22.3c4.6-4.6 12.1-4.6 16.8 0l61.4 61.4 61.4-61.4c4.6-4.6 12.1-4.6 16.8 0l22.3 22.3c4.7 4.6 4.7 12.1 0 16.8z"></path></svg>
|
After Width: | Height: | Size: 767 B |
@ -1,23 +1,29 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="theme-configurator-wrapper">
|
<div class="theme-configurator-wrapper">
|
||||||
<h3 class="configurator-title">Custom Configurator</h3>
|
<h3 class="configurator-title">Custom Configurator</h3>
|
||||||
<div class="color-row">
|
<div class="color-row" v-for="colorName in Object.keys(customColors)" :key="colorName">
|
||||||
<label for="col">Primary</label>
|
<label :for="`color-input-${colorName}`" class="color-name">
|
||||||
|
{{colorName.replace('-', ' ')}}
|
||||||
|
</label>
|
||||||
<v-swatches
|
<v-swatches
|
||||||
v-model="color"
|
v-model="customColors[colorName]"
|
||||||
show-fallback
|
show-fallback
|
||||||
fallback-input-type="color"
|
fallback-input-type="color"
|
||||||
popover-x="left"
|
popover-x="left"
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
id="col"
|
:id="`color-input-${colorName}`"
|
||||||
slot="trigger"
|
slot="trigger"
|
||||||
:value="color"
|
:value="customColors[colorName]"
|
||||||
class="swatch-input form__input__element"
|
class="swatch-input form__input__element"
|
||||||
readonly
|
readonly
|
||||||
:style="`background:${color}; color: ${getForegroundColor(color)};`"
|
:style="makeSwatchStyles(colorName)"
|
||||||
/>
|
/>
|
||||||
</v-swatches>
|
</v-swatches>
|
||||||
|
</div> <!-- End of color list -->
|
||||||
|
<div class="action-buttons">
|
||||||
|
<Button><SaveIcon />Save</Button>
|
||||||
|
<Button><CancelIcon />Cancel</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -26,28 +32,40 @@
|
|||||||
import VSwatches from 'vue-swatches';
|
import VSwatches from 'vue-swatches';
|
||||||
import 'vue-swatches/dist/vue-swatches.css';
|
import 'vue-swatches/dist/vue-swatches.css';
|
||||||
|
|
||||||
|
import Button from '@/components/FormElements/Button';
|
||||||
|
import SaveIcon from '@/assets/interface-icons/save-config.svg';
|
||||||
|
import CancelIcon from '@/assets/interface-icons/config-cancel.svg';
|
||||||
|
|
||||||
|
const mainVariables = ['primary', 'background', 'background-darker'];
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ThemeMaker',
|
name: 'ThemeMaker',
|
||||||
components: {
|
components: {
|
||||||
VSwatches,
|
VSwatches,
|
||||||
|
Button,
|
||||||
|
SaveIcon,
|
||||||
|
CancelIcon,
|
||||||
},
|
},
|
||||||
data: () => ({
|
data() {
|
||||||
color: '#A463BF',
|
return {
|
||||||
}),
|
customColors: this.makeInitialData(mainVariables),
|
||||||
methods: {
|
|
||||||
/* Returns a complmenting text color for the palete foreground */
|
|
||||||
/* White if the color is dark, otherwise black */
|
|
||||||
getForegroundColor(colorHex) {
|
|
||||||
const hexToRgb = (hex) => {
|
|
||||||
const colorParts = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
|
||||||
const parse = (index) => parseInt(colorParts[index], 16);
|
|
||||||
return colorParts ? { r: parse(1), g: parse(2), b: parse(3) } : null;
|
|
||||||
};
|
};
|
||||||
const getLightness = (rgb) => (rgb.r * 299 + rgb.g * 587 + rgb.b * 114) / 1000;
|
},
|
||||||
return getLightness(hexToRgb(colorHex)) < 100 ? 'white' : 'black';
|
methods: {
|
||||||
|
/* Finds the current dominent value for a given CSS variable */
|
||||||
|
getCssVariableValue(cssVar) {
|
||||||
|
return getComputedStyle(document.documentElement).getPropertyValue(cssVar) || 'inherit';
|
||||||
|
},
|
||||||
|
/* Returns a JSON object, with the variable name as key, and color as value */
|
||||||
|
makeInitialData(variableArray) {
|
||||||
|
const data = {};
|
||||||
|
variableArray.forEach((colorName) => {
|
||||||
|
data[colorName] = this.getCssVariableValue(`--${colorName}`);
|
||||||
|
});
|
||||||
|
return data;
|
||||||
},
|
},
|
||||||
/* If a builtin theme is applied, grab it's colors */
|
/* If a builtin theme is applied, grab it's colors */
|
||||||
getAllThemeNames() {
|
findAllVariableNames() {
|
||||||
Array.from(document.styleSheets) // Get all stylesheets, filter out irrelevant ones
|
Array.from(document.styleSheets) // Get all stylesheets, filter out irrelevant ones
|
||||||
.filter(sheet => sheet.href === null || sheet.href.startsWith(window.location.origin))
|
.filter(sheet => sheet.href === null || sheet.href.startsWith(window.location.origin))
|
||||||
.reduce(
|
.reduce(
|
||||||
@ -62,6 +80,24 @@ export default {
|
|||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
/* Returns a complmenting text color for the palete foreground */
|
||||||
|
/* White if the color is dark, otherwise black */
|
||||||
|
getForegroundColor(colorHex) {
|
||||||
|
const hexToRgb = (hex) => {
|
||||||
|
const colorParts = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
||||||
|
if (!colorParts || colorParts.length < 3) return 'pink';
|
||||||
|
const parse = (index) => parseInt(colorParts[index], 16);
|
||||||
|
return colorParts ? { r: parse(1), g: parse(2), b: parse(3) } : null;
|
||||||
|
};
|
||||||
|
const getLightness = (rgb) => (rgb.r * 299 + rgb.g * 587 + rgb.b * 114) / 1000;
|
||||||
|
return getLightness(hexToRgb(colorHex.trim())) < 100 ? 'white' : 'black';
|
||||||
|
},
|
||||||
|
/* The contents of the style attribute, to set background and text color of swatch */
|
||||||
|
makeSwatchStyles(colorName) {
|
||||||
|
const contrastingColor = this.getForegroundColor(this.customColors[colorName]);
|
||||||
|
return `background:${this.customColors[colorName]};`
|
||||||
|
+ `color:${contrastingColor}; border: 1px solid ${contrastingColor}`;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
@ -70,10 +106,11 @@ export default {
|
|||||||
|
|
||||||
div.theme-configurator-wrapper {
|
div.theme-configurator-wrapper {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 6rem;
|
top: 4rem;
|
||||||
top: 3rem;
|
right: 1rem;
|
||||||
width: 12rem;
|
width: 16rem;
|
||||||
min-height: 12rem;
|
min-height: 12rem;
|
||||||
|
max-height: 20rem;
|
||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
z-index: 5;
|
z-index: 5;
|
||||||
background: var(--config-settings-background);
|
background: var(--config-settings-background);
|
||||||
@ -83,6 +120,7 @@ div.theme-configurator-wrapper {
|
|||||||
|
|
||||||
h3.configurator-title {
|
h3.configurator-title {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
font-weight: normal;
|
||||||
margin: 0.4rem;
|
margin: 0.4rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,8 +128,11 @@ div.theme-configurator-wrapper {
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding: 0.2rem 0;
|
padding: 0.25rem 0;
|
||||||
&:not(:last-child) { border-bottom: 1px dashed var(--primary); }
|
&:not(:last-child) { border-bottom: 1px dashed var(--primary); }
|
||||||
|
label.color-name {
|
||||||
|
text-transform: capitalize;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
input.swatch-input {
|
input.swatch-input {
|
||||||
@ -114,4 +155,13 @@ div.theme-configurator-wrapper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div.action-buttons {
|
||||||
|
display: flex;
|
||||||
|
button {
|
||||||
|
min-width: 6rem;
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
margin: 1rem 0.5rem 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="theme-selector-section" v-if="themes" v-click-outside="closeThemeConfigurator">
|
<div class="theme-selector-section" v-click-outside="closeThemeConfigurator">
|
||||||
<div>
|
<div>
|
||||||
<span class="theme-label">Theme</span>
|
<span class="theme-label">Theme</span>
|
||||||
<v-select
|
<v-select
|
||||||
@ -11,7 +11,6 @@
|
|||||||
</div>
|
</div>
|
||||||
<IconPalette
|
<IconPalette
|
||||||
class="color-button"
|
class="color-button"
|
||||||
v-if="selectedTheme === 'custom'"
|
|
||||||
@click="openThemeConfigurator"
|
@click="openThemeConfigurator"
|
||||||
/>
|
/>
|
||||||
<CustomThemeMaker v-if="themeConfiguratorOpen" />
|
<CustomThemeMaker v-if="themeConfiguratorOpen" />
|
||||||
@ -28,7 +27,7 @@ import IconPalette from '@/assets/interface-icons/config-color-palette.svg';
|
|||||||
export default {
|
export default {
|
||||||
name: 'ThemeSelector',
|
name: 'ThemeSelector',
|
||||||
props: {
|
props: {
|
||||||
themes: Object,
|
externalThemes: Object,
|
||||||
confTheme: String,
|
confTheme: String,
|
||||||
userThemes: Array,
|
userThemes: Array,
|
||||||
},
|
},
|
||||||
@ -37,29 +36,33 @@ export default {
|
|||||||
IconPalette,
|
IconPalette,
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
selectedTheme(newTheme) { this.updateTheme(newTheme); },
|
/* When the theme changes, then call the update method */
|
||||||
|
selectedTheme(newTheme) {
|
||||||
|
this.updateTheme(newTheme);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
selectedTheme: this.getInitialTheme(),
|
selectedTheme: this.getInitialTheme(),
|
||||||
builtInThemes: [...Defaults.builtInThemes, ...this.userThemes],
|
builtInThemes: [...Defaults.builtInThemes, ...this.userThemes],
|
||||||
themeHelper: new LoadExternalTheme(),
|
themeHelper: new LoadExternalTheme(),
|
||||||
// modalName: modalNames.THEME_MAKER,
|
themeConfiguratorOpen: false, // Control the opening of theme config popup
|
||||||
themeConfiguratorOpen: false,
|
|
||||||
ApplyLocalTheme,
|
ApplyLocalTheme,
|
||||||
ApplyCustomTheme,
|
ApplyCustomTheme,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
/* Combines all theme names (builtin and user defined) together */
|
||||||
themeNames: function themeNames() {
|
themeNames: function themeNames() {
|
||||||
const externalThemeNames = Object.keys(this.themes);
|
const externalThemeNames = Object.keys(this.externalThemes);
|
||||||
const specialThemes = ['custom'];
|
const specialThemes = ['custom'];
|
||||||
return [...specialThemes, ...externalThemeNames, ...this.builtInThemes];
|
return [...externalThemeNames, ...this.builtInThemes, ...specialThemes];
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
const added = Object.keys(this.themes).map(
|
// Pass all user custom stylesheets to the themehelper
|
||||||
name => this.themeHelper.add(name, this.themes[name]),
|
const added = Object.keys(this.externalThemes).map(
|
||||||
|
name => this.themeHelper.add(name, this.externalThemes[name]),
|
||||||
);
|
);
|
||||||
// Quicker loading, if the theme is local we can apply it immidiatley
|
// Quicker loading, if the theme is local we can apply it immidiatley
|
||||||
if (this.isThemeLocal(this.selectedTheme)) {
|
if (this.isThemeLocal(this.selectedTheme)) {
|
||||||
@ -76,12 +79,15 @@ export default {
|
|||||||
getInitialTheme() {
|
getInitialTheme() {
|
||||||
return localStorage[localStorageKeys.THEME] || this.confTheme || Defaults.theme;
|
return localStorage[localStorageKeys.THEME] || this.confTheme || Defaults.theme;
|
||||||
},
|
},
|
||||||
|
/* Determines if a given theme is local / not a custom user stylesheet */
|
||||||
isThemeLocal(themeToCheck) {
|
isThemeLocal(themeToCheck) {
|
||||||
return this.builtInThemes.includes(themeToCheck);
|
return this.builtInThemes.includes(themeToCheck);
|
||||||
},
|
},
|
||||||
|
/* Opens the theme color configurator popup */
|
||||||
openThemeConfigurator() {
|
openThemeConfigurator() {
|
||||||
this.themeConfiguratorOpen = true;
|
this.themeConfiguratorOpen = true;
|
||||||
},
|
},
|
||||||
|
/* Closes the theme color configurator popup */
|
||||||
closeThemeConfigurator() {
|
closeThemeConfigurator() {
|
||||||
this.themeConfiguratorOpen = false;
|
this.themeConfiguratorOpen = false;
|
||||||
},
|
},
|
||||||
@ -100,6 +106,7 @@ export default {
|
|||||||
}
|
}
|
||||||
localStorage.setItem(localStorageKeys.THEME, newTheme);
|
localStorage.setItem(localStorageKeys.THEME, newTheme);
|
||||||
},
|
},
|
||||||
|
/* Removes any applied themes */
|
||||||
resetToDefault() {
|
resetToDefault() {
|
||||||
document.getElementsByTagName('html')[0].removeAttribute('data-theme');
|
document.getElementsByTagName('html')[0].removeAttribute('data-theme');
|
||||||
},
|
},
|
||||||
|
@ -12,7 +12,9 @@ export const ApplyLocalTheme = (newTheme) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/* Sets specific CSS variables, for the users custom theme */
|
/* Sets specific CSS variables, for the users custom theme */
|
||||||
export const ApplyCustomTheme = () => { };
|
export const ApplyCustomTheme = () => {
|
||||||
|
ApplyLocalTheme('custom');
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A function for pre-loading, and easy switching of external stylesheets
|
* A function for pre-loading, and easy switching of external stylesheets
|
||||||
|
Loading…
x
Reference in New Issue
Block a user