🔀 Merge pull request #271 from Lissy93/ARCH/implement-vuex-state

[ARCH] Implement VueX State Management
This commit is contained in:
Alicia Sykes 2021-10-10 18:39:10 +01:00 committed by GitHub
commit 7f555ee142
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 408 additions and 216 deletions

View File

@ -1,5 +1,8 @@
# Changelog # Changelog
## ⚡️ 1.8.6 - Implementation of VueX [PR: #271](https://github.com/Lissy93/dashy/pull/271)
- New state management pattern, which should lead to a more organized code base long term, and will also make building out the new UI editor significantly easier to do in a clean and reliable way
## 💄 1.8.5 - Lots of Requested UI Improvements [PR #261](https://github.com/Lissy93/dashy/pull/261) ## 💄 1.8.5 - Lots of Requested UI Improvements [PR #261](https://github.com/Lissy93/dashy/pull/261)
- Adds an option for landing URL in workspace, Re: #255 - Adds an option for landing URL in workspace, Re: #255
- Switches to a new API for generative icons, Re: #163 - Switches to a new API for generative icons, Re: #163

69
.github/pr-badge.yml vendored
View File

@ -1,5 +1,5 @@
# Config file for pull-request-badge. See: https://pullrequestbadge.com/ # Config file for pull-request-badge. See: https://pullrequestbadge.com/ by @stefanbuck
# Enables badges to be inserted into the PR description, based on certain conditions # Dynamically inserts status badges into PR description, based on certain conditions
# Checks if the required sections are missing # Checks if the required sections are missing
- label: "⚠Missing" - label: "⚠Missing"
@ -23,21 +23,43 @@
color: "#f25265" color: "#f25265"
when: "$labels.length == 0" when: "$labels.length == 0"
# Show note when in draft mode # Show note when task list has unfinished items
- label: "⚠Notice"
message: "Unchecked Tasks"
when: "$payload.pull_request.body.includes('- [ ] ')"
color: "#f25265"
# Show badge indicating PR status
- label: "Status" - label: "Status"
message: "Draft" message: "✏️ Draft"
when: "$isDraft" when: "$isDraft"
color: "#ffa933" color: "#ffa933"
- label: "Status"
message: "🧱 Work in Progress"
when: "$payload.pull_request.title.includes('WIP')"
color: "#29e3f4"
- label: "Status"
message: "✅ Ready"
color: "#3ef963"
when: "$labels.includes('🔀 Ready for Merge')"
# Add size label based on very large or tiny PRs # Add size label based on very large or tiny PRs
- label: "PR Size"
message: "Extra Large"
color: "#f9833e"
when: "$additions > 1000"
- label: "PR Size" - label: "PR Size"
message: "Large" message: "Large"
color: "#f79c47" color: "#f4b546"
when: "$additions > 600" when: "$additions > 500 && $additions < 1000"
- label: "PR Size"
message: "Medium"
color: "#f3ff59"
when: "$additions > 10 && $additions < 500"
- label: "PR Size" - label: "PR Size"
message: "Quick" message: "Quick"
color: "#3eef8b" color: "#3eef8b"
when: "$additions < 5" when: "$additions < 10"
# Show PR number, to destination and from destination # Show PR number, to destination and from destination
- label: "#$prNumber" - label: "#$prNumber"
@ -57,7 +79,7 @@
when: "$payload.pull_request.author_association !== 'OWNER'" when: "$payload.pull_request.author_association !== 'OWNER'"
url: "https://github.com/$payload.pull_request.user.login" url: "https://github.com/$payload.pull_request.user.login"
# Show a badge indicating the PR category # Show a badge indicating the PR category, based on tag
- label: "Type" - label: "Type"
message: "✨ Feature" message: "✨ Feature"
color: "#39b0fd" color: "#39b0fd"
@ -90,3 +112,34 @@
message: "🌟 Showcase Addition" message: "🌟 Showcase Addition"
color: "#39b0fd" color: "#39b0fd"
when: "$labels.includes('💯 Showcase')" when: "$labels.includes('💯 Showcase')"
- label: "Type"
message: "🏗️ Architecture"
color: "#39b0fd"
when: "$labels.includes('🏗️ Architectural Changes')"
- label: "Type"
message: "🤖 Auto Submission"
color: "#39b0fd"
when: "$labels.includes('🤖 Auto')"
- label: "Type"
message: "🌐 Language Update"
color: "#39b0fd"
when: "$labels.includes('🌐 Language')"
# Show warning, when certain tags are applied
- label: "Warning"
message: "⛔ Do Not Merge"
color: "#f25265"
when: "$labels.includes('⛔ Don't Merge')"
- label: "Warning"
message: "🚫 Merge Conflicts"
color: "#f25265"
when: "$labels.includes('🚫 Merge Conflicts')"
- label: "Warning"
message: "🕸️ Inactive"
color: "#f25265"
when: "$labels.includes('🕸️ Inactive')"
- label: "Warning"
message: "💀 Spam"
color: "#f25265"
when: "$labels.includes('💀 Spam')"

View File

