improve settings ui

This commit is contained in:
Send_Nukez 2021-11-15 04:47:54 +01:00
parent aaeb51a6ca
commit f50ccaaf10
4 changed files with 123 additions and 40 deletions

View file

@ -9,6 +9,7 @@ Fixed:
- Playing icon position being wrong when listening to a playlist that is inside a folder ([#106 (comment)](https://github.com/JulienMaille/dribbblish-dynamic-theme/issues/106#issuecomment-967208507)) - Playing icon position being wrong when listening to a playlist that is inside a folder ([#106 (comment)](https://github.com/JulienMaille/dribbblish-dynamic-theme/issues/106#issuecomment-967208507))
Improved: Improved:
- The settings UI now better represents grouped items
- Settings that have been changed from default will now show a line next to them. Inspired by the [Visual Studio Code settings UI](https://d33wubrfki0l68.cloudfront.net/d1f1ea4def506997ced23d3d912154794e530e1c/063d2/assets/img/blog/2020-09-17-vscode-settings/settings-ui.png) - Settings that have been changed from default will now show a line next to them. Inspired by the [Visual Studio Code settings UI](https://d33wubrfki0l68.cloudfront.net/d1f1ea4def506997ced23d3d912154794e530e1c/063d2/assets/img/blog/2020-09-17-vscode-settings/settings-ui.png)
- Checkbox / Switch input styles are now more in line with other input styles - Checkbox / Switch input styles are now more in line with other input styles
- Available updates are now shown as a clickable button next to your user icon instead of having to open the user menu - Available updates are now shown as a clickable button next to your user icon instead of having to open the user menu

View file

@ -20,6 +20,7 @@ export default class ConfigMenu {
* @property {Boolean} [insertOnTop=false] * @property {Boolean} [insertOnTop=false]
* @property {Boolean} [fireInitialChange=true] * @property {Boolean} [fireInitialChange=true]
* @property {Boolean} [save=true] * @property {Boolean} [save=true]
* @property {validate} [validate]
* @property {showChildren} [showChildren] * @property {showChildren} [showChildren]
* @property {onAppended} [onAppended] * @property {onAppended} [onAppended]
* @property {onChange} [onChange] * @property {onChange} [onChange]
@ -34,6 +35,13 @@ export default class ConfigMenu {
* @property {Boolean} [toggleable=true] * @property {Boolean} [toggleable=true]
*/ */
/**
* @callback validate
* @this {DribbblishConfigItem}
* @param {any} value
* @returns {Boolean | String[]}
*/
/** /**
* @callback showChildren * @callback showChildren
* @this {DribbblishConfigItem} * @this {DribbblishConfigItem}
@ -116,6 +124,7 @@ export default class ConfigMenu {
elem.setAttribute("type", options.type); elem.setAttribute("type", options.type);
if (options.hidden) elem.setAttribute("hidden", true); if (options.hidden) elem.setAttribute("hidden", true);
if (options.childOf) elem.setAttribute("parent", options.childOf); if (options.childOf) elem.setAttribute("parent", options.childOf);
if (options.children.length > 0) elem.setAttribute("children", options.children.map((c) => c.key).join(" "));
elem.innerHTML = /* html */ ` elem.innerHTML = /* html */ `
${ ${
options.name != null && options.description != null options.name != null && options.description != null
@ -177,6 +186,7 @@ export default class ConfigMenu {
insertOnTop: false, insertOnTop: false,
fireInitialChange: true, fireInitialChange: true,
save: true, save: true,
validate: () => true,
showChildren: () => true, showChildren: () => true,
onAppended: () => {}, onAppended: () => {},
onChange: () => {}, onChange: () => {},
@ -205,6 +215,18 @@ export default class ConfigMenu {
this.#config[options.key] = options; this.#config[options.key] = options;
function validate(val) {
const isValid = options.validate.call(options, val);
$(`.dribbblish-config-item[key="${options.key}"]`).attr("invalid", !isValid ? "" : null);
return isValid;
}
const change = (val) => {
if (!validate(val)) return;
this.set(options.key, val, options.save);
options.onChange(val);
};
if (options.type == "checkbox") { if (options.type == "checkbox") {
const input = /* html */ ` const input = /* html */ `
<input id="dribbblish-config-input-${options.key}" class="x-toggle-input" type="checkbox"${this.get(options.key) ? " checked" : ""}> <input id="dribbblish-config-input-${options.key}" class="x-toggle-input" type="checkbox"${this.get(options.key) ? " checked" : ""}>
@ -215,8 +237,7 @@ export default class ConfigMenu {
this.#addInputHTML({ ...options, input }); this.#addInputHTML({ ...options, input });
$(`#dribbblish-config-input-${options.key}`).on("change", (e) => { $(`#dribbblish-config-input-${options.key}`).on("change", (e) => {
this.set(options.key, e.target.checked, options.save); change(e.target.checked);
options.onChange(this.get(options.key));
}); });
} else if (options.type == "select") { } else if (options.type == "select") {
// Validate // Validate
@ -233,8 +254,7 @@ export default class ConfigMenu {
this.#addInputHTML({ ...options, input }); this.#addInputHTML({ ...options, input });
$(`#dribbblish-config-input-${options.key}`).on("change", (e) => { $(`#dribbblish-config-input-${options.key}`).on("change", (e) => {
this.set(options.key, e.target.value, options.save); change(e.target.value);
options.onChange(this.get(options.key));
}); });
} else if (options.type == "button") { } else if (options.type == "button") {
if (typeof options.data != "string") options.data = options.name; if (typeof options.data != "string") options.data = options.name;
@ -257,9 +277,9 @@ export default class ConfigMenu {
} else if (options.type == "number") { } else if (options.type == "number") {
// Validate // Validate
if (options.defaultValue == null) options.defaultValue = 0; if (options.defaultValue == null) options.defaultValue = 0;
const val = this.get(options.key); const _val = this.get(options.key);
if (options.data.min != null && val < options.data.min) this.set(options.key, options.data.min, options.save); if (options.data.min != null && _val < options.data.min) this.set(options.key, options.data.min, options.save);
if (options.data.max != null && val > options.data.max) this.set(options.key, options.data.max, options.save); if (options.data.max != null && _val > options.data.max) this.set(options.key, options.data.max, options.save);
const input = /* html */ ` const input = /* html */ `
<input <input
@ -282,8 +302,7 @@ export default class ConfigMenu {
if (options.data.min != null && e.target.value < options.data.min) e.target.value = options.data.min; if (options.data.min != null && e.target.value < options.data.min) e.target.value = options.data.min;
if (options.data.max != null && e.target.value > options.data.max) e.target.value = options.data.max; if (options.data.max != null && e.target.value > options.data.max) e.target.value = options.data.max;
this.set(options.key, Number(e.target.value), options.save); change(Number(e.target.value));
options.onChange(this.get(options.key));
}); });
} else if (options.type == "text") { } else if (options.type == "text") {
if (options.defaultValue == null) options.defaultValue = ""; if (options.defaultValue == null) options.defaultValue = "";
@ -294,9 +313,10 @@ export default class ConfigMenu {
this.#addInputHTML({ ...options, input }); this.#addInputHTML({ ...options, input });
$(`#dribbblish-config-input-${options.key}`).on("input", (e) => { $(`#dribbblish-config-input-${options.key}`).on("input", (e) => {
// TODO: maybe add an validation function via `data.validate` const val = e.target.value;
this.set(options.key, e.target.value, options.save); if (!validate(val)) return;
options.onChange(this.get(options.key)); this.set(options.key, val, options.save);
options.onChange(val);
}); });
} else if (options.type == "textarea") { } else if (options.type == "textarea") {
if (options.defaultValue == null) options.defaultValue = ""; if (options.defaultValue == null) options.defaultValue = "";
@ -307,9 +327,7 @@ export default class ConfigMenu {
this.#addInputHTML({ ...options, input }); this.#addInputHTML({ ...options, input });
$(`#dribbblish-config-input-${options.key}`).on("input", (e) => { $(`#dribbblish-config-input-${options.key}`).on("input", (e) => {
// TODO: maybe add an validation function via `data.validate` change(e.target.value);
this.set(options.key, e.target.value, options.save);
options.onChange(this.get(options.key));
}); });
} else if (options.type == "slider") { } else if (options.type == "slider") {
// Validate // Validate
@ -336,8 +354,7 @@ export default class ConfigMenu {
$(`#dribbblish-config-input-${options.key}`).attr("tooltip", `${e.target.value}${options.data?.suffix ?? ""}`); $(`#dribbblish-config-input-${options.key}`).attr("tooltip", `${e.target.value}${options.data?.suffix ?? ""}`);
$(`#dribbblish-config-input-${options.key}`).attr("value", e.target.value); $(`#dribbblish-config-input-${options.key}`).attr("value", e.target.value);
this.set(options.key, Number(e.target.value), options.save); change(Number(e.target.value));
options.onChange(this.get(options.key));
}); });
} else if (options.type == "time") { } else if (options.type == "time") {
// Validate // Validate
@ -350,8 +367,7 @@ export default class ConfigMenu {
$(`#dribbblish-config-input-${options.key}`).on("input", (e) => { $(`#dribbblish-config-input-${options.key}`).on("input", (e) => {
$(`#dribbblish-config-input-${options.key}`).attr("value", e.target.value); $(`#dribbblish-config-input-${options.key}`).attr("value", e.target.value);
this.set(options.key, e.target.value, options.save); change(e.target.value);
options.onChange(this.get(options.key));
}); });
} else if (options.type == "color") { } else if (options.type == "color") {
// Validate // Validate
@ -362,8 +378,7 @@ export default class ConfigMenu {
this.#addInputHTML({ ...options, input }); this.#addInputHTML({ ...options, input });
$(`#dribbblish-config-input-${options.key}`).on("input", (e) => { $(`#dribbblish-config-input-${options.key}`).on("input", (e) => {
this.set(options.key, e.target.value, options.save); change(e.target.value);
options.onChange(this.get(options.key));
}); });
} else { } else {
throw new Error(`Config Type "${options.type}" invalid`); throw new Error(`Config Type "${options.type}" invalid`);
@ -372,6 +387,7 @@ export default class ConfigMenu {
// Re-write internal config since some values may have changed // Re-write internal config since some values may have changed
this.#config[options.key] = options; this.#config[options.key] = options;
validate(this.get(options.key));
$(`.dribbblish-config-item[key="${options.key}"]`).attr("changed", options.save && this.get(options.key) != options.defaultValue ? "" : null); $(`.dribbblish-config-item[key="${options.key}"]`).attr("changed", options.save && this.get(options.key) != options.defaultValue ? "" : null);
options.children.forEach((child) => this.register(child)); options.children.forEach((child) => this.register(child));
@ -466,7 +482,32 @@ export default class ConfigMenu {
*/ */
#setHidden(key, hidden) { #setHidden(key, hidden) {
this.#config[key].hidden = hidden; this.#config[key].hidden = hidden;
$(`.dribbblish-config-item[key="${key}"]`).attr("hidden", hidden ? "" : null); const $elem = $(`.dribbblish-config-item[key="${key}"]`);
$elem.attr("hidden", hidden ? "" : null);
// If element has children or a parent
if ($elem.attr("parent") != null || $elem.attr("children") != null) {
// Get parent of element block
const $parent = $elem.attr("parent") != null ? $(`[children~="${key}"]`) : $elem;
const $nextChildren = $parent.nextAll(`[parent="${$parent.attr("key")}"]`);
// Make parent connect on bottom when children are visible
$parent.attr("connect-bottom", $nextChildren.filter(":not([hidden])").length > 0 ? "" : null);
// Reset all children's bottom connection
$nextChildren.each(function () {
$(this).attr("connect-bottom", null);
});
// Add bottom connection to all but the last visible child
$nextChildren
.filter(":not([hidden])")
.slice(0, -1)
.each(function () {
$(this).attr("connect-bottom", "");
});
//* NOTE: All children automatically have a top connection
}
} }
getOptions(key) { getOptions(key) {

View file

@ -46,10 +46,13 @@ $props-to-transition: ("sidebar", "main", "text", "button");
} }
// Color Function // Color Function
@function spiceColor($key, $alpha: 1) { // $light-offset is added when in light mode
@function spiceColor($key, $alpha: 1, $light-offset: 0) {
@if $alpha == 1 { @if $alpha == 1 {
@return var(--spice-#{$key}); @return var(--spice-#{$key});
} @else { } @else if $light-offset == 0 {
@return rgba(var(--spice-rgb-#{$key}), $alpha); @return rgba(var(--spice-rgb-#{$key}), $alpha);
} @else {
@return rgba(var(--spice-rgb-#{$key}), calc($alpha + var(--is_light) * $light-offset));
} }
} }

View file

@ -23,7 +23,7 @@
z-index: 1; z-index: 1;
position: relative; position: relative;
width: clamp(500px, 50%, 650px); width: clamp(500px, 50%, 650px);
background-color: spiceColor("main", 0.9); background-color: spiceColor("main", 0.95);
backdrop-filter: blur(3px); backdrop-filter: blur(3px);
padding: 20px 15px; padding: 20px 15px;
border-radius: var(--main-corner-radius); border-radius: var(--main-corner-radius);
@ -44,22 +44,23 @@
display: flex; display: flex;
width: 100%; width: 100%;
flex-direction: column; flex-direction: column;
gap: 16px; gap: 8px;
max-height: 60vh; max-height: 60vh;
overflow-y: auto; overflow-y: auto;
padding: 0px 50px; padding: 0px 25px;
.dribbblish-config-area { .dribbblish-config-area {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 16px; align-items: center;
gap: 8px;
&[collapsed] { &[collapsed] {
overflow: hidden; overflow: hidden;
min-height: 38px; //for some reason height alone isn't enough min-height: 38px; //for some reason height alone isn't enough
height: 38px; height: 38px;
> h2 svg { .dribbblish-config-area-header svg {
transform: rotate(270deg); transform: rotate(270deg);
} }
} }
@ -71,6 +72,7 @@
.dribbblish-config-area-header { .dribbblish-config-area-header {
position: relative; position: relative;
text-align: center; text-align: center;
width: fit-content;
height: 38px; height: 38px;
display: flex; display: flex;
gap: 10px; gap: 10px;
@ -89,7 +91,8 @@
.dribbblish-config-area-items { .dribbblish-config-area-items {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 16px; gap: 8px;
width: 100%;
.dribbblish-config-item { .dribbblish-config-item {
position: relative; position: relative;
@ -99,23 +102,58 @@
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
gap: 10px; gap: 10px;
padding: 8px 16px;
&[hidden] { &[hidden] {
display: none; display: none;
} }
&[parent] { &::before {
padding-left: 16px; z-index: -1;
}
&[changed]::before {
content: ""; content: "";
position: absolute; position: absolute;
left: -13px; inset: 0px;
top: -3px; border-radius: var(--main-corner-radius);
bottom: -3px; background-color: spiceColor("subtext", 0.03, 0.04);
width: 3px; }
background-color: spiceColor("sidebar");
&[parent]::before {
top: -8px;
border-top-left-radius: 0px;
border-top-right-radius: 0px;
}
&[connect-bottom]::before {
border-bottom-left-radius: 0px;
border-bottom-right-radius: 0px;
}
&[invalid]::before {
background-color: rgba(red, 0.2);
}
&[changed] {
&::after {
content: "";
position: absolute;
left: 0px;
top: 0px;
bottom: 0px;
width: 5px;
background-color: spiceColor("text");
border-top-left-radius: var(--main-corner-radius);
border-bottom-left-radius: var(--main-corner-radius);
}
&[parent]::after {
top: -4px;
border-top-left-radius: 0px;
}
&[connect-bottom]::after {
bottom: -4px;
border-bottom-left-radius: 0px;
}
} }
.dribbblish-config-item-header { .dribbblish-config-item-header {