mirror of https://github.com/Lissy93/dashy.git
Merge pull request #3 from Lissy93/config-editor-feature
UI Config Editor Feature
This commit is contained in:
commit
265a8b42ba
10
README.md
10
README.md
|
@ -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 📜
|
||||
|
||||
|
|
|
@ -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"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
13
src/App.vue
13
src/App.vue
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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 |
|
@ -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 |
|
@ -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 |
|
@ -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 |
|
@ -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 |
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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);
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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>
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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'] {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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;
|
|
@ -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',
|
||||
},
|
||||
};
|
||||
|
|
|
@ -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 = {};
|
||||
|
|
86
yarn.lock
86
yarn.lock
|
@ -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"
|
||||
|
|
Loading…
Reference in New Issue