@ -1,10 +1,11 @@
# PR labels and the branch patterns they should be auto-assigned to # PR labels and the branch patterns they should be auto-assigned to
✨ New Feature: ['FEATURE/*', 'FEAT/*']
🦋 Bug Fix: ['FIX/*', 'HOT-FIX/*', 'BUG-FIX/*']
✨ New Feature: ['FEATURE/*']
🚚 Refactor: ['IMPROVMENTS/*', 'REFACTOR/*'] 🚚 Refactor: ['IMPROVMENTS/*', 'REFACTOR/*']
💯 Showcase: ['SHOWCASE/*'] 🦋 Bug Fix: ['FIX/*', 'HOT-FIX/*', 'BUG-FIX/*']
💄 Stylistic Changes: ['STYLES/*', 'THEME/*'] 💯 Showcase: ['SHOWCASE/*', 'SHOWCASE_SUBMISSION/*']
🛠️ Build Changes: ['ARCH/*', 'ARCHITECTURE/*', 'DOCKER/*', 'BUILD/*'] 💄 Stylistic Changes: ['STYLES/*', 'THEME/*', 'UI/*']
🏗️ Architectural Changes: ['ARCH/*', 'ARCHITECTURE/*']
🛠️ Build Changes: ['DOCKER/*', 'BUILD/*', 'CI/*', 'ACTIONS/*']
🌐 Language: ['LANG/*', 'INTERNATIONALIZATION/*', 'I18N/*', 'TEXT-UPDATE/*']
🤖 Auto: ['AUTO/*', 'BOT/*', 'snyk-upgrade-*', 'snyk-fix-*'] 🤖 Auto: ['AUTO/*', 'BOT/*', 'snyk-upgrade-*', 'snyk-fix-*']
⛔ Don't Merge: ['WEBSITE/*', 'EXPERIMENT/*', 'DEPLOY/*', 'deploy_*', 'gh-pages', 'dev-demo'] ⛔ Don't Merge: ['WEBSITE/*', 'EXPERIMENT/*', 'DEPLOY/*', 'deploy_*', 'gh-pages', 'dev-demo']

View File

@ -4,7 +4,7 @@ on:
repository_dispatch: repository_dispatch:
workflow_dispatch: workflow_dispatch:
schedule: schedule:
- cron: '0 1 * * 0' # At 01:00 on Sunday. - cron: '0 1 1 * *' # Run monthly
jobs: jobs:
link-checker: link-checker:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -14,7 +14,7 @@ jobs:
- name: Check for Broken Links - name: Check for Broken Links
uses: lycheeverse/lychee-action@v1.0.8 uses: lycheeverse/lychee-action@v1.0.8
with: with:
args: --verbose --no-progress **/*.md **/*.html args: --verbose -a 200,302,304,429 --no-progress **/*.md **/*.html
env: env:
GITHUB_TOKEN: ${{secrets.BOT_GITHUB_TOKEN}} GITHUB_TOKEN: ${{secrets.BOT_GITHUB_TOKEN}}
LYCHEE_OUT: .github/broken-link-report.md LYCHEE_OUT: .github/broken-link-report.md

View File

@ -19,7 +19,7 @@ jobs:
add-awaiting-author: add-awaiting-author:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: ${{ github.event.comment.author_association != 'COLLABORATOR' && github.event.comment.author_association != 'OWNER' }} if: ${{ !github.event.issue.pull_request && github.event.comment.author_association != 'COLLABORATOR' && github.event.comment.author_association != 'OWNER' }}
steps: steps:
- name: Add Awaiting Author labels when Updated - name: Add Awaiting Author labels when Updated
uses: actions-cool/issues-helper@v2 uses: actions-cool/issues-helper@v2

View File

@ -1,6 +1,6 @@
{ {
"name": "Dashy", "name": "Dashy",
"version": "1.8.5", "version": "1.8.6",
"license": "MIT", "license": "MIT",
"main": "server", "main": "server",
"scripts": { "scripts": {
@ -39,7 +39,8 @@
"vue-router": "^3.0.3", "vue-router": "^3.0.3",
"vue-select": "^3.12.1", "vue-select": "^3.12.1",
"vue-swatches": "^2.1.1", "vue-swatches": "^2.1.1",
"vue-toasted": "^1.1.28" "vue-toasted": "^1.1.28",
"vuex": "^3.6.2"
}, },
"devDependencies": { "devDependencies": {
"@architect/sandbox": "^3.7.4", "@architect/sandbox": "^3.7.4",

View File

@ -11,10 +11,9 @@
import Header from '@/components/PageStrcture/Header.vue'; import Header from '@/components/PageStrcture/Header.vue';
import Footer from '@/components/PageStrcture/Footer.vue'; import Footer from '@/components/PageStrcture/Footer.vue';
import LoadingScreen from '@/components/PageStrcture/LoadingScreen.vue'; import LoadingScreen from '@/components/PageStrcture/LoadingScreen.vue';
import { componentVisibility } from '@/utils/ConfigHelpers';
import ConfigAccumulator from '@/utils/ConfigAccumalator';
import { welcomeMsg } from '@/utils/CoolConsole'; import { welcomeMsg } from '@/utils/CoolConsole';
import ErrorHandler from '@/utils/ErrorHandler'; import ErrorHandler from '@/utils/ErrorHandler';
import Keys from '@/utils/StoreMutations';
import { import {
localStorageKeys, localStorageKeys,
splashScreenTime, splashScreenTime,
@ -22,10 +21,6 @@ import {
language as defaultLanguage, language as defaultLanguage,
} from '@/utils/defaults'; } from '@/utils/defaults';
const Accumulator = new ConfigAccumulator();
const config = Accumulator.config();
const visibleComponents = componentVisibility(config.appConfig) || defaultVisibleComponents;
export default { export default {
name: 'app', name: 'app',
components: { components: {
@ -33,17 +28,9 @@ export default {
Footer, Footer,
LoadingScreen, LoadingScreen,
}, },
provide: {
config,
visibleComponents,
},
data() { data() {
return { return {
isLoading: true, // Set to false after mount complete isLoading: true, // Set to false after mount complete
showFooter: visibleComponents.footer,
appConfig: Accumulator.appConfig(),
pageInfo: Accumulator.pageInfo(),
visibleComponents,
}; };
}, },
computed: { computed: {
@ -55,6 +42,24 @@ export default {
shouldShowSplash() { shouldShowSplash() {
return (this.visibleComponents || defaultVisibleComponents).splashScreen; return (this.visibleComponents || defaultVisibleComponents).splashScreen;
}, },
config() {
return this.$store.state.config;
},
appConfig() {
return this.$store.getters.appConfig;
},
pageInfo() {
return this.$store.getters.pageInfo;
},
sections() {
return this.$store.getters.pageInfo;
},
visibleComponents() {
return this.$store.getters.visibleComponents;
},
},
created() {
this.$store.dispatch('initializeConfig');
}, },
methods: { methods: {
/* Injects the users custom CSS as a style tag */ /* Injects the users custom CSS as a style tag */
@ -103,6 +108,7 @@ export default {
/* Fetch or detect users language, then apply it */ /* Fetch or detect users language, then apply it */
applyLanguage() { applyLanguage() {
const language = this.getLanguage(); const language = this.getLanguage();
this.$store.commit(Keys.SET_LANGUAGE, language);
this.$i18n.locale = language; this.$i18n.locale = language;
document.getElementsByTagName('html')[0].setAttribute('lang', language); document.getElementsByTagName('html')[0].setAttribute('lang', language);
}, },

View File

@ -36,7 +36,11 @@ import ErrorHandler from '@/utils/ErrorHandler';
export default { export default {
name: 'AppInfoModal', name: 'AppInfoModal',
inject: ['config'], computed: {
appConfig() {
return this.$store.getters.appConfig;
},
},
data() { data() {
return { return {
appVersion: process.env.VUE_APP_VERSION, // Current version, from package.json appVersion: process.env.VUE_APP_VERSION, // Current version, from package.json
@ -50,8 +54,7 @@ export default {
}; };
}, },
mounted() { mounted() {
const appConfig = this.config.appConfig || {}; if (!this.appVersion || (this.appConfig && this.appConfig.disableUpdateChecks)) {
if (!this.appVersion || (appConfig && appConfig.disableUpdateChecks)) {
// Either current version isn't found, or user disabled checks // Either current version isn't found, or user disabled checks
this.checksEnabled = false; this.checksEnabled = false;
} else { } else {

View File

@ -55,7 +55,11 @@ import { modalNames, serviceEndpoints } from '@/utils/defaults';
export default { export default {
name: 'RebuildApp', name: 'RebuildApp',
inject: ['config'], computed: {
appConfig() {
return this.$store.getters.appConfig;
},
},
components: { components: {
Button, Button,
RebuildIcon, RebuildIcon,
@ -112,12 +116,8 @@ export default {
}, },
}, },
mounted() { mounted() {
if (this.config) { if (this.appConfig.allowConfigEdit === false) {
if (this.config.appConfig) { this.allowRebuild = false;
if (this.config.appConfig.allowConfigEdit === false) {
this.allowRebuild = false;
}
}
} }
}, },
}; };

View File

@ -1,6 +1,6 @@
<template> <template>
<transition name="slide"> <transition name="slide">
<div class="context-menu" v-if="show && menuEnabled" <div class="context-menu" v-if="show && !isMenuDisabled()"
:style="posX && posY ? `top:${posY}px;left:${posX}px;` : ''"> :style="posX && posY ? `top:${posY}px;left:${posX}px;` : ''">
<ul> <ul>
<li @click="launch('sametab')"> <li @click="launch('sametab')">
@ -33,7 +33,6 @@ import WorkspaceOpenIcon from '@/assets/interface-icons/open-workspace.svg';
export default { export default {
name: 'ContextMenu', name: 'ContextMenu',
inject: ['config'],
components: { components: {
SameTabOpenIcon, SameTabOpenIcon,
NewTabOpenIcon, NewTabOpenIcon,
@ -45,10 +44,10 @@ export default {
posY: Number, // The Y coordinate for positioning posY: Number, // The Y coordinate for positioning
show: Boolean, // Should show or hide the menu show: Boolean, // Should show or hide the menu
}, },
data() { computed: {
return { appConfig() {
menuEnabled: !this.isMenuDisabled(), // Specifies if the context menu should be used return this.$store.getters.appConfig;
}; },
}, },
methods: { methods: {
/* Called on item click, emits an event up to Item */ /* Called on item click, emits an event up to Item */
@ -58,10 +57,7 @@ export default {
}, },
/* Checks if the user as disabled context menu in config */ /* Checks if the user as disabled context menu in config */
isMenuDisabled() { isMenuDisabled() {
if (this.config && this.config.appConfig) { return !!this.appConfig.disableContextMenu;
return !!this.config.appConfig.disableContextMenu;
}
return false;
}, },
}, },
}; };

View File

@ -9,6 +9,8 @@
</template> </template>
<script> <script>
import Keys from '@/utils/StoreMutations';
export default { export default {
name: 'IframeModal', name: 'IframeModal',
props: { props: {
@ -21,13 +23,13 @@ export default {
show(url) { show(url) {
this.url = url; this.url = url;
this.$modal.show(this.name); this.$modal.show(this.name);
this.$emit('modalChanged', true); this.$store.commit(Keys.SET_MODAL_OPEN, true);
}, },
hide() { hide() {
this.$modal.hide(this.name); this.$modal.hide(this.name);
}, },
modalClosed() { modalClosed() {
this.$emit('modalChanged', false); this.$store.commit(Keys.SET_MODAL_OPEN, false);
}, },
}, },
}; };

View File

@ -53,7 +53,6 @@ import { localStorageKeys, serviceEndpoints } from '@/utils/defaults';
export default { export default {
name: 'Item', name: 'Item',
inject: ['config'],
props: { props: {
id: String, // The unique ID of a tile (e.g. 001) id: String, // The unique ID of a tile (e.g. 001)
title: String, // The main text of tile, required title: String, // The main text of tile, required
@ -77,6 +76,11 @@ export default {
statusCheckInterval: Number, statusCheckInterval: Number,
statusCheckAllowInsecure: Boolean, statusCheckAllowInsecure: Boolean,
}, },
computed: {
appConfig() {
return this.$store.getters.appConfig;
},
},
data() { data() {
return { return {
contextMenuOpen: false, contextMenuOpen: false,
@ -110,7 +114,7 @@ export default {
this.$emit('itemClicked'); this.$emit('itemClicked');
} }
// Update the most/ last used ledger, for smart-sorting // Update the most/ last used ledger, for smart-sorting
if (!this.config.appConfig.disableSmartSort) { if (!this.appConfig.disableSmartSort) {
this.incrementMostUsedCount(this.id); this.incrementMostUsedCount(this.id);
this.incrementLastUsedCount(this.id); this.incrementLastUsedCount(this.id);
} }

View File

@ -30,7 +30,6 @@ import { asciiHash } from '@/utils/MiscHelpers';
export default { export default {
name: 'Icon', name: 'Icon',
inject: ['config'],
props: { props: {
icon: String, // Path to icon asset icon: String, // Path to icon asset
url: String, // Used for fetching the favicon url: String, // Used for fetching the favicon
@ -40,6 +39,10 @@ export default {
BrokenImage, BrokenImage,
}, },
computed: { computed: {
/* Get appConfig from store */
appConfig() {
return this.$store.getters.appConfig;
},
/* Determines the type of icon */ /* Determines the type of icon */
iconType: function iconType() { iconType: function iconType() {
return this.determineImageType(this.icon); return this.determineImageType(this.icon);
@ -96,7 +99,7 @@ export default {
if (urlParts.length >= 2) return `${urlParts[0]}/${urlParts[1]}/${urlParts[2]}/${iconCdns.faviconName}`; if (urlParts.length >= 2) return `${urlParts[0]}/${urlParts[1]}/${urlParts[2]}/${iconCdns.faviconName}`;
} else if (fullUrl.includes('http')) { // Service is running publicly } else if (fullUrl.includes('http')) { // Service is running publicly
const host = this.getHostName(fullUrl); const host = this.getHostName(fullUrl);
const faviconApi = specificApi || this.config.appConfig.faviconApi || defaultFaviconApi; const faviconApi = specificApi || this.appConfig.faviconApi || defaultFaviconApi;
const endpoint = faviconApiEndpoints[faviconApi]; const endpoint = faviconApiEndpoints[faviconApi];
return endpoint.replace('$URL', host); return endpoint.replace('$URL', host);
} }
@ -120,7 +123,7 @@ export default {
/* or if user prefers local favicon, then return true */ /* or if user prefers local favicon, then return true */
shouldUseDefaultFavicon(fullUrl) { shouldUseDefaultFavicon(fullUrl) {
const isLocalIP = /(127\.)|(192\.168\.)|(10\.)|(172\.1[6-9]\.)|(172\.2[0-9]\.)|(172\.3[0-1]\.)|(::1$)|([fF][cCdD])|(localhost)/; const isLocalIP = /(127\.)|(192\.168\.)|(10\.)|(172\.1[6-9]\.)|(172\.2[0-9]\.)|(172\.3[0-1]\.)|(::1$)|([fF][cCdD])|(localhost)/;
return (isLocalIP.test(fullUrl) || this.config.appConfig.faviconApi === 'local'); return (isLocalIP.test(fullUrl) || this.appConfig.faviconApi === 'local');
}, },
/* Fetches the path of local images, from Docker container */ /* Fetches the path of local images, from Docker container */
getLocalImagePath(img) { getLocalImagePath(img) {

View File

@ -44,7 +44,6 @@
:ref="`iframeModal-${groupId}`" :ref="`iframeModal-${groupId}`"
:name="`iframeModal-${groupId}`" :name="`iframeModal-${groupId}`"
@closed="$emit('itemClicked')" @closed="$emit('itemClicked')"
@modalChanged="modalChanged"
/> />
</Collapsable> </Collapsable>
</template> </template>
@ -58,7 +57,6 @@ import IframeModal from '@/components/LinkItems/IframeModal.vue';
export default { export default {
name: 'Section', name: 'Section',
inject: ['config'],
props: { props: {
groupId: String, groupId: String,
title: String, title: String,
@ -66,7 +64,6 @@ export default {
displayData: Object, displayData: Object,
items: Array, items: Array,
itemSize: String, itemSize: String,
modalOpen: Boolean,
}, },
components: { components: {
Collapsable, Collapsable,
@ -74,13 +71,16 @@ export default {
IframeModal, IframeModal,
}, },
computed: { computed: {
appConfig() {
return this.$store.getters.appConfig;
},
sortOrder() { sortOrder() {
return this.displayData.sortBy || defaultSortOrder; return this.displayData.sortBy || defaultSortOrder;
}, },
/* If the sortBy attribute is specified, then return sorted data */ /* If the sortBy attribute is specified, then return sorted data */
sortedItems() { sortedItems() {
let { items } = this; let { items } = this;
if (this.config.appConfig.disableSmartSort) return items; if (this.appConfig.disableSmartSort) return items;
if (this.sortOrder === 'alphabetical') { if (this.sortOrder === 'alphabetical') {
this.sortAlphabetically(items); this.sortAlphabetically(items);
} else if (this.sortOrder === 'reverse-alphabetical') { } else if (this.sortOrder === 'reverse-alphabetical') {
@ -122,18 +122,14 @@ export default {
triggerModal(url) { triggerModal(url) {
this.$refs[`iframeModal-${this.groupId}`].show(url); this.$refs[`iframeModal-${this.groupId}`].show(url);
}, },
/* Emmit value upwards when iframe modal opened/ closed */
modalChanged(changedTo) {
this.$emit('change-modal-visibility', changedTo);
},
/* Determines if user has enabled online status checks */ /* Determines if user has enabled online status checks */
shouldEnableStatusCheck(itemPreference) { shouldEnableStatusCheck(itemPreference) {
const globalPreference = this.config.appConfig.statusCheck || false; const globalPreference = this.appConfig.statusCheck || false;
return itemPreference !== undefined ? itemPreference : globalPreference; return itemPreference !== undefined ? itemPreference : globalPreference;
}, },
/* Determine how often to re-fire status checks */ /* Determine how often to re-fire status checks */
getStatusCheckInterval() { getStatusCheckInterval() {
let interval = this.config.appConfig.statusCheckInterval; let interval = this.appConfig.statusCheckInterval;
if (!interval) return 0; if (!interval) return 0;
if (interval > 60) interval = 60; if (interval > 60) interval = 60;
if (interval < 1) interval = 0; if (interval < 1) interval = 0;

View File

@ -12,7 +12,6 @@ import SearchBar from '@/components/Settings/SearchBar';
export default { export default {
name: 'MinimalSearch', name: 'MinimalSearch',
inject: ['config'],
components: { components: {
SearchBar, SearchBar,
}, },
@ -24,16 +23,29 @@ export default {
input: '', // Users current search term input: '', // Users current search term
}; };
}, },
computed: {
appConfig() {
return this.$store.getters.appConfig;
},
webSearchEnabled() {
if (this.appConfig && this.appConfig.webSearch) {
return !this.appConfig.webSearch.disableWebSearch;
}
return true;
},
},
methods: { methods: {
/* Emmits users's search term up to parent */ /* Emmits users's search term up to parent */
userIsTypingSomething(searchValue) { userIsTypingSomething(searchValue) {
this.input = searchValue; this.input = searchValue;
this.$emit('user-is-searchin', searchValue); this.$emit('user-is-searchin', searchValue);
}, },
/* Emmits an event to reset state when user is finished searching */ },
clearMinFilterInput() { mounted() {
this.$refs.MinimalSearchBar.clearFilterInput(); window.addEventListener('keydown', this.startFiltering);
}, },
beforeDestroy() {
window.removeEventListener('keydown', this.startFiltering);
}, },
}; };
</script> </script>

View File

@ -26,7 +26,6 @@
:ref="`iframeModal-${groupId}`" :ref="`iframeModal-${groupId}`"
:name="`iframeModal-${groupId}`" :name="`iframeModal-${groupId}`"
@closed="$emit('itemClicked')" @closed="$emit('itemClicked')"
@modalChanged="modalChanged"
/> />
</div> </div>
</template> </template>
@ -37,7 +36,6 @@ import IframeModal from '@/components/LinkItems/IframeModal.vue';
export default { export default {
name: 'ItemGroup', name: 'ItemGroup',
inject: ['config'],
props: { props: {
groupId: String, groupId: String,
title: String, title: String,
@ -50,6 +48,11 @@ export default {
selected: Boolean, selected: Boolean,
showAll: Boolean, showAll: Boolean,
}, },
computed: {
appConfig() {
return this.$store.getters.appConfig;
},
},
components: { components: {
Item, Item,
IframeModal, IframeModal,
@ -66,15 +69,12 @@ export default {
triggerModal(url) { triggerModal(url) {
this.$refs[`iframeModal-${this.groupId}`].show(url); this.$refs[`iframeModal-${this.groupId}`].show(url);
}, },
modalChanged(changedTo) {
this.$emit('change-modal-visibility', changedTo);
},
shouldEnableStatusCheck(itemPreference) { shouldEnableStatusCheck(itemPreference) {
const globalPreference = this.config.appConfig.statusCheck || false; const globalPreference = this.appConfig.statusCheck || false;
return itemPreference !== undefined ? itemPreference : globalPreference; return itemPreference !== undefined ? itemPreference : globalPreference;
}, },
getStatusCheckInterval() { getStatusCheckInterval() {
let interval = this.config.appConfig.statusCheckInterval; let interval = this.appConfig.statusCheckInterval;
if (!interval) return 0; if (!interval) return 0;
if (interval > 60) interval = 60; if (interval > 60) interval = 60;
if (interval < 1) interval = 0; if (interval < 1) interval = 0;

View File

@ -1,5 +1,5 @@
<template> <template>
<header v-if="visible"> <header v-if="componentVisible">
<PageTitle <PageTitle
v-if="titleVisible" v-if="titleVisible"
:title="pageInfo.title" :title="pageInfo.title"
@ -13,12 +13,10 @@
<script> <script>
import PageTitle from '@/components/PageStrcture/PageTitle.vue'; import PageTitle from '@/components/PageStrcture/PageTitle.vue';
import Nav from '@/components/PageStrcture/Nav.vue'; import Nav from '@/components/PageStrcture/Nav.vue';
import { visibleComponents as defaultVisibleComponents } from '@/utils/defaults';
import { shouldBeVisible } from '@/utils/MiscHelpers'; import { shouldBeVisible } from '@/utils/MiscHelpers';
export default { export default {
name: 'Header', name: 'Header',
inject: ['visibleComponents'],
components: { components: {
PageTitle, PageTitle,
Nav, Nav,
@ -26,16 +24,19 @@ export default {
props: { props: {
pageInfo: Object, pageInfo: Object,
}, },
data() {
return {
titleVisible: (this.visibleComponents || defaultVisibleComponents).pageTitle,
navVisible: (this.visibleComponents || defaultVisibleComponents).navigation,
};
},
computed: { computed: {
visible() { componentVisible() {
return shouldBeVisible(this.$route.name); return shouldBeVisible(this.$route.name);
}, },
visibleComponents() {
return this.$store.getters.visibleComponents;
},
titleVisible() {
return this.visibleComponents.pageTitle;
},
navVisible() {
return this.visibleComponents.navigation;
},
}, },
}; };
</script> </script>

View File

@ -11,7 +11,7 @@
<!-- Modal containing all the configuration options --> <!-- Modal containing all the configuration options -->
<modal :name="modalNames.CONF_EDITOR" :resizable="true" width="60%" height="85%" <modal :name="modalNames.CONF_EDITOR" :resizable="true" width="60%" height="85%"
@closed="$emit('modalChanged', false)" classes="dashy-modal"> @closed="editorClosed" classes="dashy-modal">
<ConfigContainer :config="combineConfig()" /> <ConfigContainer :config="combineConfig()" />
</modal> </modal>
@ -48,6 +48,7 @@
import ConfigContainer from '@/components/Configuration/ConfigContainer'; import ConfigContainer from '@/components/Configuration/ConfigContainer';
import LanguageSwitcher from '@/components/Settings/LanguageSwitcher'; import LanguageSwitcher from '@/components/Settings/LanguageSwitcher';
import { topLevelConfKeys, localStorageKeys, modalNames } from '@/utils/defaults'; import { topLevelConfKeys, localStorageKeys, modalNames } from '@/utils/defaults';
import Keys from '@/utils/StoreMutations';
import IconSpanner from '@/assets/interface-icons/config-editor.svg'; import IconSpanner from '@/assets/interface-icons/config-editor.svg';
import IconViewMode from '@/assets/interface-icons/application-change-view.svg'; import IconViewMode from '@/assets/interface-icons/application-change-view.svg';
import IconHome from '@/assets/interface-icons/application-home.svg'; import IconHome from '@/assets/interface-icons/application-home.svg';
@ -71,15 +72,24 @@ export default {
IconWorkspaceView, IconWorkspaceView,
IconMinimalView, IconMinimalView,
}, },
props: { computed: {
sections: Array, sections() {
pageInfo: Object, return this.$store.getters.sections;
appConfig: Object, },
appConfig() {
return this.$store.getters.appConfig;
},
pageInfo() {
return this.$store.getters.pageInfo;
},
}, },
methods: { methods: {
showEditor: function show() { showEditor: function show() {
this.$modal.show(modalNames.CONF_EDITOR); this.$modal.show(modalNames.CONF_EDITOR);
this.$emit('modalChanged', true); this.$store.commit(Keys.SET_MODAL_OPEN, true);
},
editorClosed: function show() {
this.$store.commit(Keys.SET_MODAL_OPEN, false);
}, },
combineConfig() { combineConfig() {
const conf = {}; const conf = {};

View File

@ -28,23 +28,40 @@
import Button from '@/components/FormElements/Button'; import Button from '@/components/FormElements/Button';
import SaveConfigIcon from '@/assets/interface-icons/save-config.svg'; import SaveConfigIcon from '@/assets/interface-icons/save-config.svg';
import ErrorHandler from '@/utils/ErrorHandler'; import ErrorHandler from '@/utils/ErrorHandler';
import Keys from '@/utils/StoreMutations';
import { languages } from '@/utils/languages'; import { languages } from '@/utils/languages';
import { localStorageKeys, modalNames } from '@/utils/defaults'; import { localStorageKeys, modalNames } from '@/utils/defaults';
export default { export default {
name: 'LanguageSwitcher', name: 'LanguageSwitcher',
inject: ['config'],
components: { components: {
Button, Button,
SaveConfigIcon, SaveConfigIcon,
}, },
data() { data() {
return { return {
language: this.getCurrentLanguage(), // The currently selected language language: '', // The currently selected language
modalName: modalNames.LANG_SWITCHER, // Key for modal modalName: modalNames.LANG_SWITCHER, // Key for modal
}; };
}, },
created() {
// Initiate the current language, with VueX state
this.language = this.savedLanguage;
},
computed: { computed: {
/* Get appConfig from store */
appConfig() {
return this.$store.getters.appConfig;
},
/* The ISO code for the users language, synced with VueX store */
savedLanguage: {
get() {
return this.getIsoFromLangObj(this.$store.state.lang);
},
set(newLang) {
this.$store.commit(Keys.SET_LANGUAGE, newLang.code);
},
},
/* Return the array of language objects, plus a friends name */ /* Return the array of language objects, plus a friends name */
languageList: () => languages.map((lang) => { languageList: () => languages.map((lang) => {
const newLang = lang; const newLang = lang;
@ -73,6 +90,7 @@ export default {
if (this.checkLocale(selectedLanguage)) { if (this.checkLocale(selectedLanguage)) {
localStorage.setItem(localStorageKeys.LANGUAGE, selectedLanguage.code); localStorage.setItem(localStorageKeys.LANGUAGE, selectedLanguage.code);
this.applyLanguageLocally(); this.applyLanguageLocally();
this.savedLanguage = selectedLanguage;
const successMsg = `${selectedLanguage.flag} ` const successMsg = `${selectedLanguage.flag} `
+ `${this.$t('language-switcher.success-msg')} ${selectedLanguage.name}`; + `${this.$t('language-switcher.success-msg')} ${selectedLanguage.name}`;
this.$toasted.show(successMsg, { className: 'toast-success' }); this.$toasted.show(successMsg, { className: 'toast-success' });
@ -82,11 +100,10 @@ export default {
ErrorHandler('Unable to apply language'); ErrorHandler('Unable to apply language');
} }
}, },
/* Gets the users current language from local storage */ /* Gets the ISO code for a given language object */
getCurrentLanguage() { getIsoFromLangObj(langObj) {
const getLanguageFromIso = (iso) => languages.find((lang) => lang.code === iso); const getLanguageFromIso = (iso) => languages.find((lang) => lang.code === iso);
const current = localStorage[localStorageKeys.LANGUAGE] || this.config.appConfig.language; return getLanguageFromIso(langObj);
return getLanguageFromIso(current);
}, },
}, },
}; };

View File

@ -35,9 +35,7 @@ import {
export default { export default {
name: 'FilterTile', name: 'FilterTile',
inject: ['config'],
props: { props: {
active: Boolean,
minimalSearch: Boolean, // If true, then keep it simple minimalSearch: Boolean, // If true, then keep it simple
}, },
data() { data() {
@ -48,8 +46,11 @@ export default {
}; };
}, },
computed: { computed: {
active() {
return !this.$store.state.modalOpen;
},
searchPrefs() { searchPrefs() {
return this.config.appConfig.webSearch || {}; return this.$store.getters.webSearch || {};
}, },
}, },
mounted() { mounted() {

View File

@ -3,16 +3,14 @@
<SearchBar ref="SearchBar" <SearchBar ref="SearchBar"
@user-is-searchin="userIsTypingSomething" @user-is-searchin="userIsTypingSomething"
v-if="searchVisible" v-if="searchVisible"
:active="!modalOpen"
/> />
<div class="options-outer"> <div class="options-outer">
<div :class="`options-container ${!settingsVisible ? 'hide' : ''}`"> <div :class="`options-container ${!settingsVisible ? 'hide' : ''}`">
<ThemeSelector :externalThemes="externalThemes" @modalChanged="modalChanged" <ThemeSelector :externalThemes="externalThemes"
:confTheme="getInitialTheme()" :userThemes="getUserThemes()" /> :confTheme="getInitialTheme()" :userThemes="getUserThemes()" />
<LayoutSelector :displayLayout="displayLayout" @layoutUpdated="updateDisplayLayout"/> <LayoutSelector :displayLayout="displayLayout" @layoutUpdated="updateDisplayLayout"/>
<ItemSizeSelector :iconSize="iconSize" @iconSizeUpdated="updateIconSize" /> <ItemSizeSelector :iconSize="iconSize" @iconSizeUpdated="updateIconSize" />
<ConfigLauncher :sections="sections" :pageInfo="pageInfo" :appConfig="appConfig" <ConfigLauncher />
@modalChanged="modalChanged" />
<AuthButtons v-if="userState != 'noone'" :userType="userState" /> <AuthButtons v-if="userState != 'noone'" :userType="userState" />
</div> </div>
<div :class="`show-hide-container ${settingsVisible? 'hide-btn' : 'show-btn'}`"> <div :class="`show-hide-container ${settingsVisible? 'hide-btn' : 'show-btn'}`">
@ -52,10 +50,6 @@ export default {
displayLayout: String, displayLayout: String,
iconSize: String, iconSize: String,
externalThemes: Object, externalThemes: Object,
appConfig: Object,
pageInfo: Object,
sections: Array,
modalOpen: Boolean,
}, },
components: { components: {
SearchBar, SearchBar,
@ -69,7 +63,43 @@ export default {
IconOpen, IconOpen,
IconClose, 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: { methods: {
userIsTypingSomething(something) { userIsTypingSomething(something) {
this.$emit('user-is-searchin', something); this.$emit('user-is-searchin', something);
@ -83,9 +113,6 @@ export default {
updateIconSize(iconSize) { updateIconSize(iconSize) {
this.$emit('change-icon-size', iconSize); this.$emit('change-icon-size', iconSize);
}, },
modalChanged(changedTo) {
this.$emit('change-modal-visibility', changedTo);
},
getInitialTheme() { getInitialTheme() {
return this.appConfig.theme || ''; return this.appConfig.theme || '';
}, },
@ -104,25 +131,6 @@ export default {
|| (this.visibleComponents || defaultVisibleComponents).settings); || (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,
};
},
}; };
</script> </script>

View File

@ -31,6 +31,7 @@ import {
ApplyCustomVariables, ApplyCustomVariables,
} from '@/utils/ThemeHelper'; } from '@/utils/ThemeHelper';
import Defaults, { localStorageKeys } from '@/utils/defaults'; import Defaults, { localStorageKeys } from '@/utils/defaults';
import Keys from '@/utils/StoreMutations';
import IconPalette from '@/assets/interface-icons/config-color-palette.svg'; import IconPalette from '@/assets/interface-icons/config-color-palette.svg';
export default { export default {
@ -94,13 +95,15 @@ export default {
}, },
/* Opens the theme color configurator popup */ /* Opens the theme color configurator popup */
openThemeConfigurator() { openThemeConfigurator() {
this.$emit('modalChanged', true); this.$store.commit(Keys.SET_MODAL_OPEN, true);
this.themeConfiguratorOpen = true; this.themeConfiguratorOpen = true;
}, },
/* Closes the theme color configurator popup */ /* Closes the theme color configurator popup */
closeThemeConfigurator() { closeThemeConfigurator() {
// this.$emit('modalChanged', false); if (this.themeConfiguratorOpen) {
this.themeConfiguratorOpen = false; this.$store.commit(Keys.SET_MODAL_OPEN, false);
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 */

View File

@ -36,7 +36,6 @@ import IconMinimalView from '@/assets/interface-icons/application-minimal.svg';
export default { export default {
name: 'SideBar', name: 'SideBar',
inject: ['config'],
props: { props: {
sections: Array, sections: Array,
initUrl: String, initUrl: String,

View File

@ -12,7 +12,6 @@ import Icon from '@/components/LinkItems/ItemIcon.vue';
export default { export default {
name: 'SideBarItem', name: 'SideBarItem',
inject: ['config'],
props: { props: {
icon: String, icon: String,
title: String, title: String,

View File

@ -19,7 +19,6 @@ import SideBarItem from '@/components/Workspace/SideBarItem.vue';
export default { export default {
name: 'SideBarSection', name: 'SideBarSection',
inject: ['config'],
props: { props: {
items: Array, items: Array,
}, },

View File

@ -14,6 +14,7 @@ import Toasted from 'vue-toasted'; // Toast component, used to show confirm
// Import base Dashy components and utils // Import base Dashy components and utils
import Dashy from '@/App.vue'; // Main Dashy Vue app import Dashy from '@/App.vue'; // Main Dashy Vue app
import router from '@/router'; // Router, for navigation 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 serviceWorker from '@/utils/InitServiceWorker'; // Service worker initialization
import clickOutside from '@/utils/ClickOutside'; // Directive for closing popups, modals, etc import clickOutside from '@/utils/ClickOutside'; // Directive for closing popups, modals, etc
import { messages } from '@/utils/languages'; // Language texts import { messages } from '@/utils/languages'; // Language texts
@ -48,9 +49,14 @@ ErrorReporting(Vue, router);
// Render function // Render function
const render = (awesome) => awesome(Dashy); 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 Keycloak not enabled, then proceed straight to the app
if (!isKeycloakEnabled()) { if (!isKeycloakEnabled()) {
new Vue({ router, render, i18n }).$mount('#app'); mount();
} else { // Keycloak is enabled, redirect to KC login page } else { // Keycloak is enabled, redirect to KC login page
const { serverUrl, realm, clientId } = getKeycloakConfig(); const { serverUrl, realm, clientId } = getKeycloakConfig();
const initOptions = { const initOptions = {
@ -63,7 +69,7 @@ if (!isKeycloakEnabled()) {
window.location.reload(); window.location.reload();
} else { } else {
// Yay - user successfully authenticated with Keycloak, render the app! // Yay - user successfully authenticated with Keycloak, render the app!
new Vue({ router, render, i18n }).$mount('#app'); mount();
} }
}); });
} }

View File

@ -14,10 +14,10 @@ import Home from '@/views/Home.vue';
import Login from '@/views/Login.vue'; import Login from '@/views/Login.vue';
import Workspace from '@/views/Workspace.vue'; import Workspace from '@/views/Workspace.vue';
import Minimal from '@/views/Minimal.vue'; import Minimal from '@/views/Minimal.vue';
import ConfigAccumulator from '@/utils/ConfigAccumalator';
// Import helper functions, config data and defaults // Import helper functions, config data and defaults
import { isAuthEnabled, isLoggedIn, isGuestAccessEnabled } from '@/utils/Auth'; import { isAuthEnabled, isLoggedIn, isGuestAccessEnabled } from '@/utils/Auth';
import { config } from '@/utils/ConfigHelpers';
import { metaTagData, startingView, routePaths } from '@/utils/defaults'; import { metaTagData, startingView, routePaths } from '@/utils/defaults';
import ErrorHandler from '@/utils/ErrorHandler'; import ErrorHandler from '@/utils/ErrorHandler';
@ -32,8 +32,18 @@ const isAuthenticated = () => {
return (!authEnabled || userLoggedIn || guestEnabled); 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 */ /* 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, * Returns the component that should be rendered at the base path,
@ -51,7 +61,7 @@ const getStartingComponent = () => {
/* Returns the meta tags for each route */ /* Returns the meta tags for each route */
const makeMetaTags = (defaultTitle) => ({ const makeMetaTags = (defaultTitle) => ({
title: config.pageInfo.title || defaultTitle, title: pageInfo.title || defaultTitle,
metaTags: metaTagData, metaTags: metaTagData,
}); });
@ -62,37 +72,30 @@ const router = new Router({
path: '/', path: '/',
name: `landing-page-${getStartingView()}`, name: `landing-page-${getStartingView()}`,
component: getStartingComponent(), component: getStartingComponent(),
props: config,
meta: makeMetaTags('Home Page'), meta: makeMetaTags('Home Page'),
}, },
{ // Default home page { // Default home page
path: routePaths.home, path: routePaths.home,
name: 'home', name: 'home',
component: Home, component: Home,
props: config,
meta: makeMetaTags('Home Page'), meta: makeMetaTags('Home Page'),
}, },
{ // Workspace view page { // Workspace view page
path: routePaths.workspace, path: routePaths.workspace,
name: 'workspace', name: 'workspace',
component: Workspace, component: Workspace,
props: config,
meta: makeMetaTags('Workspace'), meta: makeMetaTags('Workspace'),
}, },
{ // Minimal view page { // Minimal view page
path: routePaths.minimal, path: routePaths.minimal,
name: 'minimal', name: 'minimal',
component: Minimal, component: Minimal,
props: config,
meta: makeMetaTags('Start Page'), meta: makeMetaTags('Start Page'),
}, },
{ // The login page { // The login page
path: routePaths.login, path: routePaths.login,
name: 'login', name: 'login',
component: Login, component: Login,
props: {
appConfig: config.appConfig,
},
beforeEnter: (to, from, next) => { beforeEnter: (to, from, next) => {
// If the user already logged in + guest mode not enabled, then redirect home // If the user already logged in + guest mode not enabled, then redirect home
if (isAuthenticated() && !isGuestAccessEnabled()) router.push({ path: '/' }); if (isAuthenticated() && !isGuestAccessEnabled()) router.push({ path: '/' });
@ -109,7 +112,6 @@ const router = new Router({
path: routePaths.download, path: routePaths.download,
name: 'download', name: 'download',
component: () => import('./views/DownloadConfig.vue'), component: () => import('./views/DownloadConfig.vue'),
props: config,
meta: makeMetaTags('Download Config'), meta: makeMetaTags('Download Config'),
}, },
{ // Page not found, any non-defined routes will land here { // Page not found, any non-defined routes will land here

61
src/store.js Normal file
View File

@ -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;

View File

@ -16,9 +16,7 @@ const getAppConfig = () => {
* Support for old user structure will be removed in V 1.7.0 * Support for old user structure will be removed in V 1.7.0
*/ */
const printWarning = () => { const printWarning = () => {
const msg = 'From V 1.6.5 onwards, the structure of the users object has changed.'; ErrorHandler('From V 1.6.5 onwards, the structure of the users object has changed.');
// eslint-disable-next-line no-console
console.warn(msg);
}; };
/* Returns true if keycloak is enabled */ /* Returns true if keycloak is enabled */

View File

@ -11,9 +11,8 @@ import {
pageInfo as defaultPageInfo, pageInfo as defaultPageInfo,
iconSize as defaultIconSize, iconSize as defaultIconSize,
layout as defaultLayout, layout as defaultLayout,
// language as defaultLanguage,
} from '@/utils/defaults'; } from '@/utils/defaults';
import ErrorHandler from '@/utils/ErrorHandler';
import conf from '../../public/conf.yml'; import conf from '../../public/conf.yml';
export default class ConfigAccumulator { export default class ConfigAccumulator {
@ -46,24 +45,14 @@ export default class ConfigAccumulator {
/* Page Info */ /* Page Info */
pageInfo() { pageInfo() {
const defaults = defaultPageInfo; let localPageInfo = {};
let localPageInfo; if (localStorage[localStorageKeys.PAGE_INFO]) {
try { // eslint-disable-next-line brace-style
localPageInfo = JSON.parse(localStorage[localStorageKeys.PAGE_INFO]); try { localPageInfo = JSON.parse(localStorage[localStorageKeys.PAGE_INFO]); }
} catch (e) { catch (e) { ErrorHandler('Malformed pageInfo data in local storage'); }
localPageInfo = {};
} }
let filePageInfo = {}; const filePageInfo = this.conf ? this.conf.pageInfo || {} : {};
if (this.conf) { return { ...defaultPageInfo, ...filePageInfo, ...localPageInfo };
filePageInfo = this.conf.pageInfo || {};
}
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;
} }
/* Sections */ /* Sections */
@ -75,13 +64,11 @@ export default class ConfigAccumulator {
const json = JSON.parse(localSections); const json = JSON.parse(localSections);
if (json.length >= 1) return json; if (json.length >= 1) return json;
} catch (e) { } 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 // If the function hasn't yet returned, then return the config file sections
let sectionsFile = []; return this.conf ? this.conf.sections || [] : [];
if (this.conf) sectionsFile = this.conf.sections || [];
return sectionsFile;
} }
/* Complete config */ /* Complete config */

View File

@ -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;

View File

@ -7,18 +7,13 @@ import JsonToYaml from '@/utils/JsonToYaml';
export default { export default {
name: 'DownloadConfig', name: 'DownloadConfig',
props: { computed: {
sections: Array, config() {
appConfig: Object, return this.$store.state.config;
pageInfo: Object, },
}, },
data() { data() {
return { return {
config: {
appConfig: this.appConfig,
pageInfo: this.pageInfo,
sections: this.sections,
},
jsonParser: JsonToYaml, jsonParser: JsonToYaml,
}; };
}, },

View File

@ -10,9 +10,6 @@
:displayLayout="layout" :displayLayout="layout"
:iconSize="itemSizeBound" :iconSize="itemSizeBound"
:externalThemes="getExternalCSSLinks()" :externalThemes="getExternalCSSLinks()"
:sections="allSections"
:appConfig="appConfig"
:pageInfo="pageInfo"
:modalOpen="modalOpen" :modalOpen="modalOpen"
class="settings-outer" class="settings-outer"
/> />
@ -55,11 +52,6 @@ import Defaults, { localStorageKeys, iconCdns } from '@/utils/defaults';
export default { export default {
name: 'home', name: 'home',
props: {
sections: Array, // Main site content
appConfig: Object, // Main site configuation (optional)
pageInfo: Object, // Page metadata (optional)
},
components: { components: {
SettingsContainer, SettingsContainer,
Section, Section,
@ -68,9 +60,20 @@ export default {
searchValue: '', searchValue: '',
layout: '', layout: '',
itemSizeBound: '', itemSizeBound: '',
modalOpen: false, // When true, keybindings are disabled
}), }),
computed: { 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 */ /* Get class for num columns, if specified by user */
colCount() { colCount() {
let { colCount } = this.appConfig; let { colCount } = this.appConfig;
@ -143,7 +146,7 @@ export default {
}, },
/* Update data when modal is open (so that key bindings can be disabled) */ /* Update data when modal is open (so that key bindings can be disabled) */
updateModalVisibility(modalState) { updateModalVisibility(modalState) {
this.modalOpen = modalState; this.$store.commit('SET_MODAL_OPEN', modalState);
}, },
/* Returns an array of links to external CSS from the Config */ /* Returns an array of links to external CSS from the Config */
getExternalCSSLinks() { getExternalCSSLinks() {

View File

@ -91,9 +91,6 @@ export default {
Button, Button,
Input, Input,
}, },
props: {
appConfig: Object,
},
data() { data() {
return { return {
username: '', username: '',
@ -104,6 +101,9 @@ export default {
}; };
}, },
computed: { computed: {
appConfig() {
return this.$store.getters.appConfig;
},
/* Data for timeout dropdown menu, translated label + value in ms */ /* Data for timeout dropdown menu, translated label + value in ms */
dropDownMenu() { dropDownMenu() {
return [ return [

View File

@ -2,8 +2,7 @@
<div class="minimal-home" :style="getBackgroundImage() + setColumnCount()"> <div class="minimal-home" :style="getBackgroundImage() + setColumnCount()">
<!-- Buttons for config and home page --> <!-- Buttons for config and home page -->
<div class="minimal-buttons"> <div class="minimal-buttons">
<ConfigLauncher :sections="sections" :pageInfo="pageInfo" :appConfig="appConfig" <ConfigLauncher @modalChanged="modalChanged" class="config-launcher" />
@modalChanged="modalChanged" class="config-launcher" />
</div> </div>
<!-- Page title and search bar --> <!-- Page title and search bar -->
<div class="title-and-search"> <div class="title-and-search">
@ -62,11 +61,6 @@ import ConfigLauncher from '@/components/Settings/ConfigLauncher';
export default { export default {
name: 'home', name: 'home',
props: {
sections: Array, // Main site content
appConfig: Object, // Main site configuation (optional)
pageInfo: Object,
},
components: { components: {
MinimalSection, MinimalSection,
MinimalHeading, MinimalHeading,
@ -81,6 +75,17 @@ export default {
tabbedView: true, // By default use tabs, when searching then show all instead tabbedView: true, // By default use tabs, when searching then show all instead
theme: GetTheme(), theme: GetTheme(),
}), }),
computed: {
sections() {
return this.$store.getters.sections;
},
appConfig() {
return this.$store.getters.appConfig;
},
pageInfo() {
return this.$store.getters.pageInfo;
},
},
watch: { watch: {
/* When the theme changes, then call the update method */ /* When the theme changes, then call the update method */
searchValue() { searchValue() {

View File

@ -16,10 +16,6 @@ import { GetTheme, ApplyLocalTheme, ApplyCustomVariables } from '@/utils/ThemeHe
export default { export default {
name: 'Workspace', name: 'Workspace',
props: {
sections: Array,
appConfig: Object,
},
data: () => ({ data: () => ({
url: '', url: '',
GetTheme, GetTheme,
@ -27,6 +23,12 @@ export default {
ApplyCustomVariables, ApplyCustomVariables,
}), }),
computed: { computed: {
sections() {
return this.$store.getters.sections;
},
appConfig() {
return this.$store.getters.appConfig;
},
isMultiTaskingEnabled() { isMultiTaskingEnabled() {
return this.appConfig.enableMultiTasking || false; return this.appConfig.enableMultiTasking || false;
}, },

View File

@ -9866,6 +9866,11 @@ vue@^2.6.10:
resolved "https://registry.yarnpkg.com/vue/-/vue-2.6.14.tgz#e51aa5250250d569a3fbad3a8a5a687d6036e235" resolved "https://registry.yarnpkg.com/vue/-/vue-2.6.14.tgz#e51aa5250250d569a3fbad3a8a5a687d6036e235"
integrity sha512-x2284lgYvjOMj3Za7kqzRcUSxBboHqtgRE2zlos1qWaOye5yUmHn42LB1250NJBLRwEcdrB0JRwyPTEPhfQjiQ== 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: watchpack-chokidar2@^2.0.1:
version "2.0.1" version "2.0.1"
resolved "https://registry.yarnpkg.com/watchpack-chokidar2/-/watchpack-chokidar2-2.0.1.tgz#38500072ee6ece66f3769936950ea1771be1c957" resolved "https://registry.yarnpkg.com/watchpack-chokidar2/-/watchpack-chokidar2-2.0.1.tgz#38500072ee6ece66f3769936950ea1771be1c957"