Merge pull request #3 from Lissy93/config-editor-feature

UI Config Editor Feature
This commit is contained in:
Alicia Sykes 2021-05-17 21:10:54 +01:00 committed by GitHub
commit 265a8b42ba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 802 additions and 28 deletions

View File

@ -162,12 +162,16 @@ There are a few self-hosted web apps, that serve a similar purpose to Dashy. Inc
### Credits 🏆
The app makes use of the following components, kudos to their respective authors
And the app itself is built with [Vue.js](https://github.com/vuejs/vue) ![vue-logo](https://i.ibb.co/xqKW6h5/vue-logo.png)
And wouldn't have been quite possible, without the following components, kudos to their respective authors
- [`vue-select`](https://github.com/sagalbot/vue-select) - Dropdown component by @sagalbot
- [`vue-js-modal`](https://github.com/euvl/vue-js-modal) - Modal component by @euvl
- [`v-tooltip`](https://github.com/Akryum/v-tooltip) - Tooltip component by @Akryum
And the app itself is built with [Vue.js](https://github.com/vuejs/vue) ![vue-logo](https://i.ibb.co/xqKW6h5/vue-logo.png)
- [`vue-material-tabs`](https://github.com/jairoblatt/vue-material-tabs) - Tab view component by @jairoblatt
- [`VJsoneditor`](https://github.com/yansenlei/VJsoneditor) - Interactive JSON editor component by @yansenlei
- Forked from [JsonEditor](https://github.com/josdejong/jsoneditor) by @josdejong
- [`vue-toasted`](https://github.com/shakee93/vue-toasted) - Toast notification component by @shakee93
### License 📜

View File

@ -10,13 +10,17 @@
"dependencies": {
"connect": "^3.7.0",
"register-service-worker": "^1.6.2",
"remedial": "^1.0.8",
"serve-static": "^1.14.1",
"v-jsoneditor": "^1.4.2",
"v-tooltip": "^2.1.3",
"vue": "^2.6.10",
"vue-cli-plugin-yaml": "^1.0.2",
"vue-js-modal": "^2.0.0-rc.6",
"vue-material-tabs": "^0.0.7",
"vue-router": "^3.0.3",
"vue-select": "^3.11.2"
"vue-select": "^3.11.2",
"vue-toasted": "^1.1.28"
},
"devDependencies": {
"@vue/cli-plugin-babel": "^4.5.12",
@ -61,4 +65,4 @@
"> 1%",
"last 2 versions"
]
}
}

View File

@ -9,7 +9,7 @@
import Header from '@/components/PageStrcture/Header.vue';
import Footer from '@/components/PageStrcture/Footer.vue';
import Defaults from '@/utils/defaults';
import Defaults, { localStorageKeys } from '@/utils/defaults';
import conf from '../public/conf.yml';
export default {
@ -27,10 +27,17 @@ export default {
/* Returns either page info from the config, or default values */
getPageInfo(pageInfo) {
const defaults = Defaults.pageInfo;
let localPageInfo;
try {
localPageInfo = JSON.parse(localStorage[localStorageKeys.PAGE_INFO]);
} catch (e) {
localPageInfo = {};
}
if (pageInfo) {
return {
title: pageInfo.title || defaults.title,
description: pageInfo.description || defaults.description,
title: localPageInfo.title || pageInfo.title || defaults.title,
description: localPageInfo.description || pageInfo.description || defaults.description,
navLinks: pageInfo.navLinks || defaults.navLinks,
};
}

View File

@ -0,0 +1 @@
<svg aria-hidden="true" focusable="false" data-prefix="far" data-icon="trash-alt" class="svg-inline--fa fa-trash-alt fa-w-14" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path fill="currentColor" d="M268 416h24a12 12 0 0 0 12-12V188a12 12 0 0 0-12-12h-24a12 12 0 0 0-12 12v216a12 12 0 0 0 12 12zM432 80h-82.41l-34-56.7A48 48 0 0 0 274.41 0H173.59a48 48 0 0 0-41.16 23.3L98.41 80H16A16 16 0 0 0 0 96v16a16 16 0 0 0 16 16h16v336a48 48 0 0 0 48 48h288a48 48 0 0 0 48-48V128h16a16 16 0 0 0 16-16V96a16 16 0 0 0-16-16zM171.84 50.91A6 6 0 0 1 177 48h94a6 6 0 0 1 5.15 2.91L293.61 80H154.39zM368 464H80V128h288zm-212-48h24a12 12 0 0 0 12-12V188a12 12 0 0 0-12-12h-24a12 12 0 0 0-12 12v216a12 12 0 0 0 12 12z"></path></svg>

After

Width:  |  Height:  |  Size: 739 B

View File

@ -0,0 +1 @@
<svg aria-hidden="true" focusable="false" data-prefix="far" data-icon="file-download" class="svg-inline--fa fa-file-download fa-w-12" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path fill="currentColor" d="M216 236.07c0-6.63-5.37-12-12-12h-24c-6.63 0-12 5.37-12 12v84.01h-48.88c-10.71 0-16.05 12.97-8.45 20.52l72.31 71.77c4.99 4.95 13.04 4.95 18.03 0l72.31-71.77c7.6-7.54 2.26-20.52-8.45-20.52H216v-84.01zM369.83 97.98L285.94 14.1c-9-9-21.2-14.1-33.89-14.1H47.99C21.5.1 0 21.6 0 48.09v415.92C0 490.5 21.5 512 47.99 512h287.94c26.5 0 48.07-21.5 48.07-47.99V131.97c0-12.69-5.17-24.99-14.17-33.99zM255.95 51.99l76.09 76.08h-76.09V51.99zM336 464.01H47.99V48.09h159.97v103.98c0 13.3 10.7 23.99 24 23.99H336v287.95z"></path></svg>

After

Width:  |  Height:  |  Size: 749 B

View File

@ -0,0 +1 @@
<svg aria-hidden="true" focusable="false" data-prefix="far" data-icon="edit" class="svg-inline--fa fa-edit fa-w-18" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path fill="currentColor" d="M402.3 344.9l32-32c5-5 13.7-1.5 13.7 5.7V464c0 26.5-21.5 48-48 48H48c-26.5 0-48-21.5-48-48V112c0-26.5 21.5-48 48-48h273.5c7.1 0 10.7 8.6 5.7 13.7l-32 32c-1.5 1.5-3.5 2.3-5.7 2.3H48v352h352V350.5c0-2.1.8-4.1 2.3-5.6zm156.6-201.8L296.3 405.7l-90.4 10c-26.2 2.9-48.5-19.2-45.6-45.6l10-90.4L432.9 17.1c22.9-22.9 59.9-22.9 82.7 0l43.2 43.2c22.9 22.9 22.9 60 .1 82.8zM460.1 174L402 115.9 216.2 301.8l-7.3 65.3 65.3-7.3L460.1 174zm64.8-79.7l-43.2-43.2c-4.1-4.1-10.8-4.1-14.8 0L436 82l58.1 58.1 30.9-30.9c4-4.2 4-10.8-.1-14.9z"></path></svg>

After

Width:  |  Height:  |  Size: 746 B

View File

@ -0,0 +1 @@
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="wrench" class="svg-inline--fa fa-wrench fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M507.73 109.1c-2.24-9.03-13.54-12.09-20.12-5.51l-74.36 74.36-67.88-11.31-11.31-67.88 74.36-74.36c6.62-6.62 3.43-17.9-5.66-20.16-47.38-11.74-99.55.91-136.58 37.93-39.64 39.64-50.55 97.1-34.05 147.2L18.74 402.76c-24.99 24.99-24.99 65.51 0 90.5 24.99 24.99 65.51 24.99 90.5 0l213.21-213.21c50.12 16.71 107.47 5.68 147.37-34.22 37.07-37.07 49.7-89.32 37.91-136.73zM64 472c-13.25 0-24-10.75-24-24 0-13.26 10.75-24 24-24s24 10.74 24 24c0 13.25-10.75 24-24 24z"></path></svg>

After

Width:  |  Height:  |  Size: 685 B

View File

@ -0,0 +1 @@
<svg aria-hidden="true" focusable="false" data-prefix="fal" data-icon="digital-tachograph" class="svg-inline--fa fa-digital-tachograph fa-w-20" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><path fill="currentColor" d="M608 96H32c-17.67 0-32 14.33-32 32v256c0 17.67 14.33 32 32 32h576c17.67 0 32-14.33 32-32V128c0-17.67-14.33-32-32-32zm0 288H32V128h576v256zM80 272h208c8.84 0 16-7.16 16-16v-80c0-8.84-7.16-16-16-16H80c-8.84 0-16 7.16-16 16v80c0 8.84 7.16 16 16 16zm8-88h192v64H88v-64zM72 360h224c4.42 0 8-3.58 8-8v-8c0-4.42-3.58-8-8-8H72c-4.42 0-8 3.58-8 8v8c0 4.42 3.58 8 8 8zm272 0h224c4.42 0 8-3.58 8-8v-8c0-4.42-3.58-8-8-8H344c-4.42 0-8 3.58-8 8v8c0 4.42 3.58 8 8 8zM80 288c-4.42 0-8 3.58-8 8v16c0 4.42 3.58 8 8 8h16c4.42 0 8-3.58 8-8v-16c0-4.42-3.58-8-8-8H80zm64 0c-4.42 0-8 3.58-8 8v16c0 4.42 3.58 8 8 8h16c4.42 0 8-3.58 8-8v-16c0-4.42-3.58-8-8-8h-16zm64 0c-4.42 0-8 3.58-8 8v16c0 4.42 3.58 8 8 8h16c4.42 0 8-3.58 8-8v-16c0-4.42-3.58-8-8-8h-16zm64 0c-4.42 0-8 3.58-8 8v16c0 4.42 3.58 8 8 8h16c4.42 0 8-3.58 8-8v-16c0-4.42-3.58-8-8-8h-16z"></path></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1,204 @@
<template>
<Tabs :navAuto="true" name="Add Item" ref="tabView">
<TabItem name="Config">
<div class="main-options-container">
<h2>Configuration Options</h2>
<a href="/conf.yml" download class="hyperlink-wrapper">
<button class="config-button center">
<DownloadIcon class="button-icon"/>
Download Config
</button>
</a>
<button class="config-button center" @click="goToEdit()">
<EditIcon class="button-icon"/>
Edit Sections
</button>
<button class="config-button center" @click="goToMetaEdit()">
<MetaDataIcon class="button-icon"/>
Edit Meta Data
</button>
<button class="config-button center" @click="resetLocalSettings()">
<DeleteIcon class="button-icon"/>
Reset Local Settings
</button>
</div>
</TabItem>
<TabItem name="Edit Sections">
<JsonEditor :sections="sections" />
</TabItem>
<TabItem name="View Raw YAML">
<pre>{{this.jsonParser(this.config)}}</pre>
<a class="download-button" href="/conf.yml" download>Download Config</a>
</TabItem>
<TabItem name="Edit Site Meta">
<EditSiteMeta :config="config" />
</TabItem>
</Tabs>
</template>
<script>
import JsonToYaml from '@/utils/JsonToYaml';
import EditSiteMeta from '@/components/Configuration/EditSiteMeta';
import JsonEditor from '@/components/Configuration/JsonEditor';
import DownloadIcon from '@/assets/interface-icons/config-download-file.svg';
import DeleteIcon from '@/assets/interface-icons/config-delete-local.svg';
import EditIcon from '@/assets/interface-icons/config-edit-json.svg';
import MetaDataIcon from '@/assets/interface-icons/config-meta-data.svg';
export default {
name: 'ConfigContainer',
data() {
return {
jsonParser: JsonToYaml,
};
},
props: {
config: Object,
},
computed: {
sections: function getSections() {
return this.config.sections;
},
},
components: {
EditSiteMeta,
JsonEditor,
DownloadIcon,
DeleteIcon,
EditIcon,
MetaDataIcon,
},
methods: {
/* Seletcs the edit tab of the tab view */
goToEdit() {
const itemToSelect = this.$refs.tabView.navItems[1];
this.$refs.tabView.activeTabItem({ tabItem: itemToSelect, byUser: true });
},
goToMetaEdit() {
const itemToSelect = this.$refs.tabView.navItems[3];
this.$refs.tabView.activeTabItem({ tabItem: itemToSelect, byUser: true });
},
/* 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 isTheUserSure = confirm(msg); // eslint-disable-line no-alert, no-restricted-globals
if (isTheUserSure) {
localStorage.clear();
this.$toasted.show('Data cleared succesfully');
setTimeout(() => {
location.reload(); // eslint-disable-line no-restricted-globals
}, 1900);
}
},
},
};
</script>
<style scoped lang="scss">
pre {
color: var(--config-code-color);
background: var(--config-code-background);
}
a.config-button, button.config-button {
display: flex;
align-items: center;
padding: 0.5rem 1rem;
margin: 0.25rem auto;
font-size: 1.2rem;
background: var(--config-settings-background);
color: var(--config-settings-color);
border: 1px solid var(--config-settings-color);
border-radius: var(--curve-factor);
text-decoration: none;
cursor: pointer;
margin: 0.5rem auto;
width: 18rem;
svg.button-icon {
path {
fill: var(--config-settings-color);
}
width: 1rem;
height: 1rem;
padding: 0.2rem;
margin-right: 0.5rem;
}
&:hover {
background: var(--config-settings-color);
color: var(--config-settings-background);
svg path {
fill: var(--config-settings-background);
}
}
}
a.download-button {
position: absolute;
top: 2px;
right: 2px;
padding: 0.25rem 0.5rem;
font-size: 1rem;
color: var(--config-settings-background);
border-radius: var(--curve-factor);
cursor: pointer;
&:hover {
background: var(--config-settings-color);
}
}
.tab-item {
overflow-y: auto;
}
a.hyperlink-wrapper {
margin: 0 auto;
text-decoration: none;
}
.main-options-container {
display: flex;
flex-direction: column;
padding-top: 2rem;
background: var(--background-darker);
height: calc(100% - 2rem);
h2 {
margin: 1rem auto;
color: var(--config-settings-color);
}
}
</style>
<style lang="scss">
.tab__pagination {
background: var(--config-settings-background);
color: var(--config-settings-color);
.tab__nav__items .tab__nav__item {
span {
color: var(--config-settings-color);
}
&:hover {
background: var(--config-settings-color) !important;
span {
color: var(--config-settings-background);
}
}
&.active {
span {
font-weight: bold;
color: var(--config-settings-color) !important;
}
}
}
.tab__nav__items .tab__nav__item.active {
border-bottom: 2px solid var(--config-settings-color);
}
hr.tab__slider {
background: var(--config-settings-color);
}
}
</style>

View File

@ -0,0 +1,111 @@
<template>
<div class="site-meta-container">
<h2>Edit Site Meta</h2>
<div class="form">
<div class="row">
<span>Title</span>
<input v-model="formElements.title" />
</div>
<div class="row">
<span>Description</span>
<input v-model="formElements.description" />
</div>
<div class="row">
<span>Footer Text</span>
<input v-model="formElements.footerText" />
</div>
</div>
<button class="save-button" @click="save()">Save Changes</button>
</div>
</template>
<script>
import { localStorageKeys } from '@/utils/defaults';
export default {
name: 'EditSiteMeta',
props: {
config: Object,
},
methods: {
save() {
const pageInfo = { ...this.config.pageInfo };
pageInfo.title = this.formElements.title;
pageInfo.description = this.formElements.description;
pageInfo.footerText = this.formElements.footerText;
localStorage.setItem(localStorageKeys.PAGE_INFO, JSON.stringify(pageInfo));
this.$toasted.show('Changes seved succesfully');
setTimeout(() => { location.reload(); }, 1500); // eslint-disable-line no-restricted-globals
},
},
data() {
return {
formElements: {
title: this.config.pageInfo.title,
description: this.config.pageInfo.description,
footerText: this.config.pageInfo.footerText,
},
};
},
};
</script>
<style scoped lang="scss">
.site-meta-container {
display: flex;
flex-direction: column;
padding-top: 1rem;
background: var(--background-darker);
height: calc(100% - 1rem);
h2 {
margin: 1rem auto;
color: var(--config-settings-color);
}
}
div.form {
display: flex;
flex-direction: column;
div.row {
margin: 0.25rem auto;
display: flex;
justify-content: space-between;
align-items: baseline;
min-width: 24rem;
span {
font-size: 1.2rem;
}
input {
color: var(--config-settings-color);
background: none;
border: 1px solid var(--config-settings-color);
border-radius: var(--curve-factor);
padding: 0.25rem 0.5rem;
margin: 0.5rem;
min-width: 8rem;
font-size: 1.2rem;
&:focus {
box-shadow: 1px 1px 6px #00ccb4;
outline: none;
}
}
}
}
button.save-button {
padding: 0.5rem 1rem;
margin: 0.5rem auto;
font-size: 1.2rem;
width: 24rem;
background: var(--config-settings-background);
color: var(--config-settings-color);
border: 1px solid var(--config-settings-color);
border-radius: var(--curve-factor);
cursor: pointer;
&:hover {
background: var(--config-settings-color);
color: var(--config-settings-background);
}
}
</style>

View File

@ -0,0 +1,85 @@
<template>
<div class="json-editor-outer">
<v-jsoneditor
v-model="jsonData"
:options="options"
height="650px"
/>
<button class="save-button" @click="save()">Save Changes</button>
<p class="note">
It is recommend to backup your existing confiruration before making any changes.
</p>
</div>
</template>
<script>
import VJsoneditor from 'v-jsoneditor';
import { localStorageKeys } from '@/utils/defaults';
export default {
name: 'JsonEditor',
props: {
sections: Array,
},
components: {
VJsoneditor,
},
data() {
return {
jsonData: this.sections,
options: {
mode: 'tree',
modes: ['tree', 'code', 'preview'],
name: 'sections',
},
};
},
methods: {
save() {
localStorage.setItem(localStorageKeys.CONF_SECTIONS, JSON.stringify(this.jsonData));
this.$toasted.show('Changes seved succesfully');
},
},
};
</script>
<style lang="scss">
.json-editor-outer {
text-align: center;
}
p.note {
font-size: 0.8rem;
color: var(--medium-grey);
margin: 0.2rem;
}
button.save-button {
padding: 0.5rem 1rem;
margin: 0.25rem auto;
font-size: 1.2rem;
background: var(--config-settings-color);
color: var(--config-settings-background);
border: 1px solid var(--config-settings-background);
border-radius: var(--curve-factor);
cursor: pointer;
&:hover {
background: var(--config-settings-background);
color: var(--config-settings-color);
}
}
.jsoneditor-menu {
background: var(--config-settings-background);
color: var(--config-settings-color);
}
.jsoneditor-contextmenu .jsoneditor-menu li button.jsoneditor-selected,
.jsoneditor-contextmenu .jsoneditor-menu li button.jsoneditor-selected:focus,
.jsoneditor-contextmenu .jsoneditor-menu li button.jsoneditor-selected:hover {
background: var(--config-settings-color);
color: var(--config-settings-background);
}
.jsoneditor-poweredBy {
display: none;
}
</style>

View File

@ -100,7 +100,6 @@ export default {
.item {
flex-grow: 1;
position: relative;
color: var(--item-text-color);
vertical-align: middle;
margin: 0.5rem;
@ -116,6 +115,7 @@ export default {
&:hover {
box-shadow: var(--item-hover-shadow);
background: var(--item-background-hover);
position: relative;
}
&:focus {
outline: 2px solid var(--primary);

View File

@ -99,7 +99,7 @@ export default {
<style lang="scss">
.tile-icon {
width: 60px;
filter: var(--item-icon-transform);
// filter: var(--item-icon-transform);
&.broken { display: none; }
}
i.fas, i.fab, i.far, i.fal, i.fad {

View File

@ -0,0 +1,91 @@
<template>
<div class="config-options">
<!-- Button and label -->
<span>Config</span>
<div class="config-buttons">
<IconSpanner v-tooltip="tooltip('Update configuration locally')" @click="showEditor()" />
</div>
<!-- Modal containing all the configuration options -->
<modal :name="modalName" :resizable="true" width="60%" height="80%"
@closed="$emit('modalChanged', false)">
<ConfigContainer :config="combineConfig()" />
</modal>
</div>
</template>
<script>
import IconSpanner from '@/assets/interface-icons/config-editor.svg';
import ConfigContainer from '@/components/Configuration/ConfigContainer';
import { topLevelConfKeys } from '@/utils/defaults';
export default {
name: 'ConfigLauncher',
data() {
return {
modalName: 'CONF-EDITOR',
};
},
components: {
IconSpanner,
ConfigContainer,
},
props: {
sections: Array,
pageInfo: Object,
appConfig: Object,
},
methods: {
showEditor: function show() {
this.$modal.show(this.modalName);
this.$emit('modalChanged', true);
},
combineConfig() {
const conf = {};
conf[topLevelConfKeys.APP_CONFIG] = this.appConfig;
conf[topLevelConfKeys.PAGE_INFO] = this.pageInfo;
conf[topLevelConfKeys.SECTIONS] = this.sections;
return conf;
},
updateConfig() {
// this.$emit('iconSizeUpdated', iconSize);
},
tooltip(content) {
return { content, trigger: 'hover focus', delay: 250 };
},
},
};
</script>
<style scoped lang="scss">
.config-options {
display: flex;
flex-direction: column;
color: var(--settings-text-color);
svg {
path {
fill: var(--settings-text-color);
}
width: 1rem;
height: 1rem;
margin: 0.2rem;
padding: 0.2rem;
text-align: center;
background: var(--background);
border: 1px solid currentColor;
border-radius: var(--curve-factor);
cursor: pointer;
&:hover, &.selected {
background: var(--settings-text-color);
path { fill: var(--background); }
}
}
}
</style>
<style lang="scss">
.vm--modal {
box-shadow: 0 40px 70px -2px hsl(0deg 0% 0% / 60%), 1px 1px 6px var(--primary);
}
</style>

View File

@ -21,13 +21,13 @@ export default {
data() {
return {
shouldHide: true, // False = show/ true = hide. Intuitive, eh?
timeDelay: 3000, // Short delay in ms before popup appears
};
},
methods: {
/**
* If the session storage item exists, true will be returned
* Otherwise, if not then false is returned.
* Note the !! just converts 'false' to false, as strings resolve to true
* Returns true if the key exists in session storage, otherwise false
* And the !! just converts 'false' to false, as strings resolve to true
*/
shouldHideWelcomeMessage() {
return !!localStorage[localStorageKeys.HIDE_WELCOME_BANNER];
@ -39,7 +39,11 @@ export default {
hideWelcomeHelper() {
this.shouldHide = true;
localStorage.setItem(localStorageKeys.HIDE_WELCOME_BANNER, true);
window.removeEventListener('keyup');
window.removeEventListener('keyup', this.keyPressEvent);
},
/* Passed to window function, to add/ remove event listener */
keyPressEvent(event) {
if (event.keyCode === 27) this.hideWelcomeHelper();
},
},
/**
@ -50,11 +54,8 @@ export default {
mounted() {
const shouldHide = this.shouldHideWelcomeMessage();
if (!shouldHide) {
window.setTimeout(() => { this.shouldHide = shouldHide; }, 3000);
window.addEventListener('keyup', (ev) => {
// User pressed the escape key. Trigger permanent dismissal of dialog
if (ev.keyCode === 27) this.hideWelcomeHelper();
});
window.setTimeout(() => { this.shouldHide = shouldHide; }, this.timeDelay);
window.addEventListener('keyup', this.keyPressEvent);
} else { // Meh, component not needed.
// No point wasting valuable bytes of your 32GB Ram, lets kill it
this.$destroy();

View File

@ -21,6 +21,9 @@ import ArrowKeyNavigation from '@/utils/ArrowKeyNavigation';
export default {
name: 'FilterTile',
props: {
active: Boolean,
},
data() {
return {
input: '', // Users current search term
@ -31,6 +34,8 @@ export default {
window.addEventListener('keydown', (event) => {
const currentElem = document.activeElement.id;
const { key, keyCode } = event;
/* If a modal is open, then do nothing */
if (!this.active) return;
if (/^[a-zA-Z]$/.test(key) && currentElem !== 'filter-tiles') {
/* Letter key pressed - start searching */
this.$refs.filter.focus();

View File

@ -1,11 +1,17 @@
<template>
<section>
<SearchBar @user-is-searchin="userIsTypingSomething" ref="SearchBar" v-if="searchVisible" />
<SearchBar ref="SearchBar"
@user-is-searchin="userIsTypingSomething"
v-if="searchVisible"
:active="!modalOpen"
/>
<div class="options-container" v-if="settingsVisible">
<ThemeSelector :themes="availableThemes"
:confTheme="getInitialTheme()" :userThemes="getUserThemes()" />
<LayoutSelector :displayLayout="displayLayout" @layoutUpdated="updateDisplayLayout"/>
<ItemSizeSelector :iconSize="iconSize" @iconSizeUpdated="updateIconSize" />
<ConfigLauncher :sections="sections" :pageInfo="pageInfo" :appConfig="appConfig"
@modalChanged="modalChanged" />
</div>
<KeyboardShortcutInfo />
</section>
@ -14,6 +20,7 @@
<script>
import Defaults from '@/utils/defaults';
import SearchBar from '@/components/Settings/SearchBar';
import ConfigLauncher from '@/components/Settings/ConfigLauncher';
import ThemeSelector from '@/components/Settings/ThemeSelector';
import LayoutSelector from '@/components/Settings/LayoutSelector';
import ItemSizeSelector from '@/components/Settings/ItemSizeSelector';
@ -26,9 +33,13 @@ export default {
iconSize: String,
availableThemes: Object,
appConfig: Object,
pageInfo: Object,
sections: Array,
modalOpen: Boolean,
},
components: {
SearchBar,
ConfigLauncher,
ThemeSelector,
LayoutSelector,
ItemSizeSelector,
@ -47,6 +58,9 @@ export default {
updateIconSize(iconSize) {
this.$emit('change-icon-size', iconSize);
},
modalChanged(changedTo) {
this.$emit('change-modal-visibility', changedTo);
},
getInitialTheme() {
return this.appConfig.theme || '';
},
@ -59,7 +73,7 @@ export default {
},
data() {
return {
searchVisible: Defaults.visibleComponents.searchBar,
searchVisible: Defaults.visibleComponents.searchBar && !this.modalOpen,
settingsVisible: Defaults.visibleComponents.settings,
};
},
@ -89,6 +103,7 @@ export default {
div {
margin-left: 0.5rem;
opacity: var(--dimming-factor);
opacity: 1;
&:hover { opacity: 1; }
}
}

View File

@ -3,13 +3,18 @@ import Vue from 'vue';
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 App from './App.vue';
import router from './router';
import './registerServiceWorker';
Vue.use(VTooltip);
Vue.use(VModal);
Vue.use(VTabs);
Vue.use(Toasted, toastedOptions);
Vue.component('v-select', VSelect);
Vue.config.productionTip = false;

View File

@ -2,9 +2,18 @@ import Vue from 'vue';
import Router from 'vue-router';
import Home from './views/Home.vue';
import conf from '../public/conf.yml'; // Main site configuration
import { pageInfo as defaultPageInfo, localStorageKeys } from './utils/defaults';
Vue.use(Router);
const { sections, pageInfo, appConfig } = conf;
let localPageInfo;
try {
localPageInfo = JSON.parse(localStorage[localStorageKeys.PAGE_INFO]);
} catch (e) {
localPageInfo = undefined;
}
const router = new Router({
routes: [
{
@ -12,8 +21,9 @@ const router = new Router({
name: 'home',
component: Home,
props: {
sections: conf.sections || [],
appConfig: conf.appConfig || {},
sections: sections || [],
pageInfo: localPageInfo || pageInfo || defaultPageInfo,
appConfig: appConfig || {},
},
meta: {
title: 'Home Page',

View File

@ -31,7 +31,9 @@
--item-group-shadow: var(--item-shadow);
--settings-container-shadow: none;
/* Specific components, using variables allows them to be overridden individually */
/* Color variables for specific components
* all variables are optional, since they inherit initial values from above*
* Using specific variables makes overriding for custom themes really easy */
--heading-text-color: var(--primary);
--nav-link-text-color: var(--primary);
--nav-link-background-color: #607d8b33;
@ -51,4 +53,10 @@
--footer-text-color-link: var(--primary);
--welcome-popup-background: var(--background-darker);
--welcome-popup-text-color: var(--primary);
--config-code-background: #fff;
--config-code-color: var(--background);
--config-settings-color: var(--primary);
--config-settings-background: var(--background-darker);
--toast-background: var(--primary);
--toast-color: var(--background);
}

View File

@ -176,6 +176,8 @@ html[data-theme='material-dark'] {
--item-icon-transform-hover: drop-shadow(1px 3px 2px var(--transparent-30)) saturate(2);
--welcome-popup-background: #131a1f;
--welcome-popup-text-color: var(--primary);
--config-settings-background: #131a1f;
--config-settings-color: #41e2ed;
}
html[data-theme='colorful'] {

View File

@ -11,7 +11,7 @@ html {
}
/* Default app font face */
body, div, p, a, span, label, input {
body, div, p, a, span, label, input, button {
font-family: 'Inconsolata', sans-serif;
}
@ -20,4 +20,13 @@ h1, h2, h3, h4, h5 {
font-family: 'Inconsolata', sans-serif;
}
/* Overiding styles for the global toast component */
.toast-message {
background: var(--toast-background) !important;
color: var(--toast-color) !important;
border: 1px solid var(--toast-color) !important;
border-radius: var(--curve-factor) !important;
font-size: 1.25rem !important;
}

87
src/utils/JsonToYaml.js Normal file
View File

@ -0,0 +1,87 @@
import { typeOf } from 'remedial';
const trimWhitespace = (input) => input.split('\n').map(x => x.trimRight()).join('\n');
const throwError = (msg) => {
throw new Error(`Error in Json to YAML conversion: ${msg}`);
};
/* A function that converts valid JSON into valid YAML */
const stringify = (data) => {
let indentLevel = '';
const handlers = {
undefined() {
return 'null';
},
null() {
return 'null';
},
number(x) {
return x;
},
boolean(x) {
return x ? 'true' : 'false';
},
string(x) {
return JSON.stringify(x);
},
array(x) {
let output = '';
if (x.length === 0) {
output += '[]';
return output;
}
indentLevel = indentLevel.replace(/$/, ' ');
x.forEach((y) => {
const handler = handlers[typeOf(y)];
if (!handler) throwError(typeOf(y));
output += `\n${indentLevel}- ${handler(y, true)}`;
});
indentLevel = indentLevel.replace(/ {2}/, '');
return output;
},
object(x, inArray, rootNode) {
let output = '';
if (Object.keys(x).length === 0) {
output += '{}';
return output;
}
if (!rootNode) {
indentLevel = indentLevel.replace(/$/, ' ');
}
Object.keys(x).forEach((k, i) => {
const val = x[k];
const handler = handlers[typeOf(val)];
if (typeof val === 'undefined') {
return;
}
if (!handler) throwError(typeOf(val));
if (!(inArray && i === 0)) {
output += `\n${indentLevel}`;
}
output += `${k}: ${handler(val)}`;
});
indentLevel = indentLevel.replace(/ {2}/, '');
return output;
},
function() {
return '[object Function]';
},
};
return trimWhitespace(`${handlers[typeOf(data)](data, true, true)}\n`);
};
export default stringify;

View File

@ -44,5 +44,20 @@ module.exports = {
COLLAPSE_STATE: 'collapseState',
ICON_SIZE: 'iconSize',
THEME: 'theme',
CONF_SECTIONS: 'confSections',
PAGE_INFO: 'pageInfo',
APP_CONFIG: 'appConfig',
},
topLevelConfKeys: {
PAGE_INFO: 'pageInfo',
APP_CONFIG: 'appConfig',
SECTIONS: 'sections',
},
toastedOptions: {
position: 'bottom-center',
duration: 2000,
keepOnHover: true,
className: 'toast-message',
iconPack: 'fontawesome',
},
};

View File

@ -5,17 +5,21 @@
@user-is-searchin="searching"
@change-display-layout="setLayoutOrientation"
@change-icon-size="setItemSize"
@change-modal-visibility="updateModalVisibility"
:displayLayout="layout"
:iconSize="itemSizeBound"
:availableThemes="getExternalCSSLinks()"
:sections="getSections(sections)"
:appConfig="appConfig"
:pageInfo="pageInfo"
:modalOpen="modalOpen"
class="filter-container"
/>
<!-- Main content, section for each group of items -->
<div v-if="checkTheresData(sections)"
:class="`item-group-container orientation-${layout} item-size-${itemSizeBound}`">
<ItemGroup
v-for="(section, index) in sections"
v-for="(section, index) in getSections(sections)"
:key="index"
:title="section.name"
:displayData="getDisplayData(section)"
@ -44,6 +48,7 @@ export default {
props: {
sections: Array, // Main site content
appConfig: Object, // Main site configuation (optional)
pageInfo: Object, // Page metadata (optional)
},
components: {
SettingsContainer,
@ -53,6 +58,7 @@ export default {
searchValue: '',
layout: '',
itemSizeBound: '',
modalOpen: false, // When true, keybindings are disabled
}),
computed: {
layoutOrientation: {
@ -73,7 +79,19 @@ export default {
methods: {
/* Returns true if there is one or more sections in the config */
checkTheresData(sections) {
return sections && sections.length >= 1;
const localSections = localStorage[localStorageKeys.CONF_SECTIONS];
return (sections && sections.length >= 1) || (localSections && localSections.length >= 1);
},
/* Returns sections from local storage if available, otherwise uses the conf.yml */
getSections(sections) {
// If the user has stored sections in local storage, return those
const localSections = localStorage[localStorageKeys.CONF_SECTIONS];
if (localSections) {
const json = JSON.parse(localSections);
if (json.length >= 1) return json;
}
// Otherwise, return the usuall data from conf.yml
return sections;
},
/* Updates local data with search value, triggered from filter comp */
searching(searchValue) {
@ -116,6 +134,10 @@ export default {
setItemSize(itemSize) {
this.iconSize = itemSize;
},
/* Update data when modal is open (so that key bindings can be disabled) */
updateModalVisibility(modalState) {
this.modalOpen = modalState;
},
/* Returns an array of links to external CSS from the Config */
getExternalCSSLinks() {
const availibleThemes = {};

View File

@ -957,6 +957,11 @@
resolved "https://registry.yarnpkg.com/@soda/get-current-script/-/get-current-script-1.0.2.tgz#a53515db25d8038374381b73af20bb4f2e508d87"
integrity sha512-T7VNNlYVM1SgQ+VsMYhnDkcGmWhQdL0bDyGm5TlQ3GBXnJscEClUUOKduWTmm2zCnvNLC1hc3JpuXjs/nFOc5w==
"@sphinxxxx/color-conversion@^2.2.2":
version "2.2.2"
resolved "https://registry.yarnpkg.com/@sphinxxxx/color-conversion/-/color-conversion-2.2.2.tgz#03ecc29279e3c0c832f6185a5bfa3497858ac8ca"
integrity sha512-XExJS3cLqgrmNBIP3bBw6+1oQ1ksGjFh0+oClDKFYpCCqx/hlqwWO5KO/S63fzUo67SxI9dMrF0y5T/Ey7h8Zw==
"@types/anymatch@*":
version "1.3.1"
resolved "https://registry.yarnpkg.com/@types/anymatch/-/anymatch-1.3.1.tgz#336badc1beecb9dacc38bea2cf32adf627a8421a"
@ -1580,6 +1585,11 @@ accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.7:
mime-types "~2.1.24"
negotiator "0.6.2"
ace-builds@^1.4.12:
version "1.4.12"
resolved "https://registry.yarnpkg.com/ace-builds/-/ace-builds-1.4.12.tgz#888efa386e36f4345f40b5233fcc4fe4c588fae7"
integrity sha512-G+chJctFPiiLGvs3+/Mly3apXTcfgE45dT5yp12BcWZ1kUs+gm0qd3/fv4gsz6fVag4mM0moHVpjHDIgph6Psg==
acorn-jsx@^5.2.0, acorn-jsx@^5.3.1:
version "5.3.1"
resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.1.tgz#fc8661e11b7ac1539c47dbfea2e72b3af34d267b"
@ -1623,7 +1633,7 @@ ajv-keywords@^3.1.0, ajv-keywords@^3.4.1, ajv-keywords@^3.5.2:
resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d"
integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==
ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.3, ajv@^6.12.4:
ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.3, ajv@^6.12.4, ajv@^6.12.6:
version "6.12.6"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==
@ -5231,6 +5241,11 @@ isstream@~0.1.2:
resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=
javascript-natural-sort@^0.7.1:
version "0.7.1"
resolved "https://registry.yarnpkg.com/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz#f9e2303d4507f6d74355a73664d1440fb5a0ef59"
integrity sha1-+eIwPUUH9tdDVac2ZNFED7Wg71k=
javascript-stringify@^2.0.1:
version "2.1.0"
resolved "https://registry.yarnpkg.com/javascript-stringify/-/javascript-stringify-2.1.0.tgz#27c76539be14d8bd128219a2d731b09337904e79"
@ -5244,6 +5259,11 @@ jest-worker@^25.4.0:
merge-stream "^2.0.0"
supports-color "^7.0.0"
jmespath@^0.15.0:
version "0.15.0"
resolved "https://registry.yarnpkg.com/jmespath/-/jmespath-0.15.0.tgz#a3f222a9aae9f966f5d27c796510e28091764217"
integrity sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc=
js-message@1.0.7:
version "1.0.7"
resolved "https://registry.yarnpkg.com/js-message/-/js-message-1.0.7.tgz#fbddd053c7a47021871bb8b2c95397cc17c20e47"
@ -5314,6 +5334,11 @@ json-schema@0.2.3:
resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13"
integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=
json-source-map@^0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/json-source-map/-/json-source-map-0.6.1.tgz#e0b1f6f4ce13a9ad57e2ae165a24d06e62c79a0f"
integrity sha512-1QoztHPsMQqhDq0hlXY5ZqcEdUzxQEIxgFkKl4WUp2pgShObl+9ovi4kRh2TfvAfxAoHOJ9vIMEqk3k4iex7tg==
json-stable-stringify-without-jsonify@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651"
@ -5355,6 +5380,21 @@ json5@^2.1.2:
dependencies:
minimist "^1.2.5"
jsoneditor@^9.1.1:
version "9.4.1"
resolved "https://registry.yarnpkg.com/jsoneditor/-/jsoneditor-9.4.1.tgz#cebd6cefc74ae3c8354b6d84c4e86d4e78fea0b9"
integrity sha512-Uf/ru12Y4eRo4xoYaFjpbB13fIXVUQG6GZO21i3yhlfKL389HLV8e8El77grup01IRdtcoB01P8rZTY4d6uXjA==
dependencies:
ace-builds "^1.4.12"
ajv "^6.12.6"
javascript-natural-sort "^0.7.1"
jmespath "^0.15.0"
json-source-map "^0.6.1"
jsonrepair "^2.2.0"
mobius1-selectr "^2.4.13"
picomodal "^3.0.0"
vanilla-picker "^2.11.2"
jsonfile@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb"
@ -5367,6 +5407,11 @@ jsonify@~0.0.0:
resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73"
integrity sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=
jsonrepair@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/jsonrepair/-/jsonrepair-2.2.0.tgz#3cdaa6fbc9ced360f401cef9c97f1a8caf3470e5"
integrity sha512-NyqcDyer9N4OEDwkZZjmSwd17T9tOfvqTSs9GDpbmPp928Rc1Tot7sOTNenIpMaavD3LkAFkDcNcjmxv3Vqvbg==
jsprim@^1.2.2:
version "1.4.1"
resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2"
@ -5874,6 +5919,11 @@ mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@^0.5.5, mkdirp@~0.5.1:
dependencies:
minimist "^1.2.5"
mobius1-selectr@^2.4.13:
version "2.4.13"
resolved "https://registry.yarnpkg.com/mobius1-selectr/-/mobius1-selectr-2.4.13.tgz#0019dfd9f984840d6e40f70683ab3ec78ce3b5df"
integrity sha512-Mk9qDrvU44UUL0EBhbAA1phfQZ7aMZPjwtL7wkpiBzGh8dETGqfsh50mWoX9EkjDlkONlErWXArHCKfoxVg0Bw==
move-concurrently@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92"
@ -6553,6 +6603,11 @@ picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3:
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.3.tgz#465547f359ccc206d3c48e46a1bcb89bf7ee619d"
integrity sha512-KpELjfwcCDUb9PeigTs2mBJzXUPzAuP2oPcA989He8Rte0+YUAjw1JVedDhuTKPkHjSYzMN3npC9luThGYEKdg==
picomodal@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/picomodal/-/picomodal-3.0.0.tgz#facd30f4fbf34a809c1e04ea525f004f399c0b82"
integrity sha1-+s0w9PvzSoCcHgTqUl8ATzmcC4I=
pify@^2.0.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
@ -7314,6 +7369,11 @@ relateurl@0.2.x:
resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9"
integrity sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=
remedial@^1.0.8:
version "1.0.8"
resolved "https://registry.yarnpkg.com/remedial/-/remedial-1.0.8.tgz#a5e4fd52a0e4956adbaf62da63a5a46a78c578a0"
integrity sha512-/62tYiOe6DzS5BqVsNpH/nkGlX45C/Sp6V+NtiN6JQNS1Viay7cWkazmRkrQrdFj2eshDe96SIQNIoMxqhzBOg==
remove-trailing-separator@^1.0.1:
version "1.1.0"
resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef"
@ -8657,6 +8717,13 @@ uuid@^3.3.2, uuid@^3.4.0:
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"
integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==
v-jsoneditor@^1.4.2:
version "1.4.2"
resolved "https://registry.yarnpkg.com/v-jsoneditor/-/v-jsoneditor-1.4.2.tgz#2a877fb3ed137732f8e6269b99b32758bd304ad0"
integrity sha512-cLY/41uD7+1fJGpbs7HPwBv20UHlNpi8A6zhI9t5lVGLgQ/7lK5pLsZeLAz+4ybOXXK091HDgdB/wEnCTYMFFw==
dependencies:
jsoneditor "^9.1.1"
v-tooltip@^2.1.3:
version "2.1.3"
resolved "https://registry.yarnpkg.com/v-tooltip/-/v-tooltip-2.1.3.tgz#281c2015d1e73787f13c8956aa295b8c3a73f261"
@ -8680,6 +8747,13 @@ validate-npm-package-license@^3.0.1:
spdx-correct "^3.0.0"
spdx-expression-parse "^3.0.0"
vanilla-picker@^2.11.2:
version "2.11.2"
resolved "https://registry.yarnpkg.com/vanilla-picker/-/vanilla-picker-2.11.2.tgz#eaa24efa68c27e7ee9e0776df55d6913b855f133"
integrity sha512-2cP7LlUnxHxwOf06ReUVtd2RFJMnJGaxN2s0p8wzBH3In5b00Le7fFZ9VrIoBE0svZkSq/BC/Pwq/k/9n+AA2g==
dependencies:
"@sphinxxxx/color-conversion" "^2.2.2"
vary@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
@ -8756,6 +8830,11 @@ vue-loader@^15.9.2:
vue-hot-reload-api "^2.3.0"
vue-style-loader "^4.1.0"
vue-material-tabs@^0.0.7:
version "0.0.7"
resolved "https://registry.yarnpkg.com/vue-material-tabs/-/vue-material-tabs-0.0.7.tgz#5f3fa04ad35384af68582f7c89ad4cecac89207b"
integrity sha512-02X5paTksYKrGvSRpMdkctRO9qhvJFD5VEGxd0xjOX4sYz6mZSAez0Z/+aYf7Z5ziY+eJ9dMQmxaLn9DVKQRJw==
vue-resize@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/vue-resize/-/vue-resize-1.0.1.tgz#c120bed4e09938771d622614f57dbcf58a5147ee"
@ -8802,6 +8881,11 @@ vue-template-es2015-compiler@^1.9.0:
resolved "https://registry.yarnpkg.com/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.9.1.tgz#1ee3bc9a16ecbf5118be334bb15f9c46f82f5825"
integrity sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==
vue-toasted@^1.1.28:
version "1.1.28"
resolved "https://registry.yarnpkg.com/vue-toasted/-/vue-toasted-1.1.28.tgz#dbabb83acc89f7a9e8765815e491d79f0dc65c26"
integrity sha512-UUzr5LX51UbbiROSGZ49GOgSzFxaMHK6L00JV8fir/CYNJCpIIvNZ5YmS4Qc8Y2+Z/4VVYRpeQL2UO0G800Raw==
vue@^2.6.10:
version "2.6.12"
resolved "https://registry.yarnpkg.com/vue/-/vue-2.6.12.tgz#f5ebd4fa6bd2869403e29a896aed4904456c9123"