Compare commits

...

4 Commits

Author SHA1 Message Date
Alicia Sykes 3416615d30 Merge branch 'FEAT/3.0.1-improvements' of github.com:lissy93/dashy into FEAT/3.0.1-improvements 2024-04-27 01:01:48 +01:00
Alicia Sykes db63362327 💬 Translate critical error text, and update styles 2024-04-27 00:45:30 +01:00
Alicia Sykes 9e6fb17d93 🥅 Catch error caused by empty config 2024-04-27 00:32:01 +01:00
Alicia Sykes 4594c99b57 🐛 Fix collapse state persistence (#1546) 2024-04-26 00:29:35 +01:00
9 changed files with 61 additions and 19 deletions

View File

@ -312,6 +312,14 @@
"view-title": "View Config"
}
},
"critical-error": {
"title": "Configuration Load Error",
"subtitle": "Dashy has failed to load correctly due to a configuration error.",
"sub-ensure-that": "Ensure that",
"sub-error-details": "Error Details",
"sub-next-steps": "Next Steps",
"ignore-button": "Ignore Critical Errors"
},
"widgets": {
"general": {
"loading": "Loading...",

View File

@ -116,7 +116,8 @@ export default {
},
mounted() {
const jsonData = { ...this.config };
jsonData.sections = jsonData.sections.map(({ filteredItems, ...section }) => section);
jsonData.sections = (jsonData.sections || []).map(({ filteredItems, ...section }) => section);
if (!jsonData.pageInfo) jsonData.pageInfo = { title: 'Dashy' };
this.jsonData = jsonData;
if (!this.allowWriteToDisk) this.saveMode = 'local';
},

View File

@ -64,7 +64,6 @@ export default {
return this.$store.state.editMode;
},
sectionKey() {
if (this.isEditMode) return undefined;
return `collapsible-${this.uniqueKey}`;
},
collapseClass() {
@ -104,12 +103,23 @@ export default {
watch: {
checkboxState(newState) {
this.isExpanded = newState;
this.updateLocalStorage(); // Save every change immediately
},
uniqueKey() {
this.checkboxState = this.isExpanded;
uniqueKey(newVal, oldVal) {
if (newVal !== oldVal) {
this.refreshCollapseState(); // Refresh state when key changes
}
},
},
methods: {
refreshCollapseState() {
this.checkboxState = this.isExpanded;
},
updateLocalStorage() {
const collapseState = this.locallyStoredCollapseStates();
collapseState[this.uniqueKey] = this.checkboxState;
localStorage.setItem(localStorageKeys.COLLAPSE_STATE, JSON.stringify(collapseState));
},
/* Either expand or collapse section, based on it's current state */
toggle() {
this.checkboxState = !this.checkboxState;

View File

@ -1,19 +1,17 @@
<template>
<div class="critical-error-wrap" v-if="shouldShow">
<button class="close" title="Close Warning" @click="close">🗙</button>
<h3>Configuration Load Error</h3>
<p>
Dashy has failed to load correctly due to a configuration error.
</p>
<h4>Ensure that</h4>
<h3>{{ $t('critical-error.title') }}</h3>
<p>{{ $t('critical-error.subtitle') }}</p>
<h4>{{ $t('critical-error.sub-ensure-that') }}</h4>
<ul>
<li>The configuration file can be found at the specified location</li>
<li>There are no CORS rules preventing client-side access</li>
<li>The YAML is valid, parsable and matches the schema</li>
</ul>
<h4>Error Details</h4>
<h4>{{ $t('critical-error.sub-error-details') }}</h4>
<pre>{{ this.$store.state.criticalError }}</pre>
<h4>Next Steps</h4>
<h4>{{ $t('critical-error.sub-next-steps') }}</h4>
<ul>
<li>Check the browser console for more details
(<a href="https://github.com/Lissy93/dashy/blob/master/docs/troubleshooting.md#how-to-open-browser-console">see how</a>)
@ -29,7 +27,9 @@
</li>
<li>Click 'Ignore Critical Errors' below to not show this warning again</li>
</ul>
<button class="user-doesnt-care" @click="ignoreWarning">Ignore Critical Errors</button>
<button class="user-doesnt-care" @click="ignoreWarning">
{{ $t('critical-error.ignore-button') }}
</button>
</div>
</template>
@ -69,6 +69,7 @@ export default {
left: 50%;
transform: translate(-50%, -50%);
z-index: 3;
max-width: 50rem;
background: var(--background-darker);
padding: 1rem;
border-radius: var(--curve-factor);

View File

@ -28,7 +28,7 @@ const HomeMixin = {
return this.$store.state.modalOpen;
},
pageId() {
return (this.subPageInfo && this.subPageInfo.pageId) ? this.subPageInfo.pageId : 'home';
return this.$store.state.currentConfigInfo?.confId || 'home';
},
},
data: () => ({
@ -84,6 +84,14 @@ const HomeMixin = {
searching(searchValue) {
this.searchValue = searchValue || '';
},
/* Returns a unique ID based on the page and section name */
makeSectionId(section) {
const normalize = (str) => (
str ? str.trim().toLowerCase().replace(/[^a-zA-Z0-9]/g, '-')
: `unnamed-${(`000${Math.floor(Math.random() * 1000)}`).slice(-3)}`
);
return `${this.pageId || 'unknown-page'}-${normalize(section.name)}`;
},
/* Returns true if there is one or more sections in the config */
checkTheresData(sections) {
const localSections = localStorage[localStorageKeys.CONF_SECTIONS];

View File

@ -44,6 +44,12 @@ const {
CRITICAL_ERROR_MSG,
} = Keys;
const emptyConfig = {
appConfig: {},
pageInfo: { title: 'Dashy' },
sections: [],
};
const store = new Vuex.Store({
state: {
config: {}, // The current config being used, and rendered to the UI
@ -335,7 +341,7 @@ const store = new Vuex.Store({
data = yaml.load(response.data);
} catch (parseError) {
commit(CRITICAL_ERROR_MSG, `Failed to parse YAML: ${parseError.message}`);
return {};
return { ...emptyConfig };
}
// Replace missing root properties with empty objects
if (!data.appConfig) data.appConfig = {};
@ -357,7 +363,7 @@ const store = new Vuex.Store({
} else {
commit(CRITICAL_ERROR_MSG, `Failed to fetch configuration: ${fetchError.message}`);
}
return {};
return { ...emptyConfig };
}
},
/**
@ -368,6 +374,7 @@ const store = new Vuex.Store({
*/
async [INITIALIZE_CONFIG]({ commit, state }, subConfigId) {
const rootConfig = state.rootConfig || await this.dispatch(Keys.INITIALIZE_ROOT_CONFIG);
commit(SET_IS_USING_LOCAL_CONFIG, false);
if (!subConfigId) { // Use root config as config
commit(SET_CONFIG, rootConfig);
@ -396,7 +403,7 @@ const store = new Vuex.Store({
if (!subConfigPath) {
commit(CRITICAL_ERROR_MSG, `Unable to find config for '${subConfigId}'`);
return null;
return { ...emptyConfig };
}
axios.get(subConfigPath).then((response) => {
@ -428,7 +435,7 @@ const store = new Vuex.Store({
commit(CRITICAL_ERROR_MSG, `Unable to load config from '${subConfigPath}'`, err);
});
}
return null;
return { ...emptyConfig };
},
},
modules: {},

View File

@ -1717,6 +1717,7 @@ html[data-theme='neomorphic'] {
.config-buttons > svg,
.display-options svg,
form.minimal input,
.critical-error-wrap button.user-doesnt-care,
a.config-button, button.config-button {
border-radius: 0.35rem;
box-shadow: var(--glass-button-shadow);
@ -1724,6 +1725,7 @@ html[data-theme='neomorphic'] {
border: 1px solid rgba(255, 255, 255, 0.19);
background: rgba(255, 255, 255, 0.15);
transition: all 0.2s ease-in-out;
text-decoration: none;
&:hover, &.selected {
box-shadow: var(--glass-button-hover-shadow);
border: 1px solid rgba(255, 255, 255, 0.25) !important;
@ -1791,6 +1793,11 @@ html[data-theme='neomorphic'] {
background: rgba(255, 255, 255, 0.15);
backdrop-filter: blur(50px);
}
.critical-error-wrap {
backdrop-filter: blur(15px);
background: #0f0528c4;
}
}
html[data-theme='glass'] {

View File

@ -34,7 +34,7 @@
:title="section.name"
:icon="section.icon || undefined"
:displayData="getDisplayData(section)"
:groupId="`${pageId}-section-${index}`"
:groupId="makeSectionId(section)"
:items="section.filteredItems"
:widgets="section.widgets"
:searchTerm="searchValue"

View File

@ -34,7 +34,7 @@
:index="index"
:title="section.name"
:icon="section.icon || undefined"
:groupId="`section-${index}`"
:groupId="makeSectionId(section)"
:items="filterTiles(section.items)"
:widgets="section.widgets"
:selected="selectedSection === index"