@@ -52,10 +50,6 @@ export default {
displayLayout: String,
iconSize: String,
externalThemes: Object,
- appConfig: Object,
- pageInfo: Object,
- sections: Array,
- modalOpen: Boolean,
},
components: {
SearchBar,
@@ -69,7 +63,43 @@ export default {
IconOpen,
IconClose,
},
- inject: ['visibleComponents'],
+ data() {
+ return {
+ settingsVisible: true,
+ };
+ },
+ computed: {
+ sections() {
+ return this.$store.getters.sections;
+ },
+ appConfig() {
+ return this.$store.getters.appConfig;
+ },
+ pageInfo() {
+ return this.$store.getters.pageInfo;
+ },
+ /**
+ * Determines which button should display, based on the user type
+ * 0 = Auth not configured, don't show anything
+ * 1 = Auth condifured, and user logged in, show logout button
+ * 2 = Auth configured, guest access enabled, and not logged in, show login
+ * Note that if auth is enabled, but not guest access, and user not logged in,
+ * then they will never be able to view the homepage, so no button needed
+ */
+ userState() {
+ return getUserState();
+ },
+ /* Object indicating which components should be hidden, based on user preferences */
+ visibleComponents() {
+ return this.$store.getters.visibleComponents;
+ },
+ searchVisible() {
+ return this.$store.getters.visibleComponents.searchBar;
+ },
+ },
+ mounted() {
+ this.settingsVisible = this.getSettingsVisibility();
+ },
methods: {
userIsTypingSomething(something) {
this.$emit('user-is-searchin', something);
@@ -83,9 +113,6 @@ export default {
updateIconSize(iconSize) {
this.$emit('change-icon-size', iconSize);
},
- modalChanged(changedTo) {
- this.$emit('change-modal-visibility', changedTo);
- },
getInitialTheme() {
return this.appConfig.theme || '';
},
@@ -104,25 +131,6 @@ export default {
|| (this.visibleComponents || defaultVisibleComponents).settings);
},
},
- computed: {
- /**
- * Determines which button should display, based on the user type
- * 0 = Auth not configured, don't show anything
- * 1 = Auth condifured, and user logged in, show logout button
- * 2 = Auth configured, guest access enabled, and not logged in, show login
- * Note that if auth is enabled, but not guest access, and user not logged in,
- * then they will never be able to view the homepage, so no button needed
- */
- userState() {
- return getUserState();
- },
- },
- data() {
- return {
- settingsVisible: this.getSettingsVisibility(),
- searchVisible: (this.visibleComponents || defaultVisibleComponents).searchBar,
- };
- },
};
diff --git a/src/components/Settings/ThemeSelector.vue b/src/components/Settings/ThemeSelector.vue
index 7151aba3d1..8009241cf9 100644
--- a/src/components/Settings/ThemeSelector.vue
+++ b/src/components/Settings/ThemeSelector.vue
@@ -31,6 +31,7 @@ import {
ApplyCustomVariables,
} from '@/utils/ThemeHelper';
import Defaults, { localStorageKeys } from '@/utils/defaults';
+import Keys from '@/utils/StoreMutations';
import IconPalette from '@/assets/interface-icons/config-color-palette.svg';
export default {
@@ -94,13 +95,15 @@ export default {
},
/* Opens the theme color configurator popup */
openThemeConfigurator() {
- this.$emit('modalChanged', true);
+ this.$store.commit(Keys.SET_MODAL_OPEN, true);
this.themeConfiguratorOpen = true;
},
/* Closes the theme color configurator popup */
closeThemeConfigurator() {
- // this.$emit('modalChanged', false);
- this.themeConfiguratorOpen = false;
+ if (this.themeConfiguratorOpen) {
+ this.$store.commit(Keys.SET_MODAL_OPEN, false);
+ this.themeConfiguratorOpen = false;
+ }
},
/* Updates theme. Checks if the new theme is local or external,
and calls appropirate updating function. Updates local storage */
diff --git a/src/components/Workspace/SideBar.vue b/src/components/Workspace/SideBar.vue
index 0c8fa5a453..cc81fa3bc6 100644
--- a/src/components/Workspace/SideBar.vue
+++ b/src/components/Workspace/SideBar.vue
@@ -36,7 +36,6 @@ import IconMinimalView from '@/assets/interface-icons/application-minimal.svg';
export default {
name: 'SideBar',
- inject: ['config'],
props: {
sections: Array,
initUrl: String,
diff --git a/src/components/Workspace/SideBarItem.vue b/src/components/Workspace/SideBarItem.vue
index da5508c6d1..90319afe72 100644
--- a/src/components/Workspace/SideBarItem.vue
+++ b/src/components/Workspace/SideBarItem.vue
@@ -12,7 +12,6 @@ import Icon from '@/components/LinkItems/ItemIcon.vue';
export default {
name: 'SideBarItem',
- inject: ['config'],
props: {
icon: String,
title: String,
diff --git a/src/components/Workspace/SideBarSection.vue b/src/components/Workspace/SideBarSection.vue
index e2f8f8ff43..778a6cffc1 100644
--- a/src/components/Workspace/SideBarSection.vue
+++ b/src/components/Workspace/SideBarSection.vue
@@ -19,7 +19,6 @@ import SideBarItem from '@/components/Workspace/SideBarItem.vue';
export default {
name: 'SideBarSection',
- inject: ['config'],
props: {
items: Array,
},
diff --git a/src/main.js b/src/main.js
index a5d4c7646d..f1a7f1a023 100644
--- a/src/main.js
+++ b/src/main.js
@@ -14,6 +14,7 @@ import Toasted from 'vue-toasted'; // Toast component, used to show confirm
// Import base Dashy components and utils
import Dashy from '@/App.vue'; // Main Dashy Vue app
import router from '@/router'; // Router, for navigation
+import store from '@/store'; // Store, for local state management
import serviceWorker from '@/utils/InitServiceWorker'; // Service worker initialization
import clickOutside from '@/utils/ClickOutside'; // Directive for closing popups, modals, etc
import { messages } from '@/utils/languages'; // Language texts
@@ -48,9 +49,14 @@ ErrorReporting(Vue, router);
// Render function
const render = (awesome) => awesome(Dashy);
+// Mount the app, with router, store i18n and render func
+const mount = () => new Vue({
+ store, router, render, i18n,
+}).$mount('#app');
+
// If Keycloak not enabled, then proceed straight to the app
if (!isKeycloakEnabled()) {
- new Vue({ router, render, i18n }).$mount('#app');
+ mount();
} else { // Keycloak is enabled, redirect to KC login page
const { serverUrl, realm, clientId } = getKeycloakConfig();
const initOptions = {
@@ -63,7 +69,7 @@ if (!isKeycloakEnabled()) {
window.location.reload();
} else {
// Yay - user successfully authenticated with Keycloak, render the app!
- new Vue({ router, render, i18n }).$mount('#app');
+ mount();
}
});
}
diff --git a/src/router.js b/src/router.js
index e76efe4252..d0cf8f9ef8 100644
--- a/src/router.js
+++ b/src/router.js
@@ -14,10 +14,10 @@ import Home from '@/views/Home.vue';
import Login from '@/views/Login.vue';
import Workspace from '@/views/Workspace.vue';
import Minimal from '@/views/Minimal.vue';
+import ConfigAccumulator from '@/utils/ConfigAccumalator';
// Import helper functions, config data and defaults
import { isAuthEnabled, isLoggedIn, isGuestAccessEnabled } from '@/utils/Auth';
-import { config } from '@/utils/ConfigHelpers';
import { metaTagData, startingView, routePaths } from '@/utils/defaults';
import ErrorHandler from '@/utils/ErrorHandler';
@@ -32,8 +32,18 @@ const isAuthenticated = () => {
return (!authEnabled || userLoggedIn || guestEnabled);
};
+const getConfig = () => {
+ const Accumulator = new ConfigAccumulator();
+ return {
+ appConfig: Accumulator.appConfig(),
+ pageInfo: Accumulator.pageInfo(),
+ };
+};
+
+const { appConfig, pageInfo } = getConfig();
+
/* Get the users chosen starting view from app config, or return default */
-const getStartingView = () => config.appConfig.startingView || startingView;
+const getStartingView = () => appConfig.startingView || startingView;
/**
* Returns the component that should be rendered at the base path,
@@ -51,7 +61,7 @@ const getStartingComponent = () => {
/* Returns the meta tags for each route */
const makeMetaTags = (defaultTitle) => ({
- title: config.pageInfo.title || defaultTitle,
+ title: pageInfo.title || defaultTitle,
metaTags: metaTagData,
});
@@ -62,37 +72,30 @@ const router = new Router({
path: '/',
name: `landing-page-${getStartingView()}`,
component: getStartingComponent(),
- props: config,
meta: makeMetaTags('Home Page'),
},
{ // Default home page
path: routePaths.home,
name: 'home',
component: Home,
- props: config,
meta: makeMetaTags('Home Page'),
},
{ // Workspace view page
path: routePaths.workspace,
name: 'workspace',
component: Workspace,
- props: config,
meta: makeMetaTags('Workspace'),
},
{ // Minimal view page
path: routePaths.minimal,
name: 'minimal',
component: Minimal,
- props: config,
meta: makeMetaTags('Start Page'),
},
{ // The login page
path: routePaths.login,
name: 'login',
component: Login,
- props: {
- appConfig: config.appConfig,
- },
beforeEnter: (to, from, next) => {
// If the user already logged in + guest mode not enabled, then redirect home
if (isAuthenticated() && !isGuestAccessEnabled()) router.push({ path: '/' });
@@ -109,7 +112,6 @@ const router = new Router({
path: routePaths.download,
name: 'download',
component: () => import('./views/DownloadConfig.vue'),
- props: config,
meta: makeMetaTags('Download Config'),
},
{ // Page not found, any non-defined routes will land here
diff --git a/src/store.js b/src/store.js
new file mode 100644
index 0000000000..306c80ef61
--- /dev/null
+++ b/src/store.js
@@ -0,0 +1,61 @@
+/* eslint-disable no-param-reassign */
+import Vue from 'vue';
+import Vuex from 'vuex';
+import Keys from '@/utils/StoreMutations';
+import ConfigAccumulator from '@/utils/ConfigAccumalator';
+import { componentVisibility } from '@/utils/ConfigHelpers';
+import filterUserSections from '@/utils/CheckSectionVisibility';
+
+Vue.use(Vuex);
+
+const { UPDATE_CONFIG, SET_MODAL_OPEN, SET_LANGUAGE } = Keys;
+
+const store = new Vuex.Store({
+ state: {
+ config: {},
+ lang: '', // The users language, auto-detected or read from local storage / config
+ modalOpen: false, // KB shortcut functionality will be disabled when modal is open
+ },
+ getters: {
+ config(state) {
+ return state.config;
+ },
+ pageInfo(state) {
+ return state.config.pageInfo || {};
+ },
+ appConfig(state) {
+ return state.config.appConfig || {};
+ },
+ sections(state) {
+ return filterUserSections(state.config.sections || []);
+ },
+ webSearch(state, getters) {
+ return getters.appConfig.webSearch || {};
+ },
+ visibleComponents(state, getters) {
+ return componentVisibility(getters.appConfig);
+ },
+ },
+ mutations: {
+ [UPDATE_CONFIG](state, config) {
+ state.config = config;
+ },
+ [SET_LANGUAGE](state, lang) {
+ state.lang = lang;
+ },
+ [SET_MODAL_OPEN](state, modalOpen) {
+ state.modalOpen = modalOpen;
+ },
+ },
+ actions: {
+ /* Called when app first loaded. Reads config and sets state */
+ initializeConfig({ commit }) {
+ const Accumulator = new ConfigAccumulator();
+ const config = Accumulator.config();
+ commit(UPDATE_CONFIG, config);
+ },
+ },
+ modules: {},
+});
+
+export default store;
diff --git a/src/utils/Auth.js b/src/utils/Auth.js
index 49cc5ae239..a1e8fdbda6 100644
--- a/src/utils/Auth.js
+++ b/src/utils/Auth.js
@@ -16,9 +16,7 @@ const getAppConfig = () => {
* Support for old user structure will be removed in V 1.7.0
*/
const printWarning = () => {
- const msg = 'From V 1.6.5 onwards, the structure of the users object has changed.';
- // eslint-disable-next-line no-console
- console.warn(msg);
+ ErrorHandler('From V 1.6.5 onwards, the structure of the users object has changed.');
};
/* Returns true if keycloak is enabled */
diff --git a/src/utils/ConfigAccumalator.js b/src/utils/ConfigAccumalator.js
index 594cda79d3..ca6bc0757f 100644
--- a/src/utils/ConfigAccumalator.js
+++ b/src/utils/ConfigAccumalator.js
@@ -11,9 +11,8 @@ import {
pageInfo as defaultPageInfo,
iconSize as defaultIconSize,
layout as defaultLayout,
- // language as defaultLanguage,
} from '@/utils/defaults';
-
+import ErrorHandler from '@/utils/ErrorHandler';
import conf from '../../public/conf.yml';
export default class ConfigAccumulator {
@@ -46,24 +45,14 @@ export default class ConfigAccumulator {
/* Page Info */
pageInfo() {
- const defaults = defaultPageInfo;
- let localPageInfo;
- try {
- localPageInfo = JSON.parse(localStorage[localStorageKeys.PAGE_INFO]);
- } catch (e) {
- localPageInfo = {};
- }
- let filePageInfo = {};
- if (this.conf) {
- filePageInfo = this.conf.pageInfo || {};
+ let localPageInfo = {};
+ if (localStorage[localStorageKeys.PAGE_INFO]) {
+ // eslint-disable-next-line brace-style
+ try { localPageInfo = JSON.parse(localStorage[localStorageKeys.PAGE_INFO]); }
+ catch (e) { ErrorHandler('Malformed pageInfo data in local storage'); }
}
- const pi = filePageInfo || defaults; // The page info object to return
- pi.title = localPageInfo.title || filePageInfo.title || defaults.title;
- pi.logo = localPageInfo.logo || filePageInfo.logo || defaults.logo;
- pi.description = localPageInfo.description || filePageInfo.description || defaults.description;
- pi.navLinks = localPageInfo.navLinks || filePageInfo.navLinks || defaults.navLinks;
- pi.footerText = localPageInfo.footerText || filePageInfo.footerText || defaults.footerText;
- return pi;
+ const filePageInfo = this.conf ? this.conf.pageInfo || {} : {};
+ return { ...defaultPageInfo, ...filePageInfo, ...localPageInfo };
}
/* Sections */
@@ -75,13 +64,11 @@ export default class ConfigAccumulator {
const json = JSON.parse(localSections);
if (json.length >= 1) return json;
} catch (e) {
- // The data in local storage has been malformed, will return conf.sections instead
+ ErrorHandler('Malformed section data in local storage');
}
}
// If the function hasn't yet returned, then return the config file sections
- let sectionsFile = [];
- if (this.conf) sectionsFile = this.conf.sections || [];
- return sectionsFile;
+ return this.conf ? this.conf.sections || [] : [];
}
/* Complete config */
diff --git a/src/utils/StoreMutations.js b/src/utils/StoreMutations.js
new file mode 100644
index 0000000000..f5ab815fb4
--- /dev/null
+++ b/src/utils/StoreMutations.js
@@ -0,0 +1,11 @@
+// A list of mutation names
+const KEY_NAMES = [
+ 'UPDATE_CONFIG',
+ 'SET_MODAL_OPEN',
+ 'SET_LANGUAGE',
+];
+
+// Convert array of key names into an object, and export
+const MUTATIONS = {};
+KEY_NAMES.forEach((key) => { MUTATIONS[key] = key; });
+export default MUTATIONS;
diff --git a/src/views/DownloadConfig.vue b/src/views/DownloadConfig.vue
index 9a6950526a..ef3bf58c5e 100644
--- a/src/views/DownloadConfig.vue
+++ b/src/views/DownloadConfig.vue
@@ -7,18 +7,13 @@ import JsonToYaml from '@/utils/JsonToYaml';
export default {
name: 'DownloadConfig',
- props: {
- sections: Array,
- appConfig: Object,
- pageInfo: Object,
+ computed: {
+ config() {
+ return this.$store.state.config;
+ },
},
data() {
return {
- config: {
- appConfig: this.appConfig,
- pageInfo: this.pageInfo,
- sections: this.sections,
- },
jsonParser: JsonToYaml,
};
},
diff --git a/src/views/Home.vue b/src/views/Home.vue
index 6c36bb2ff7..ec12c72b57 100644
--- a/src/views/Home.vue
+++ b/src/views/Home.vue
@@ -10,9 +10,6 @@
:displayLayout="layout"
:iconSize="itemSizeBound"
:externalThemes="getExternalCSSLinks()"
- :sections="allSections"
- :appConfig="appConfig"
- :pageInfo="pageInfo"
:modalOpen="modalOpen"
class="settings-outer"
/>
@@ -55,11 +52,6 @@ import Defaults, { localStorageKeys, iconCdns } from '@/utils/defaults';
export default {
name: 'home',
- props: {
- sections: Array, // Main site content
- appConfig: Object, // Main site configuation (optional)
- pageInfo: Object, // Page metadata (optional)
- },
components: {
SettingsContainer,
Section,
@@ -68,9 +60,20 @@ export default {
searchValue: '',
layout: '',
itemSizeBound: '',
- modalOpen: false, // When true, keybindings are disabled
}),
computed: {
+ sections() {
+ return this.$store.getters.sections;
+ },
+ appConfig() {
+ return this.$store.getters.appConfig;
+ },
+ pageInfo() {
+ return this.$store.getters.pageInfo;
+ },
+ modalOpen() {
+ return this.$store.state.modalOpen;
+ },
/* Get class for num columns, if specified by user */
colCount() {
let { colCount } = this.appConfig;
@@ -143,7 +146,7 @@ export default {
},
/* Update data when modal is open (so that key bindings can be disabled) */
updateModalVisibility(modalState) {
- this.modalOpen = modalState;
+ this.$store.commit('SET_MODAL_OPEN', modalState);
},
/* Returns an array of links to external CSS from the Config */
getExternalCSSLinks() {
diff --git a/src/views/Login.vue b/src/views/Login.vue
index 270302083e..cea52489f7 100644
--- a/src/views/Login.vue
+++ b/src/views/Login.vue
@@ -91,9 +91,6 @@ export default {
Button,
Input,
},
- props: {
- appConfig: Object,
- },
data() {
return {
username: '',
@@ -104,6 +101,9 @@ export default {
};
},
computed: {
+ appConfig() {
+ return this.$store.getters.appConfig;
+ },
/* Data for timeout dropdown menu, translated label + value in ms */
dropDownMenu() {
return [
diff --git a/src/views/Minimal.vue b/src/views/Minimal.vue
index ba3e49c6bf..9f76020a16 100644
--- a/src/views/Minimal.vue
+++ b/src/views/Minimal.vue
@@ -2,8 +2,7 @@
-
+
@@ -62,11 +61,6 @@ import ConfigLauncher from '@/components/Settings/ConfigLauncher';
export default {
name: 'home',
- props: {
- sections: Array, // Main site content
- appConfig: Object, // Main site configuation (optional)
- pageInfo: Object,
- },
components: {
MinimalSection,
MinimalHeading,
@@ -81,6 +75,17 @@ export default {
tabbedView: true, // By default use tabs, when searching then show all instead
theme: GetTheme(),
}),
+ computed: {
+ sections() {
+ return this.$store.getters.sections;
+ },
+ appConfig() {
+ return this.$store.getters.appConfig;
+ },
+ pageInfo() {
+ return this.$store.getters.pageInfo;
+ },
+ },
watch: {
/* When the theme changes, then call the update method */
searchValue() {
diff --git a/src/views/Workspace.vue b/src/views/Workspace.vue
index c23eb98fa2..ea84c04255 100644
--- a/src/views/Workspace.vue
+++ b/src/views/Workspace.vue
@@ -16,10 +16,6 @@ import { GetTheme, ApplyLocalTheme, ApplyCustomVariables } from '@/utils/ThemeHe
export default {
name: 'Workspace',
- props: {
- sections: Array,
- appConfig: Object,
- },
data: () => ({
url: '',
GetTheme,
@@ -27,6 +23,12 @@ export default {
ApplyCustomVariables,
}),
computed: {
+ sections() {
+ return this.$store.getters.sections;
+ },
+ appConfig() {
+ return this.$store.getters.appConfig;
+ },
isMultiTaskingEnabled() {
return this.appConfig.enableMultiTasking || false;
},
diff --git a/yarn.lock b/yarn.lock
index edca2bb280..fbd2289f21 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -9866,6 +9866,11 @@ vue@^2.6.10:
resolved "https://registry.yarnpkg.com/vue/-/vue-2.6.14.tgz#e51aa5250250d569a3fbad3a8a5a687d6036e235"
integrity sha512-x2284lgYvjOMj3Za7kqzRcUSxBboHqtgRE2zlos1qWaOye5yUmHn42LB1250NJBLRwEcdrB0JRwyPTEPhfQjiQ==
+vuex@^3.6.2:
+ version "3.6.2"
+ resolved "https://registry.yarnpkg.com/vuex/-/vuex-3.6.2.tgz#236bc086a870c3ae79946f107f16de59d5895e71"
+ integrity sha512-ETW44IqCgBpVomy520DT5jf8n0zoCac+sxWnn+hMe/CzaSejb/eVw2YToiXYX+Ex/AuHHia28vWTq4goAexFbw==
+
watchpack-chokidar2@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/watchpack-chokidar2/-/watchpack-chokidar2-2.0.1.tgz#38500072ee6ece66f3769936950ea1771be1c957"