Merge branch 'master' of github.com:Lissy93/dashy into FEATURE/minimal-view
This commit is contained in:
25
src/App.vue
25
src/App.vue
@@ -17,6 +17,7 @@ import {
|
||||
localStorageKeys,
|
||||
splashScreenTime,
|
||||
visibleComponents as defaultVisibleComponents,
|
||||
language as defaultLanguage,
|
||||
} from '@/utils/defaults';
|
||||
|
||||
const Accumulator = new ConfigAccumulator();
|
||||
@@ -70,9 +71,33 @@ export default {
|
||||
this.isLoading = false;
|
||||
}
|
||||
},
|
||||
/* Checks local storage, then appConfig, and if a custom language is specified, its applied */
|
||||
applyLanguage() {
|
||||
let language = defaultLanguage; // Language to apply
|
||||
const availibleLocales = this.$i18n.availableLocales; // All available locales
|
||||
|
||||
// If user has specified a language, locally or in config, then check and apply it
|
||||
const usersLang = localStorage[localStorageKeys.LANGUAGE] || this.appConfig.language;
|
||||
if (usersLang && availibleLocales.includes(usersLang)) {
|
||||
language = usersLang;
|
||||
} else {
|
||||
// Otherwise, attempt to apply language automatically, based on their system language
|
||||
const usersBorwserLang1 = window.navigator.language || ''; // e.g. en-GB or or ''
|
||||
const usersBorwserLang2 = usersBorwserLang1.split('-')[0]; // e.g. en or undefined
|
||||
if (availibleLocales.includes(usersBorwserLang1)) {
|
||||
language = usersBorwserLang1;
|
||||
} else if (availibleLocales.includes(usersBorwserLang2)) {
|
||||
language = usersBorwserLang2;
|
||||
}
|
||||
}
|
||||
// Apply Language
|
||||
this.$i18n.locale = language;
|
||||
document.getElementsByTagName('html')[0].setAttribute('lang', language);
|
||||
},
|
||||
},
|
||||
/* When component mounted, hide splash and initiate the injection of custom styles */
|
||||
mounted() {
|
||||
this.applyLanguage();
|
||||
this.hideSplash();
|
||||
if (this.appConfig.customCss) {
|
||||
const cleanedCss = this.appConfig.customCss.replace(/<\/?[^>]+(>|$)/g, '');
|
||||
|
||||
1
src/assets/interface-icons/config-language.svg
Normal file
1
src/assets/interface-icons/config-language.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg aria-hidden="true" focusable="false" data-prefix="far" data-icon="language" class="svg-inline--fa fa-language fa-w-20" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><path fill="currentColor" d="M160.3 203.8h-.5s-4.3 20.9-7.8 33l-11 37.3h37.9l-10.7-37.3c-3.6-12.1-7.9-33-7.9-33zM616 96H24c-13.3 0-24 10.7-24 24v272c0 13.3 10.7 24 24 24h592c13.3 0 24-10.7 24-24V120c0-13.3-10.7-24-24-24zM233.2 352h-22.6a12 12 0 0 1-11.5-8.6l-9.3-31.7h-59.9l-9.1 31.6c-1.5 5.1-6.2 8.7-11.5 8.7H86.8c-8.2 0-14-8.1-11.4-15.9l57.1-168c1.7-4.9 6.2-8.1 11.4-8.1h32.2c5.1 0 9.7 3.3 11.4 8.1l57.1 168c2.6 7.8-3.2 15.9-11.4 15.9zM600 376H320V136h280zM372 228h110.8c-6.3 12.8-15.1 25.9-25.9 38.5-6.6-7.8-12.8-15.8-18.3-24-3.5-5.3-10.6-6.9-16.1-3.6l-13.7 8.2c-5.9 3.5-7.6 11.3-3.8 17 6.5 9.7 14.4 20.1 23.5 30.6-9 7.7-18.6 14.8-28.7 21.2-5.4 3.4-7.1 10.5-3.9 16l7.9 13.9c3.4 5.9 11 7.9 16.8 4.2 12.5-7.9 24.6-17 36-26.8 10.7 9.6 22.3 18.6 34.6 26.6 5.8 3.7 13.6 1.9 17-4.1l8-13.9c3.1-5.5 1.5-12.5-3.8-16-9.2-6-18.4-13.1-27.2-20.9 1.5-1.7 2.9-3.3 4.3-5 17.1-20.6 29.6-41.7 36.8-62H540c6.6 0 12-5.4 12-12v-16c0-6.6-5.4-12-12-12h-64v-16c0-6.6-5.4-12-12-12h-16c-6.6 0-12 5.4-12 12v16h-64c-6.6 0-12 5.4-12 12v16c0 6.7 5.4 12.1 12 12.1z"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
136
src/assets/locales/en.json
Normal file
136
src/assets/locales/en.json
Normal file
@@ -0,0 +1,136 @@
|
||||
{
|
||||
"home": {
|
||||
"no-results": "No Search Results",
|
||||
"no-data": "No Data Configured"
|
||||
},
|
||||
"search": {
|
||||
"search-label": "Search",
|
||||
"search-placeholder": "Start typing to filter",
|
||||
"clear-search-tooltip": "Clear Search"
|
||||
},
|
||||
"login": {
|
||||
"title": "Dashy",
|
||||
"username-label": "Username",
|
||||
"password-label": "Password",
|
||||
"login-button": "Login",
|
||||
"remember-me-label": "Remember me for",
|
||||
"remember-me-never": "Never",
|
||||
"remember-me-hour": "4 Hours",
|
||||
"remember-me-day": "1 Day",
|
||||
"remember-me-week": "1 Week"
|
||||
},
|
||||
"config": {
|
||||
"main-tab": "Config",
|
||||
"view-config-tab": "View Config",
|
||||
"edit-config-tab": "Edit Config",
|
||||
"custom-css-tab": "Custom Styles",
|
||||
"heading": "Configuration Options",
|
||||
"download-config-button": "Download Config",
|
||||
"edit-config-button": "Edit Config",
|
||||
"edit-css-button": "Edit Custom CSS",
|
||||
"cloud-sync-button": "Enable Cloud Sync",
|
||||
"edit-cloud-sync-button": "Edit Cloud Sync",
|
||||
"rebuild-app-button": "Rebuild Application",
|
||||
"change-language-button": "Change App Language",
|
||||
"reset-settings-button": "Reset Local Settings",
|
||||
"app-info-button": "App Info",
|
||||
"app-version-note": "Dashy version",
|
||||
"backup-note": "It is recommend to make a backup of your configuration before making changes.",
|
||||
"reset-config-msg-l1": "This will remove all user settings from local storage, but won't effect your 'conf.yml' file.",
|
||||
"reset-config-msg-l2": "You should first backup any changes you've made locally, if you want to use them in the future.",
|
||||
"reset-config-msg-l3": "Are you sure you want to proceed?",
|
||||
"data-cleared-msg": "Data cleared successfully",
|
||||
"actions-label": "Actions",
|
||||
"copy-config-label": "Copy Config",
|
||||
"data-copied-msg": "Config has been copied to clipboard",
|
||||
"reset-config-label": "Reset Config",
|
||||
"css-save-btn": "Save Changes",
|
||||
"css-note-label": "Note",
|
||||
"css-note-l1": "You will need to refresh the page for your changes to take effect.",
|
||||
"css-note-l2": "Styles overrides are only stored locally, so it is recommended to make a copy of your CSS.",
|
||||
"css-note-l3": "To remove all custom styles, delete the contents and hit Save Changes"
|
||||
},
|
||||
"settings": {
|
||||
"theme-label": "Theme",
|
||||
"layout-label": "Layout",
|
||||
"layout-auto": "Auto",
|
||||
"layout-horizontal": "Horizontal",
|
||||
"layout-vertical": "Vertical",
|
||||
"item-size-label": "Item Size",
|
||||
"item-size-small": "Small",
|
||||
"item-size-medium": "Medium",
|
||||
"item-size-large": "Large",
|
||||
"config-launcher-label": "Config"
|
||||
},
|
||||
"language-switcher": {
|
||||
"title": "Change Application Language",
|
||||
"dropdown-label": "Select a Language",
|
||||
"save-button": "Save",
|
||||
"success-msg": "Language Updated to"
|
||||
},
|
||||
"theme-maker": {
|
||||
"title": "Theme Configurator",
|
||||
"export-button": "Export Custom Variables",
|
||||
"reset-button": "Reset Styles for",
|
||||
"show-all-button": "Show All Variables",
|
||||
"save-button": "Save",
|
||||
"cancel-button": "Cancel",
|
||||
"saved-toast": "{theme} Updated Successfully",
|
||||
"copied-toast": "Theme data for {theme} copied to clipboard",
|
||||
"reset-toast": "Custom Colors for {theme} Removed"
|
||||
},
|
||||
"config-editor": {
|
||||
"save-location-label": "Save Location",
|
||||
"location-local-label": "Apply Locally",
|
||||
"location-disk-label": "Write Changes to Config File",
|
||||
"save-button": "Save Changes",
|
||||
"valid-label": "Config is Valid",
|
||||
"status-success-msg": "Task Complete",
|
||||
"status-fail-msg": "Task Failed",
|
||||
"success-msg-disk": "Config file written to disk successfully",
|
||||
"success-msg-local": "Local changes saved successfully",
|
||||
"success-note-l1": "The app should rebuild automatically.",
|
||||
"success-note-l2": "This may take up to a minute.",
|
||||
"success-note-l3": "You will need to refresh the page for changes to take effect.",
|
||||
"error-msg-save-mode": "Please select a Save Mode: Local or File",
|
||||
"error-msg-cannot-save": "An error occurred saving config",
|
||||
"error-msg-bad-json": "Error in JSON, possibly malformed",
|
||||
"warning-msg-validation": "Validation Warning"
|
||||
},
|
||||
"app-rebuild": {
|
||||
"title": "Rebuild Application",
|
||||
"rebuild-note-l1": "A rebuild is required for changes written to the conf.yml file to take effect.",
|
||||
"rebuild-note-l2": "This should happen automatically, but if it hasn't, you can manually trigger it here.",
|
||||
"rebuild-note-l3": "This is not required for modifications stored locally.",
|
||||
"rebuild-button": "Start Build",
|
||||
"rebuilding-status-1": "Building...",
|
||||
"rebuilding-status-2": "This may take a few minutes",
|
||||
"error-permission": "You do no have permission to trigger this action",
|
||||
"success-msg": "Build completed succesfully",
|
||||
"fail-msg": "Build operation failed",
|
||||
"reload-note": "A page reload is now required for changes to take effect",
|
||||
"reload-button": "Reload Page"
|
||||
},
|
||||
"cloud-sync": {
|
||||
"title": "Cloud Backup & Restore",
|
||||
"intro-l1": "Cloud backup and restore is an optional feature, that enables you to upload your config to the internet, and then restore it on any other device or instance of Dashy.",
|
||||
"intro-l2": "All data is fully end-to-end encrypted with AES, using your password as the key.",
|
||||
"intro-l3": "For more info, please see the",
|
||||
"backup-title-setup": "Make a Backup",
|
||||
"backup-title-update": "Update Backup",
|
||||
"password-label-setup": "Choose a Password",
|
||||
"password-label-update": "Enter your Password",
|
||||
"backup-button-setup": "Backup",
|
||||
"backup-button-update": "Update Backup",
|
||||
"backup-id-label": "Your Backup ID",
|
||||
"backup-id-note": "This is used to restore from backups later. So keep it, along with your password somewhere safe.",
|
||||
"restore-title": "Restore a Backup",
|
||||
"restore-id-label": "Restore ID",
|
||||
"restore-password-label": "Password",
|
||||
"restore-button": "Restore",
|
||||
"backup-error-unknown": "Unable to process request",
|
||||
"backup-error-password": "Incorrect password. Please enter your current password.",
|
||||
"backup-success-msg": "Completed Successfully",
|
||||
"restore-success-msg": "Config Restored Successfully"
|
||||
}
|
||||
}
|
||||
@@ -1,55 +1,55 @@
|
||||
<template>
|
||||
<div class="cloud-backup-restore-wrapper">
|
||||
<div class="section intro">
|
||||
<h2>Cloud Backup & Restore</h2>
|
||||
<h2>{{ $t('cloud-sync.title') }}</h2>
|
||||
<p class="intro">
|
||||
Cloud backup and restore is an optional feature, that enables you to upload your
|
||||
config to the internet, and then restore it on any other device or instance of Dashy.
|
||||
{{ $t('cloud-sync.intro-l1') }}
|
||||
<br><br>
|
||||
All data is fully end-to-end encrypted with AES, using your password as the key.
|
||||
{{ $t('cloud-sync.intro-l2') }}
|
||||
<br>
|
||||
For more info, please see the
|
||||
{{ $t('cloud-sync.intro-l3') }}
|
||||
<a href="https://github.com/Lissy93/dashy/blob/master/docs/backup-restore.md">docs</a>
|
||||
</p>
|
||||
</div>
|
||||
<div class="section backup-section">
|
||||
<h3 v-if="backupId">Update Backup</h3>
|
||||
<h3 v-else>Make a Backup</h3>
|
||||
<h3 v-if="backupId">{{ $t('cloud-sync.backup-title-setup') }}</h3>
|
||||
<h3 v-else>{{ $t('cloud-sync.backup-title-setup') }}</h3>
|
||||
<Input
|
||||
v-model="backupPassword"
|
||||
name="backup-password"
|
||||
:label="backupId ? 'Enter your Password' : 'Choose a Password'"
|
||||
:label="backupId
|
||||
? $t('cloud-sync.password-label-update') : $t('cloud-sync.password-label-setup')"
|
||||
layout="vertical"
|
||||
type="password"
|
||||
/>
|
||||
<Button :click="checkPass">
|
||||
<template v-slot:text>{{backupId ? 'Update Backup' : 'Backup'}}</template>
|
||||
<template v-slot:text>
|
||||
{{backupId
|
||||
? $t('cloud-sync.backup-button-update') : $t('cloud-sync.backup-button-setup')}}
|
||||
</template>
|
||||
<template v-slot:icon><IconBackup /></template>
|
||||
</Button>
|
||||
<div class="results-view" v-if="backupId">
|
||||
<span class="backup-id-label">Your Backup ID: </span>
|
||||
<span class="backup-id-label">{{ $t('cloud-sync.backup-id-label') }}: </span>
|
||||
<pre class="backup-id-value">{{ backupId }}</pre>
|
||||
<span class="backup-id-note">
|
||||
This is used to restore from backups later.
|
||||
So keep it, along with your password somewhere safe.
|
||||
</span>
|
||||
<span class="backup-id-note">{{ $t('cloud-sync.backup-id-note') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="section restore-section">
|
||||
<h3>Restore a Backup</h3>
|
||||
<h3>{{ $t('cloud-sync.restore-title') }}</h3>
|
||||
<Input
|
||||
v-model="restoreCode"
|
||||
name="restore-code"
|
||||
label="Restore ID"
|
||||
:label="$t('cloud-sync.restore-id-label')"
|
||||
/>
|
||||
<Input
|
||||
v-model="restorePassword"
|
||||
name="restore-password"
|
||||
label="Password"
|
||||
:label="$t('cloud-sync.restore-password-label')"
|
||||
type="password"
|
||||
/>
|
||||
<Button :click="restoreBackup">
|
||||
<template v-slot:text>Restore</template>
|
||||
<template v-slot:text>{{ $t('cloud-sync.restore-button') }}</template>
|
||||
<template v-slot:icon><IconRestore /></template>
|
||||
</Button>
|
||||
</div>
|
||||
@@ -101,7 +101,7 @@ export default {
|
||||
} else if (savedHash === this.makeHash(this.backupPassword)) {
|
||||
this.makeUpdate();
|
||||
} else {
|
||||
this.showErrorMsg('Incorrect password. Please enter your current password.');
|
||||
this.showErrorMsg(this.$t('cloud-sync.backup-error-password'));
|
||||
}
|
||||
},
|
||||
makeBackup() {
|
||||
@@ -113,7 +113,7 @@ export default {
|
||||
this.updateUiAfterBackup(response.data.backupId, false);
|
||||
}
|
||||
}).catch(() => {
|
||||
this.showErrorMsg('Unable to process request');
|
||||
this.showErrorMsg(this.$t('cloud-sync.backup-error-unknown'));
|
||||
});
|
||||
},
|
||||
makeUpdate() {
|
||||
@@ -125,7 +125,7 @@ export default {
|
||||
this.updateUiAfterBackup(response.data.backupId, true);
|
||||
}
|
||||
}).catch(() => {
|
||||
this.showErrorMsg('Unable to process request');
|
||||
this.showErrorMsg(this.$t('cloud-sync.backup-error-unknown'));
|
||||
});
|
||||
},
|
||||
restoreFromBackup(config, backupId) {
|
||||
@@ -136,12 +136,14 @@ export default {
|
||||
localStorage.setItem(localStorageKeys.THEME, config.appConfig.theme);
|
||||
}
|
||||
this.setBackupIdLocally(backupId, this.restorePassword);
|
||||
this.showSuccessMsg('Config Restored Succesfully');
|
||||
this.showSuccessMsg(this.$t('cloud-sync.restore-success-msg'));
|
||||
setTimeout(() => { location.reload(); }, 1500); // eslint-disable-line no-restricted-globals
|
||||
},
|
||||
updateUiAfterBackup(backupId, isUpdate = false) {
|
||||
this.setBackupIdLocally(backupId, this.backupPassword);
|
||||
this.showSuccessMsg(`${isUpdate ? 'Update' : 'Backup'} Completed Succesfully`);
|
||||
this.showSuccessMsg(
|
||||
`${isUpdate ? 'Update' : 'Backup'} ${this.$t('cloud-sync.backup-success-msg')}`,
|
||||
);
|
||||
this.backupPassword = '';
|
||||
},
|
||||
showErrorMsg(errorMsg) {
|
||||
|
||||
@@ -1,67 +1,74 @@
|
||||
<template>
|
||||
<Tabs :navAuto="true" name="Add Item" ref="tabView">
|
||||
<TabItem name="Config" class="main-tab">
|
||||
<TabItem :name="$t('config.main-tab')" class="main-tab">
|
||||
<div class="main-options-container">
|
||||
<h2>Configuration Options</h2>
|
||||
<a class="hyperlink-wrapper" @click="downloadConfigFile('conf.yml', yaml)">
|
||||
<button class="config-button center">
|
||||
<DownloadIcon class="button-icon"/>
|
||||
Download Config
|
||||
<DownloadIcon class="button-icon"/>
|
||||
{{ $t('config.download-config-button') }}
|
||||
</button>
|
||||
</a>
|
||||
<button class="config-button center" @click="() => navigateToTab(2)">
|
||||
<EditIcon class="button-icon"/>
|
||||
Edit Config
|
||||
{{ $t('config.edit-config-button') }}
|
||||
</button>
|
||||
<button class="config-button center" @click="() => navigateToTab(3)">
|
||||
<CustomCssIcon class="button-icon"/>
|
||||
Edit Custom CSS
|
||||
{{ $t('config.edit-css-button') }}
|
||||
</button>
|
||||
<button class="config-button center" @click="openCloudSync()">
|
||||
<CloudIcon class="button-icon"/>
|
||||
{{backupId ? 'Edit Cloud Sync' : 'Enable Cloud Sync'}}
|
||||
{{backupId ? $t('config.edit-cloud-sync-button') : $t('config.cloud-sync-button') }}
|
||||
</button>
|
||||
<button class="config-button center" @click="openLanguageSwitchModal()">
|
||||
<LanguageIcon class="button-icon"/>
|
||||
{{ $t('config.change-language-button') }}
|
||||
</button>
|
||||
<button class="config-button center" @click="openRebuildAppModal()">
|
||||
<RebuildIcon class="button-icon"/>
|
||||
Rebuild Application
|
||||
{{ $t('config.rebuild-app-button') }}
|
||||
</button>
|
||||
<button class="config-button center" @click="resetLocalSettings()">
|
||||
<DeleteIcon class="button-icon"/>
|
||||
Reset Local Settings
|
||||
{{ $t('config.reset-settings-button') }}
|
||||
</button>
|
||||
<button class="config-button center" @click="openAboutModal()">
|
||||
<IconAbout class="button-icon" />
|
||||
App Info
|
||||
{{ $t('config.app-info-button') }}
|
||||
</button>
|
||||
<p class="small-screen-note" style="display: none;">
|
||||
You are using a very small screen, and some screens in this menu may not be optimal
|
||||
</p>
|
||||
<p class="app-version">Dashy version {{ appVersion }}</p>
|
||||
<p class="app-version">{{ $t('config.app-version-note') }} {{ appVersion }}</p>
|
||||
<p class="language">{{ getLanguage() }}</p>
|
||||
<div class="config-note">
|
||||
<span>
|
||||
It is recommend to make a backup of your conf.yml file before making changes.
|
||||
</span>
|
||||
<span>{{ $t('config.backup-note') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Rebuild App Modal -->
|
||||
<RebuildApp />
|
||||
</TabItem>
|
||||
<TabItem name="View Config" class="code-container">
|
||||
<TabItem :name="$t('config.view-config-tab')" class="code-container">
|
||||
<pre id="conf-yaml">{{yaml}}</pre>
|
||||
<div class="yaml-action-buttons">
|
||||
<h2>Actions</h2>
|
||||
<h2>{{ $t('config.actions-label') }}</h2>
|
||||
<a class="yaml-button download" @click="downloadConfigFile('conf.yml', yaml)">
|
||||
Download Config
|
||||
{{ $t('config.download-config-button') }}
|
||||
</a>
|
||||
<a class="yaml-button copy" @click="copyConfigToClipboard()">
|
||||
{{ $t('config.copy-config-label') }}
|
||||
</a>
|
||||
<a class="yaml-button reset" @click="resetLocalSettings()">
|
||||
{{ $t('config.reset-config-label') }}
|
||||
</a>
|
||||
<a class="yaml-button copy" @click="copyConfigToClipboard()">Copy Config</a>
|
||||
<a class="yaml-button reset" @click="resetLocalSettings()">Reset Config</a>
|
||||
</div>
|
||||
</TabItem>
|
||||
<TabItem name="Edit Config">
|
||||
<TabItem :name="$t('config.edit-config-tab')">
|
||||
<JsonEditor :config="config" />
|
||||
</TabItem>
|
||||
<TabItem name="Custom Styles">
|
||||
<CustomCssEditor :config="config" initialCss="hello" />
|
||||
<TabItem :name="$t('config.custom-css-tab')">
|
||||
<CustomCssEditor :config="config" />
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
</template>
|
||||
@@ -74,6 +81,7 @@ import 'highlight.js/styles/mono-blue.css';
|
||||
|
||||
import JsonToYaml from '@/utils/JsonToYaml';
|
||||
import { localStorageKeys, modalNames } from '@/utils/defaults';
|
||||
import { getUsersLanguage } from '@/utils/ConfigHelpers';
|
||||
import JsonEditor from '@/components/Configuration/JsonEditor';
|
||||
import CustomCssEditor from '@/components/Configuration/CustomCss';
|
||||
import RebuildApp from '@/components/Configuration/RebuildApp';
|
||||
@@ -84,6 +92,7 @@ import EditIcon from '@/assets/interface-icons/config-edit-json.svg';
|
||||
import CustomCssIcon from '@/assets/interface-icons/config-custom-css.svg';
|
||||
import CloudIcon from '@/assets/interface-icons/cloud-backup-restore.svg';
|
||||
import RebuildIcon from '@/assets/interface-icons/application-rebuild.svg';
|
||||
import LanguageIcon from '@/assets/interface-icons/config-language.svg';
|
||||
import IconAbout from '@/assets/interface-icons/application-about.svg';
|
||||
|
||||
export default {
|
||||
@@ -115,6 +124,7 @@ export default {
|
||||
EditIcon,
|
||||
CloudIcon,
|
||||
CustomCssIcon,
|
||||
LanguageIcon,
|
||||
RebuildIcon,
|
||||
IconAbout,
|
||||
},
|
||||
@@ -133,20 +143,21 @@ export default {
|
||||
openCloudSync() {
|
||||
this.$modal.show(modalNames.CLOUD_BACKUP);
|
||||
},
|
||||
openLanguageSwitchModal() {
|
||||
this.$modal.show(modalNames.LANG_SWITCHER);
|
||||
},
|
||||
copyConfigToClipboard() {
|
||||
navigator.clipboard.writeText(this.jsonParser(this.config));
|
||||
// event.target.textContent = 'Copied to clipboard';
|
||||
this.$toasted.show(this.$t('config.data-copied-msg'));
|
||||
},
|
||||
/* Checks that the user is sure, then resets site-wide local storage, and reloads page */
|
||||
resetLocalSettings() {
|
||||
const msg = 'This will remove all user settings from local storage, '
|
||||
+ 'but won\'t effect your \'conf.yml\' file. '
|
||||
+ 'It is recommend to make a backup of your modified YAML settings first.\n\n'
|
||||
+ 'Are you sure you want to proceed?';
|
||||
const msg = `${this.$t('config.reset-config-msg-l1')
|
||||
}${this.$t('config.reset-config-msg-l2')}\n\n${this.$t('config.reset-config-msg-l3')}`;
|
||||
const isTheUserSure = confirm(msg); // eslint-disable-line no-alert, no-restricted-globals
|
||||
if (isTheUserSure) {
|
||||
localStorage.clear();
|
||||
this.$toasted.show('Data cleared succesfully');
|
||||
this.$toasted.show(this.$t('config.data-cleared-msg'));
|
||||
setTimeout(() => {
|
||||
location.reload(true); // eslint-disable-line no-restricted-globals
|
||||
}, 1900);
|
||||
@@ -162,11 +173,19 @@ export default {
|
||||
element.click();
|
||||
document.body.removeChild(element);
|
||||
},
|
||||
/* Highlights the YAML config in View config tab */
|
||||
initiateStntaxHighlighter() {
|
||||
hljs.registerLanguage('yaml', yaml);
|
||||
const highlighted = hljs.highlight(this.jsonParser(this.config), { language: 'yaml' }).value;
|
||||
document.getElementById('conf-yaml').innerHTML = highlighted;
|
||||
},
|
||||
getLanguage() {
|
||||
const lang = getUsersLanguage();
|
||||
return lang ? `${lang.flag} ${lang.name}` : '';
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
hljs.registerLanguage('yaml', yaml);
|
||||
const highlighted = hljs.highlight(this.jsonParser(this.config), { language: 'yaml' }).value;
|
||||
document.getElementById('conf-yaml').innerHTML = highlighted;
|
||||
this.initiateStntaxHighlighter();
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -213,10 +232,11 @@ a.config-button, button.config-button {
|
||||
}
|
||||
}
|
||||
|
||||
p.app-version {
|
||||
p.app-version, p.language {
|
||||
margin: 0.5rem auto;
|
||||
font-size: 1rem;
|
||||
color: var(--transparent-white-50);
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
div.code-container {
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
<template>
|
||||
<div class="css-editor-outer">
|
||||
<prism-editor class="my-editor" v-model="customCss" :highlight="highlighter" line-numbers />
|
||||
<button class="save-button" @click="save()">Save Changes</button>
|
||||
<button class="save-button" @click="save()">{{ $t('config.css-save-btn') }}</button>
|
||||
<p class="quick-note">
|
||||
<b>Note</b>: You will need to refresh the page for your changes to take effect.
|
||||
Styles overides are only stored locally, so it is reccomended to make a copy of your CSS.
|
||||
To remove all custom styles, delete the contents and hit Save Changes
|
||||
<b>{{ $t('config.css-note-label') }}:</b>
|
||||
{{ $t('config.css-note-l1') }} {{ $t('config.css-note-l2') }} {{ $t('config.css-note-l3') }}
|
||||
</p>
|
||||
<CustomThemeMaker :themeToEdit="currentTheme" class="color-config" />
|
||||
</div>
|
||||
|
||||
@@ -8,19 +8,25 @@
|
||||
/>
|
||||
<!-- Options raido, and save button -->
|
||||
<div class="save-options">
|
||||
<span class="save-option-title">Save Location:</span>
|
||||
<span class="save-option-title">{{ $t('config-editor.save-location-label') }}:</span>
|
||||
<div class="option">
|
||||
<input type="radio" id="local" value="local"
|
||||
v-model="saveMode" class="radio-option" :disabled="!allowWriteToDisk" />
|
||||
<label for="local" class="save-option-label">Apply Locally</label>
|
||||
<label for="local" class="save-option-label">
|
||||
{{ $t('config-editor.location-local-label') }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="option">
|
||||
<input type="radio" id="file" value="file" v-model="saveMode" class="radio-option"
|
||||
:disabled="!allowWriteToDisk" />
|
||||
<label for="file" class="save-option-label">Write Changes to Config File</label>
|
||||
<label for="file" class="save-option-label">
|
||||
{{ $t('config-editor.location-disk-label') }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<button :class="`save-button ${!isValid ? 'err' : ''}`" @click="save()">Save Changes</button>
|
||||
<button :class="`save-button ${!isValid ? 'err' : ''}`" @click="save()">
|
||||
{{ $t('config-editor.save-button') }}
|
||||
</button>
|
||||
<!-- List validation warnings -->
|
||||
<p class="errors">
|
||||
<ul>
|
||||
@@ -28,24 +34,23 @@
|
||||
{{error.msg}}
|
||||
</li>
|
||||
<li v-if="errorMessages.length < 1" class="type-valid">
|
||||
Config is Valid
|
||||
{{ $t('config-editor.valid-label') }}
|
||||
</li>
|
||||
</ul>
|
||||
</p>
|
||||
<!-- Information notes -->
|
||||
<p v-if="saveSuccess !== undefined"
|
||||
:class="`response-output status-${saveSuccess ? 'success' : 'fail'}`">
|
||||
{{saveSuccess ? 'Task Complete' : 'Task Failed'}}
|
||||
{{saveSuccess
|
||||
? $t('config-editor.status-success-msg') : $t('config-editor.status-fail-msg') }}
|
||||
</p>
|
||||
<p class="response-output">{{ responseText }}</p>
|
||||
<p v-if="saveSuccess" class="response-output">
|
||||
The app should rebuild automatically.
|
||||
This may take up to a minute.
|
||||
You will need to refresh the page for changes to take effect.
|
||||
</p>
|
||||
<p class="note">
|
||||
It is recommend to backup your existing confiruration before making any changes.
|
||||
{{ $t('config-editor.success-note-l1') }}
|
||||
{{ $t('config-editor.success-note-l2') }}
|
||||
{{ $t('config-editor.success-note-l3') }}
|
||||
</p>
|
||||
<p class="note">{{ $t('config.backup-note') }}</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -103,7 +108,7 @@ export default {
|
||||
} else if (this.saveMode === 'file') {
|
||||
this.writeConfigToDisk();
|
||||
} else {
|
||||
this.$toasted.show('Please select a Save Mode: Local or File');
|
||||
this.$toasted.show(this.$t('config-editor.error-msg-save-mode'));
|
||||
}
|
||||
},
|
||||
writeConfigToDisk() {
|
||||
@@ -121,9 +126,9 @@ export default {
|
||||
this.responseText = response.data.message;
|
||||
if (this.saveSuccess) {
|
||||
this.carefullyClearLocalStorage();
|
||||
this.showToast('Config file written to disk succesfully', true);
|
||||
this.showToast(this.$t('config-editor.success-msg-disk'), true);
|
||||
} else {
|
||||
this.showToast('An error occurred saving config', false);
|
||||
this.showToast(this.$t('config-editor.error-msg-cannot-save'), false);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
@@ -146,7 +151,7 @@ export default {
|
||||
if (data.appConfig.theme) {
|
||||
localStorage.setItem(localStorageKeys.THEME, data.appConfig.theme);
|
||||
}
|
||||
this.showToast('Changes saved succesfully', true);
|
||||
this.showToast(this.$t('config-editor.success-msg-local'), true);
|
||||
},
|
||||
carefullyClearLocalStorage() {
|
||||
localStorage.removeItem(localStorageKeys.PAGE_INFO);
|
||||
@@ -160,7 +165,8 @@ export default {
|
||||
case 'validation':
|
||||
errorMessages.push({
|
||||
type: 'validation',
|
||||
msg: `Validatation Warning: ${error.error.keyword} ${error.error.message}`,
|
||||
msg: `${this.$t('config-editor.warning-msg-validation')}: `
|
||||
+ `${error.error.keyword} ${error.error.message}`,
|
||||
});
|
||||
break;
|
||||
case 'error':
|
||||
@@ -172,7 +178,7 @@ export default {
|
||||
default:
|
||||
errorMessages.push({
|
||||
type: 'editor',
|
||||
msg: 'Error in JSON',
|
||||
msg: this.$t('config-editor.error-msg-bad-json'),
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -2,35 +2,41 @@
|
||||
<modal :name="modalName" :resizable="true" width="50%" height="60%" classes="dashy-modal">
|
||||
<div class="rebuild-app-container">
|
||||
<!-- Title, intro and start button -->
|
||||
<h3 class="rebuild-app-title">Rebuild Application</h3>
|
||||
<h3 class="rebuild-app-title">{{ $t('app-rebuild.title') }}</h3>
|
||||
<p>
|
||||
A rebuild is required for changes written to the conf.yml file to take effect.
|
||||
This should happen automatically, but if it hasn't, you can manually trigger it here.<br>
|
||||
This is not required for modifications stored locally.
|
||||
{{ $t('app-rebuild.rebuild-note-l1') }}
|
||||
{{ $t('app-rebuild.rebuild-note-l2') }}<br>
|
||||
{{ $t('app-rebuild.rebuild-note-l3') }}
|
||||
</p>
|
||||
<Button :click="startBuild" :disabled="loading || !allowRebuild" :disallow="!allowRebuild">
|
||||
<template v-slot:text>{{ loading ? 'Building...' : 'Start Build' }}</template>
|
||||
<template v-slot:text>
|
||||
{{ loading ? $t('app-rebuild.rebuilding-status-1') : $t('app-rebuild.rebuild-button') }}
|
||||
</template>
|
||||
<template v-slot:icon><RebuildIcon /></template>
|
||||
</Button>
|
||||
<div v-if="!allowRebuild">
|
||||
<p class="disallow-rebuild-msg">You do no have permission to trigger this action</p>
|
||||
<p class="disallow-rebuild-msg">{{ $t('app-rebuild.error-permission') }}</p>
|
||||
</div>
|
||||
<!-- Loading animation and text (shown while build is happening) -->
|
||||
<div v-if="loading" class="loader-info">
|
||||
<LoadingAnimation class="loader" />
|
||||
<p class="loading-message">This may take a few minutes...</p>
|
||||
<p class="loading-message">{{ $t('app-rebuild.rebuilding-status-2') }}...</p>
|
||||
</div>
|
||||
<!-- Build response, and next actions (shown after build is done) -->
|
||||
<div class="rebuild-response" v-if="success !== undefined">
|
||||
<p v-if="success" class="response-status success">✅ Build completed succesfully</p>
|
||||
<p v-else class="response-status failure">❌ Build operation failed</p>
|
||||
<p v-if="success" class="response-status success">
|
||||
✅ {{ $t('app-rebuild.success-msg') }}
|
||||
</p>
|
||||
<p v-else class="response-status failure">
|
||||
❌ {{ $t('app-rebuild.fail-msg') }}
|
||||
</p>
|
||||
<pre class="output"><code>{{ output || error }}</code></pre>
|
||||
<p class="rebuild-message">{{ message }}</p>
|
||||
<p v-if="success" class="rebuild-message">
|
||||
A page reload is now required for changes to take effect
|
||||
{{ $t('app-rebuild.reload-note') }}
|
||||
</p>
|
||||
<Button :click="refreshPage" v-if="success">
|
||||
<template v-slot:text>Reload Page</template>
|
||||
<template v-slot:text>{{ $t('app-rebuild.reload-button') }}</template>
|
||||
<template v-slot:icon><ReloadIcon /></template>
|
||||
</Button>
|
||||
</div>
|
||||
@@ -65,6 +71,7 @@ export default {
|
||||
allowRebuild: true,
|
||||
}),
|
||||
methods: {
|
||||
/* Calls to the rebuild endpoint, to kickoff the app build */
|
||||
startBuild() {
|
||||
const baseUrl = process.env.VUE_APP_DOMAIN || window.location.origin;
|
||||
const endpoint = `${baseUrl}/config-manager/rebuild`;
|
||||
@@ -77,6 +84,7 @@ export default {
|
||||
this.finished({ success: false, error });
|
||||
});
|
||||
},
|
||||
/* Called when rebuild is complete, updates UI with either success or fail message */
|
||||
finished(responseData) {
|
||||
this.loading = false;
|
||||
if (responseData) {
|
||||
@@ -89,7 +97,8 @@ export default {
|
||||
this.error = error;
|
||||
}
|
||||
this.$toasted.show(
|
||||
(this.success ? '✅ Build Completed Succesfully' : '❌ Build Failed'),
|
||||
(this.success
|
||||
? `✅ ${this.$t('app-rebuild.success-msg')}` : `❌ ${this.$t('app-rebuild.fail-msg')}`),
|
||||
{ className: `toast-${this.success ? 'success' : 'error'}` },
|
||||
);
|
||||
},
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<template>
|
||||
<div class="config-options">
|
||||
<!-- Button and label -->
|
||||
<span>Config</span>
|
||||
<span>{{ $t('settings.config-launcher-label') }}</span>
|
||||
<div class="config-buttons">
|
||||
<IconSpanner @click="showEditor()" tabindex="-2"
|
||||
v-tooltip="tooltip('Update configuration locally')" />
|
||||
v-tooltip="tooltip('Update configuration')" />
|
||||
<IconCloud @click="showCloudModal()" tabindex="-2"
|
||||
v-tooltip="tooltip('Backup / restore cloud config')" />
|
||||
</div>
|
||||
@@ -20,6 +20,13 @@
|
||||
@closed="$emit('modalChanged', false)" classes="dashy-modal">
|
||||
<CloudBackupRestore :config="combineConfig()" />
|
||||
</modal>
|
||||
|
||||
<!-- Modal for manually changing locale -->
|
||||
<modal :name="modalNames.LANG_SWITCHER" classes="dashy-modal"
|
||||
:resizable="true" width="30%" height="25%">
|
||||
<LanguageSwitcher />
|
||||
</modal>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -29,6 +36,7 @@ import IconSpanner from '@/assets/interface-icons/config-editor.svg';
|
||||
import IconCloud from '@/assets/interface-icons/cloud-backup-restore.svg';
|
||||
import ConfigContainer from '@/components/Configuration/ConfigContainer';
|
||||
import CloudBackupRestore from '@/components/Configuration/CloudBackupRestore';
|
||||
import LanguageSwitcher from '@/components/Settings/LanguageSwitcher';
|
||||
import { topLevelConfKeys, localStorageKeys, modalNames } from '@/utils/defaults';
|
||||
|
||||
export default {
|
||||
@@ -43,6 +51,7 @@ export default {
|
||||
IconCloud,
|
||||
ConfigContainer,
|
||||
CloudBackupRestore,
|
||||
LanguageSwitcher,
|
||||
},
|
||||
props: {
|
||||
sections: Array,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div :class="`theme-configurator-wrapper ${showingAllVars ? 'showing-all' : ''}`">
|
||||
<h3 class="configurator-title">Theme Configurator</h3>
|
||||
<h3 class="configurator-title">{{ $t('theme-maker.title') }}</h3>
|
||||
<div class="color-row-container">
|
||||
<div class="color-row" v-for="colorName in Object.keys(customColors)" :key="colorName">
|
||||
<label :for="`color-input-${colorName}`" class="color-name">
|
||||
@@ -33,17 +33,21 @@
|
||||
</div> <!-- End of color list -->
|
||||
</div>
|
||||
<p @click="exportToClipboard" class="action-text-btn">
|
||||
Export Custom Variables
|
||||
{{ $t('theme-maker.export-button') }}
|
||||
</p>
|
||||
<p @click="resetAndSave" class="action-text-btn show-all-vars-btn">
|
||||
Reset Styles for '{{ themeToEdit }}'
|
||||
{{ $t('theme-maker.reset-button') }} '{{ themeToEdit }}'
|
||||
</p>
|
||||
<p @click="findAllVariableNames" class="action-text-btn">
|
||||
Show All Variables
|
||||
{{ $t('theme-maker.show-all-button') }}
|
||||
</p>
|
||||
<div class="action-buttons">
|
||||
<Button :click="saveChanges"><SaveIcon />Save</Button>
|
||||
<Button :click="resetUnsavedColors"><CancelIcon />Cancel</Button>
|
||||
<Button :click="saveChanges">
|
||||
<SaveIcon /> {{ $t('theme-maker.save-button') }}
|
||||
</Button>
|
||||
<Button :click="resetUnsavedColors">
|
||||
<CancelIcon /> {{ $t('theme-maker.cancel-button') }}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -89,7 +93,7 @@ export default {
|
||||
const priorSettings = JSON.parse(localStorage[localStorageKeys.CUSTOM_COLORS] || '{}');
|
||||
priorSettings[this.themeToEdit] = this.customColors;
|
||||
localStorage.setItem(localStorageKeys.CUSTOM_COLORS, JSON.stringify(priorSettings));
|
||||
this.$toasted.show('Theme Updates Succesfully');
|
||||
this.$toasted.show(this.$t('theme-maker.saved-toast', { theme: this.themeToEdit }));
|
||||
this.$emit('closeThemeConfigurator');
|
||||
},
|
||||
/* Itterates over available variables, removing them from the DOM */
|
||||
@@ -107,7 +111,7 @@ export default {
|
||||
delete priorSettings[this.themeToEdit];
|
||||
localStorage.setItem(localStorageKeys.CUSTOM_COLORS, JSON.stringify(priorSettings));
|
||||
this.resetUnsavedColors();
|
||||
this.$toasted.show(`Custom Colors for ${this.themeToEdit} Removed`);
|
||||
this.$toasted.show(this.$t('theme-maker.reset-toast', { theme: this.themeToEdit }));
|
||||
},
|
||||
/* Generates CSS for the currently applied variables, and copys to users clipboard */
|
||||
exportToClipboard() {
|
||||
@@ -117,7 +121,7 @@ export default {
|
||||
clipboardText += (`--${customVar}: ${this.customColors[customVar]};\n`);
|
||||
});
|
||||
navigator.clipboard.writeText(clipboardText);
|
||||
this.$toasted.show(`Theme data for ${themeName} copied to clipboard`);
|
||||
this.$toasted.show(this.$t('theme-maker.copied-toast', { theme: themeName }));
|
||||
},
|
||||
/* Returns a JSON object, with the variable name as key, and color as value */
|
||||
makeInitialData(variableArray) {
|
||||
|
||||
@@ -1,13 +1,25 @@
|
||||
<template>
|
||||
<div>
|
||||
<span class="options-label">Icon Size</span>
|
||||
<span class="options-label">{{ $t('settings.item-size-label') }}</span>
|
||||
<div class="display-options">
|
||||
<IconSmall @click="updateIconSize('small')" v-tooltip="tooltip('Small')"
|
||||
:class="`layout-icon ${iconSize === 'small' ? 'selected' : ''}`" tabindex="-2" />
|
||||
<IconMedium @click="updateIconSize('medium')" v-tooltip="tooltip('Medium')"
|
||||
:class="`layout-icon ${iconSize === 'medium' ? 'selected' : ''}`" tabindex="-2" />
|
||||
<IconLarge @click="updateIconSize('large')" v-tooltip="tooltip('Large')"
|
||||
:class="`layout-icon ${iconSize === 'large' ? 'selected' : ''}`" tabindex="-2" />
|
||||
<IconSmall
|
||||
@click="updateIconSize('small')"
|
||||
v-tooltip="tooltip($t('settings.item-size-small'))"
|
||||
:class="`layout-icon ${iconSize === 'small' ? 'selected' : ''}`"
|
||||
tabindex="-2"
|
||||
/>
|
||||
<IconMedium
|
||||
@click="updateIconSize('medium')"
|
||||
v-tooltip="tooltip($t('settings.item-size-medium'))"
|
||||
:class="`layout-icon ${iconSize === 'medium' ? 'selected' : ''}`"
|
||||
tabindex="-2"
|
||||
/>
|
||||
<IconLarge
|
||||
@click="updateIconSize('large')"
|
||||
v-tooltip="tooltip($t('settings.item-size-large'))"
|
||||
:class="`layout-icon ${iconSize === 'large' ? 'selected' : ''}`"
|
||||
tabindex="-2"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
110
src/components/Settings/LanguageSwitcher.vue
Normal file
110
src/components/Settings/LanguageSwitcher.vue
Normal file
@@ -0,0 +1,110 @@
|
||||
<template>
|
||||
<div class="language-switcher">
|
||||
<h3 class="title">{{ $t('language-switcher.title') }}</h3>
|
||||
<p class="intro">{{ $t('language-switcher.dropdown-label') }}:</p>
|
||||
<v-select
|
||||
v-model="language"
|
||||
:selectOnTab="true"
|
||||
:options="availibleLanguages"
|
||||
class="language-dropdown"
|
||||
label="name"
|
||||
:input="setLangLocally()"
|
||||
/>
|
||||
<Button class="save-button" :click="saveLanguage" :disallow="!language">
|
||||
{{ $t('language-switcher.save-button') }}
|
||||
<SaveConfigIcon />
|
||||
</Button>
|
||||
<p v-if="language">{{ language.flag }} {{ language.name }}</p>
|
||||
<p v-if="$i18n.availableLocales.length <= 1" class="sad-times">
|
||||
There are not currently any additional languages supported,
|
||||
but stay tuned as more are on their way!
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Button from '@/components/FormElements/Button';
|
||||
import { languages } from '@/utils/languages';
|
||||
import SaveConfigIcon from '@/assets/interface-icons/save-config.svg';
|
||||
import { localStorageKeys, modalNames } from '@/utils/defaults';
|
||||
|
||||
export default {
|
||||
name: 'LanguageSwitcher',
|
||||
inject: ['config'],
|
||||
components: {
|
||||
Button,
|
||||
SaveConfigIcon,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
availibleLanguages: languages,
|
||||
language: '',
|
||||
modalName: modalNames.LANG_SWITCHER,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
/* Save language to local storage, show success msg and close modal */
|
||||
saveLanguage() {
|
||||
const selectedLanguage = this.language;
|
||||
if (this.checkLocale(selectedLanguage)) {
|
||||
localStorage.setItem(localStorageKeys.LANGUAGE, selectedLanguage.code);
|
||||
this.setLangLocally();
|
||||
const successMsg = `${selectedLanguage.flag} `
|
||||
+ `${this.$t('language-switcher.success-msg')} ${selectedLanguage.name}`;
|
||||
this.$toasted.show(successMsg, { className: 'toast-success' });
|
||||
this.$modal.hide(this.modalName);
|
||||
} else {
|
||||
this.$toasted.show('Unable to update language', { className: 'toast-error' });
|
||||
}
|
||||
},
|
||||
/* Check language is supported, before saving */
|
||||
checkLocale(selectedLanguage) {
|
||||
if (!selectedLanguage || !selectedLanguage.code) return false;
|
||||
const i18nLocales = this.$i18n.availableLocales;
|
||||
return i18nLocales.includes(selectedLanguage.code);
|
||||
},
|
||||
/* Apply language locally */
|
||||
setLangLocally() {
|
||||
if (this.language && this.language.code) {
|
||||
this.$i18n.locale = this.language.code;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
.language-switcher {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 1rem;
|
||||
background: var(--config-settings-background);
|
||||
color: var(--config-settings-color);
|
||||
|
||||
h3.title {
|
||||
text-align: center;
|
||||
}
|
||||
p.intro {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
button.save-button {
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
p.sad-times {
|
||||
color: var(--warning);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.language-dropdown {
|
||||
margin: 1rem auto;
|
||||
div.vs__dropdown-toggle {
|
||||
padding: 0.2rem 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -1,13 +1,25 @@
|
||||
<template>
|
||||
<div>
|
||||
<span class="options-label">Layout</span>
|
||||
<span class="options-label">{{ $t('settings.layout-label') }}</span>
|
||||
<div class="display-options">
|
||||
<IconDeafault @click="updateDisplayLayout('auto')" v-tooltip="tooltip('Auto')"
|
||||
:class="`layout-icon ${displayLayout === 'auto' ? 'selected' : ''}`" tabindex="-2" />
|
||||
<IconHorizontal @click="updateDisplayLayout('horizontal')" v-tooltip="tooltip('Horizontal')"
|
||||
:class="`layout-icon ${displayLayout === 'horizontal' ? 'selected' : ''}`" tabindex="-2" />
|
||||
<IconVertical @click="updateDisplayLayout('vertical')" v-tooltip="tooltip('Vertical')"
|
||||
:class="`layout-icon ${displayLayout === 'vertical' ? 'selected' : ''}`" tabindex="-2" />
|
||||
<IconDeafault
|
||||
@click="updateDisplayLayout('auto')"
|
||||
v-tooltip="tooltip($t('settings.layout-auto'))"
|
||||
:class="`layout-icon ${displayLayout === 'auto' ? 'selected' : ''}`"
|
||||
tabindex="-2"
|
||||
/>
|
||||
<IconHorizontal
|
||||
@click="updateDisplayLayout('horizontal')"
|
||||
v-tooltip="tooltip($t('settings.layout-horizontal'))"
|
||||
:class="`layout-icon ${displayLayout === 'horizontal' ? 'selected' : ''}`"
|
||||
tabindex="-2"
|
||||
/>
|
||||
<IconVertical
|
||||
@click="updateDisplayLayout('vertical')"
|
||||
v-tooltip="tooltip($t('settings.layout-vertical'))"
|
||||
:class="`layout-icon ${displayLayout === 'vertical' ? 'selected' : ''}`"
|
||||
tabindex="-2"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
<template>
|
||||
<form>
|
||||
<label for="filter-tiles">Search</label>
|
||||
<label for="filter-tiles">{{ $t('search.search-label') }}</label>
|
||||
<input
|
||||
id="filter-tiles"
|
||||
v-model="input"
|
||||
ref="filter"
|
||||
placeholder="Start typing to filter..."
|
||||
:placeholder="$t('search.search-placeholder')"
|
||||
v-on:input="userIsTypingSomething"
|
||||
@keydown.esc="clearFilterInput" />
|
||||
<i v-if="input.length > 0"
|
||||
class="clear-search"
|
||||
title="Clear search"
|
||||
:title="$t('search.clear-search-tooltip')"
|
||||
@click="clearFilterInput">x</i>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="theme-selector-section" v-click-outside="closeThemeConfigurator">
|
||||
<div>
|
||||
<span class="theme-label">Theme</span>
|
||||
<span class="theme-label">{{ $t('settings.theme-label') }}</span>
|
||||
<v-select
|
||||
:options="themeNames"
|
||||
v-model="selectedTheme"
|
||||
|
||||
25
src/main.js
25
src/main.js
@@ -1,18 +1,23 @@
|
||||
// Import core framework and essential utils
|
||||
import Vue from 'vue';
|
||||
import VueI18n from 'vue-i18n'; // i18n for localization
|
||||
|
||||
/* Import component Vue plugins, used throughout the app */
|
||||
// Import component Vue plugins, used throughout the app
|
||||
import VTooltip from 'v-tooltip'; // A Vue directive for Popper.js, tooltip component
|
||||
import VModal from 'vue-js-modal'; // Modal component
|
||||
import VSelect from 'vue-select'; // Select dropdown component
|
||||
import VTabs from 'vue-material-tabs'; // Tab view component, used on the config page
|
||||
import Toasted from 'vue-toasted'; // Toast component, used to show confirmation notifications
|
||||
|
||||
import { toastedOptions } from '@/utils/defaults';
|
||||
// Import base Dashy components and utils
|
||||
import Dashy from '@/App.vue';
|
||||
import router from '@/router';
|
||||
import registerServiceWorker from '@/registerServiceWorker';
|
||||
import clickOutside from '@/utils/ClickOutside';
|
||||
import { toastedOptions, language as defaultLanguage } from '@/utils/defaults';
|
||||
import { messages } from '@/utils/languages';
|
||||
|
||||
Vue.use(VueI18n);
|
||||
Vue.use(VTooltip);
|
||||
Vue.use(VModal);
|
||||
Vue.use(VTabs);
|
||||
@@ -22,10 +27,18 @@ Vue.directive('clickOutside', clickOutside);
|
||||
|
||||
Vue.config.productionTip = false;
|
||||
|
||||
// Setup i18n translations
|
||||
const i18n = new VueI18n({
|
||||
locale: defaultLanguage,
|
||||
fallbackLocale: defaultLanguage,
|
||||
messages,
|
||||
});
|
||||
|
||||
// Register Service Worker
|
||||
registerServiceWorker();
|
||||
|
||||
new Vue({
|
||||
router,
|
||||
render: (awesome) => awesome(Dashy),
|
||||
}).$mount('#app');
|
||||
// Render function
|
||||
const render = (awesome) => awesome(Dashy);
|
||||
|
||||
// All done, now just initialize main Vue app!
|
||||
new Vue({ router, render, i18n }).$mount('#app');
|
||||
|
||||
@@ -24,8 +24,8 @@ html {
|
||||
/* Overriding styles for the modal component */
|
||||
.vm--modal, .dashy-modal {
|
||||
box-shadow: 0 40px 70px -2px hsl(0deg 0% 0% / 60%), 1px 1px 6px var(--primary) !important;
|
||||
min-width: 300px;
|
||||
min-height: 500px;
|
||||
min-width: 350px;
|
||||
min-height: 200px;
|
||||
}
|
||||
.vm--overlay {
|
||||
background: #00000080;
|
||||
@@ -48,4 +48,32 @@ html {
|
||||
background: var(--success) !important;
|
||||
color: var(--white) !important;
|
||||
font-size: 1.25rem !important;
|
||||
}
|
||||
|
||||
/* v-select, dropdown styles */
|
||||
.v-select {
|
||||
.vs__dropdown-toggle {
|
||||
border-color: var(--primary);
|
||||
background: var(--background);
|
||||
cursor: pointer;
|
||||
span.vs__selected {
|
||||
color: var(--primary);
|
||||
}
|
||||
.vs__actions svg path { fill: var(--primary); }
|
||||
}
|
||||
ul.vs__dropdown-menu {
|
||||
background: var(--background);
|
||||
border-color: var(--primary);
|
||||
li {
|
||||
color: var(--primary);
|
||||
&:hover {
|
||||
color: var(--background);
|
||||
background: var(--primary);
|
||||
}
|
||||
&.vs__dropdown-option--highlight {
|
||||
color: var(--background);
|
||||
background: var(--primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
pageInfo as defaultPageInfo,
|
||||
iconSize as defaultIconSize,
|
||||
layout as defaultLayout,
|
||||
language as defaultLanguage,
|
||||
} from '@/utils/defaults';
|
||||
|
||||
import conf from '../../public/conf.yml';
|
||||
@@ -33,6 +34,8 @@ export default class ConfigAccumulator {
|
||||
|| appConfigFile.layout || defaultLayout;
|
||||
usersAppConfig.iconSize = localStorage[localStorageKeys.ICON_SIZE]
|
||||
|| appConfigFile.iconSize || defaultIconSize;
|
||||
usersAppConfig.language = localStorage[localStorageKeys.LANGUAGE]
|
||||
|| appConfigFile.language || defaultLanguage;
|
||||
return usersAppConfig;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
import ConfigAccumulator from '@/utils/ConfigAccumalator';
|
||||
import { visibleComponents, localStorageKeys, theme as defaultTheme } from '@/utils/defaults';
|
||||
import { languages } from '@/utils/languages';
|
||||
import {
|
||||
visibleComponents,
|
||||
localStorageKeys,
|
||||
theme as defaultTheme,
|
||||
language as defaultLanguage,
|
||||
} from '@/utils/defaults';
|
||||
|
||||
/**
|
||||
* Initiates the Accumulator class and generates a complete config object
|
||||
@@ -74,3 +80,15 @@ export const getCustomKeyShortcuts = () => {
|
||||
});
|
||||
return results.flat();
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the users chosen language. Defaults to English.
|
||||
* @returns {object} Language, including code, name and flag
|
||||
*/
|
||||
export const getUsersLanguage = () => {
|
||||
const langCode = localStorage[localStorageKeys.LANGUAGE]
|
||||
|| config.appConfig.language
|
||||
|| defaultLanguage;
|
||||
const langObj = languages.find(lang => lang.code === langCode);
|
||||
return langObj;
|
||||
};
|
||||
|
||||
@@ -54,6 +54,10 @@
|
||||
"type": "string",
|
||||
"description": "A URL to an image asset to be displayed as background"
|
||||
},
|
||||
"language": {
|
||||
"type": "string",
|
||||
"description": "The ISO code of your desired language, must have translations present, check docs for more info"
|
||||
},
|
||||
"theme": {
|
||||
"type": "string",
|
||||
"default": "callisto",
|
||||
|
||||
@@ -11,6 +11,8 @@ module.exports = {
|
||||
},
|
||||
/* Default appConfig to be used, if user does not specify their own */
|
||||
appConfig: {},
|
||||
/* Default language code */
|
||||
language: 'en',
|
||||
/* Default icon size to be applied on initial load */
|
||||
iconSize: 'medium',
|
||||
/* Default layout to be applied on initial load */
|
||||
@@ -58,6 +60,7 @@ module.exports = {
|
||||
},
|
||||
/* Key names for local storage identifiers */
|
||||
localStorageKeys: {
|
||||
LANGUAGE: 'language',
|
||||
HIDE_WELCOME_BANNER: 'hideWelcomeHelpers',
|
||||
LAYOUT_ORIENTATION: 'layoutOrientation',
|
||||
COLLAPSE_STATE: 'collapseState',
|
||||
@@ -87,6 +90,7 @@ module.exports = {
|
||||
REBUILD_APP: 'REBUILD_APP',
|
||||
THEME_MAKER: 'THEME_MAKER',
|
||||
ABOUT_APP: 'ABOUT_APP',
|
||||
LANG_SWITCHER: 'LANG_SWITCHER',
|
||||
},
|
||||
/* Key names for the top-level objects in conf.yml */
|
||||
topLevelConfKeys: {
|
||||
|
||||
23
src/utils/languages.js
Normal file
23
src/utils/languages.js
Normal file
@@ -0,0 +1,23 @@
|
||||
// Locales - Import translation files here!
|
||||
import en from '@/assets/locales/en.json';
|
||||
|
||||
// Language data - Add your country name, locale code and imported file here
|
||||
export const languages = [
|
||||
{
|
||||
name: 'English',
|
||||
code: 'en',
|
||||
locale: en,
|
||||
flag: '🇬🇧',
|
||||
},
|
||||
// Including:
|
||||
// name - Human readable name for your language (e.g German)
|
||||
// code - ISO language code (e.g. de)
|
||||
// locale - The file that you imported above
|
||||
// flag - A nice emoji flag (optional, e.g. 🇩🇪)
|
||||
];
|
||||
|
||||
const i18nMessages = {};
|
||||
languages.forEach((lang) => {
|
||||
i18nMessages[lang.code] = lang.locale;
|
||||
});
|
||||
export const messages = i18nMessages;
|
||||
@@ -13,7 +13,7 @@
|
||||
:appConfig="appConfig"
|
||||
:pageInfo="pageInfo"
|
||||
:modalOpen="modalOpen"
|
||||
class="filter-container"
|
||||
class="settings-outer"
|
||||
/>
|
||||
<!-- Main content, section for each group of items -->
|
||||
<div v-if="checkTheresData(sections)"
|
||||
@@ -27,14 +27,14 @@
|
||||
:groupId="`section-${index}`"
|
||||
:items="filterTiles(section.items)"
|
||||
:itemSize="itemSizeBound"
|
||||
@itemClicked="finishedSearching()"
|
||||
@change-modal-visibility="updateModalVisibility"
|
||||
:class="(filterTiles(section.items).length === 0 && searchValue) ? 'no-results' : ''"
|
||||
@itemClicked="finishedSearching()"
|
||||
@change-modal-visibility="updateModalVisibility"
|
||||
:class="(filterTiles(section.items).length === 0 && searchValue) ? 'no-results' : ''"
|
||||
/>
|
||||
</div>
|
||||
<!-- Show message when there's no data to show -->
|
||||
<div v-if="checkIfResults()" class="no-data">
|
||||
{{searchValue ? 'No Search Results' : 'No Data Configured'}}
|
||||
{{searchValue ? $t('home.no-results') : $t('home.no-data')}}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -63,6 +63,7 @@ export default {
|
||||
modalOpen: false, // When true, keybindings are disabled
|
||||
}),
|
||||
computed: {
|
||||
/* Updates layout (when button clicked), and saves in local storage */
|
||||
layoutOrientation: {
|
||||
get() { return this.appConfig.layout || Defaults.layout; },
|
||||
set: function setLayout(layout) {
|
||||
@@ -70,6 +71,7 @@ export default {
|
||||
this.layout = layout;
|
||||
},
|
||||
},
|
||||
/* Updates icon size (when button clicked), and saves in local storage */
|
||||
iconSize: {
|
||||
get() { return this.appConfig.iconSize || Defaults.iconSize; },
|
||||
set: function setIconSize(iconSize) {
|
||||
@@ -192,6 +194,7 @@ export default {
|
||||
return itemsFound;
|
||||
}
|
||||
},
|
||||
/* If user has a background image, then generate CSS attributes */
|
||||
getBackgroundImage() {
|
||||
if (this.appConfig && this.appConfig.backgroundImg) {
|
||||
return `background: url('${this.appConfig.backgroundImg}');background-size:cover;`;
|
||||
@@ -267,6 +270,7 @@ export default {
|
||||
.no-results { display: none; }
|
||||
}
|
||||
|
||||
/* Custom styles only applied when there is no sections in config */
|
||||
.no-data {
|
||||
font-size: 2rem;
|
||||
color: var(--background);
|
||||
@@ -277,7 +281,8 @@ export default {
|
||||
border-radius: var(--curve-factor);
|
||||
}
|
||||
|
||||
section.filter-container {
|
||||
/* Settings section, includes search, config and user settings */
|
||||
section.settings-outer {
|
||||
border-bottom: 1px solid var(--outline-color);
|
||||
@include phone {
|
||||
flex-direction: column;
|
||||
|
||||
@@ -1,20 +1,31 @@
|
||||
<template>
|
||||
<div class="login-page">
|
||||
<form class="login-form">
|
||||
<h2 class="login-title">Dashy</h2>
|
||||
<Input v-model="username" label="Username" class="login-field username" type="text" />
|
||||
<Input v-model="password" label="Password" class="login-field password" type="password" />
|
||||
<label>Remember me for</label>
|
||||
<h2 class="login-title">{{ $t('login.title') }}</h2>
|
||||
<Input
|
||||
v-model="username"
|
||||
type="text"
|
||||
:label="$t('login.username-label')"
|
||||
class="login-field username"
|
||||
/>
|
||||
<Input
|
||||
v-model="password"
|
||||
type="password"
|
||||
:label="$t('login.password-label')"
|
||||
class="login-field password"
|
||||
/>
|
||||
<label>{{ $t('login.remember-me-label') }}</label>
|
||||
<v-select
|
||||
v-model="timeout"
|
||||
:options="dropDownMenu"
|
||||
label="label"
|
||||
:selectOnTab="true"
|
||||
:options="dropDownMenu"
|
||||
class="login-time-dropdown"
|
||||
/>
|
||||
<Button class="login-button" :click="submitLogin">Login</Button>
|
||||
<Button class="login-button" :click="submitLogin">
|
||||
{{ $t('login.login-button') }}
|
||||
</Button>
|
||||
<transition name="bounce">
|
||||
<p :class="`login-error-message ${status}`" v-show="message">{{ message }}</p>
|
||||
<p :class="`login-error-message ${status}`" v-show="message">{{ message }}</p>
|
||||
</transition>
|
||||
</form>
|
||||
</div>
|
||||
@@ -38,12 +49,12 @@ export default {
|
||||
password: '',
|
||||
message: '',
|
||||
status: 'waiting', // wating, error, success
|
||||
timeout: { label: 'Never', time: 0 },
|
||||
dropDownMenu: [ // Data for timeout dropdown menu, label + value
|
||||
{ label: 'Never', time: 0 }, // Time is specified in ms
|
||||
{ label: '4 Hours', time: 14400 * 1000 },
|
||||
{ label: '1 Day', time: 86400 * 1000 },
|
||||
{ label: '1 Week', time: 604800 * 1000 },
|
||||
timeout: { label: this.$t('login.remember-me-never'), time: 0 },
|
||||
dropDownMenu: [ // Data for timeout dropdown menu, translated label + value in ms
|
||||
{ label: this.$t('login.remember-me-never'), time: 0 },
|
||||
{ label: this.$t('login.remember-me-hour'), time: 14400 * 1000 },
|
||||
{ label: this.$t('login.remember-me-day'), time: 86400 * 1000 },
|
||||
{ label: this.$t('login.remember-me-week'), time: 604800 * 1000 },
|
||||
],
|
||||
};
|
||||
},
|
||||
@@ -52,8 +63,9 @@ export default {
|
||||
Input,
|
||||
},
|
||||
methods: {
|
||||
/* Checks form is filled in, then initiates the login, and redirects to /home */
|
||||
submitLogin() {
|
||||
const timeout = this.timeout.time || 0;
|
||||
const timeout = this.timeout ? this.timeout.time : 0;
|
||||
const response = checkCredentials(this.username, this.password, this.appConfig.auth || []);
|
||||
this.message = response.msg; // Show error or success message to the user
|
||||
this.status = response.correct ? 'success' : 'error';
|
||||
@@ -64,6 +76,7 @@ export default {
|
||||
}, 250);
|
||||
}
|
||||
},
|
||||
/* Since we don't have the Theme setter at this point, we must manually set users theme */
|
||||
setTheme() {
|
||||
const theme = localStorage[localStorageKeys.THEME] || Defaults.theme;
|
||||
document.getElementsByTagName('html')[0].setAttribute('data-theme', theme);
|
||||
@@ -78,43 +91,48 @@ export default {
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
/* Login page base styles */
|
||||
.login-page {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 800px;
|
||||
min-height: calc(100vh - var(--footer-height));
|
||||
|
||||
.login-form {
|
||||
/* Login form container */
|
||||
form.login-form {
|
||||
background: var(--login-form-background);
|
||||
color: var(--login-form-color);
|
||||
border: 1px solid var(--login-form-color);
|
||||
border-radius: var(--curve-factor);
|
||||
font-size: 1.4rem;
|
||||
padding: 2rem;
|
||||
margin: 2rem auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
/* Login form title */
|
||||
h2.login-title {
|
||||
font-size: 3rem;
|
||||
margin: 0 0 1rem 0;
|
||||
text-align: center;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
/* Set sizings for input fields and login button */
|
||||
.login-field input, Button.login-button {
|
||||
width: 18rem;
|
||||
width: 20rem;
|
||||
margin: 0.5rem auto;
|
||||
font-size: 1.4rem;
|
||||
padding: 0.5rem 1rem;
|
||||
}
|
||||
|
||||
/* Custom colors for username/ password input fields */
|
||||
.login-field input {
|
||||
color: var(--login-form-color);
|
||||
border-color: var(--login-form-color);
|
||||
background: var(--login-form-background);
|
||||
&:focus {
|
||||
|
||||
}
|
||||
}
|
||||
/* Custom colors for Login Button */
|
||||
Button.login-button {
|
||||
background: var(--login-form-color);
|
||||
border-color: var(--login-form-background);
|
||||
@@ -128,6 +146,7 @@ export default {
|
||||
box-shadow: 1px 1px 6px var(--login-form-color);
|
||||
}
|
||||
}
|
||||
/* Apply color to status message, depending on status */
|
||||
p.login-error-message {
|
||||
font-size: 1rem;
|
||||
text-align: center;
|
||||
@@ -138,6 +157,7 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
/* Enter animations for error/ success message */
|
||||
.bounce-enter-active { animation: bounce-in 0.25s; }
|
||||
.bounce-leave-active { animation: bounce-in 0.25s reverse; }
|
||||
@keyframes bounce-in {
|
||||
@@ -146,6 +166,7 @@ export default {
|
||||
100% { transform: scale(1); }
|
||||
}
|
||||
|
||||
/* Custom styles for dropdown component */
|
||||
.v-select.login-time-dropdown {
|
||||
margin: 0.5rem 0;
|
||||
.vs__dropdown-toggle {
|
||||
|
||||
Reference in New Issue
Block a user