Merge pull request #8 from Lissy93/bug-fixes-and-improvments

Implements custom CSS editor, customizable item grids, other new features and bug fixes
This commit is contained in:
Alicia Sykes 2021-05-31 17:07:54 +01:00 committed by GitHub
commit 484a62566b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 375 additions and 55 deletions

View File

@ -36,6 +36,8 @@
<img width="800" src="https://i.ibb.co/L8YbNNc/dashy-demo2.gif" alt="Demo"> <img width="800" src="https://i.ibb.co/L8YbNNc/dashy-demo2.gif" alt="Demo">
</p> </p>
![More themes and screens](https://i.ibb.co/M6nyvqW/dashy-options-screen.png)
--- ---
## Running the App 🏃‍♂️ ## Running the App 🏃‍♂️
@ -96,6 +98,7 @@ All fields are optional, unless otherwise stated.
- `theme`- String: The default theme for first load (you can change this later from the UI) - `theme`- String: The default theme for first load (you can change this later from the UI)
- `cssThemes` - String[]: An array of custom theme names which can be used in the theme switcher dropdown - _See **theming** below_ - `cssThemes` - String[]: An array of custom theme names which can be used in the theme switcher dropdown - _See **theming** below_
- `externalStyleSheet` - String or String[] - Either a URL to an external stylesheet or an array or URLs, which can be applied as themes within the UI - `externalStyleSheet` - String or String[] - Either a URL to an external stylesheet or an array or URLs, which can be applied as themes within the UI
- `customCss` - String: Raw CSS that will be applied to the page. Please minify it first.
**`sections`** - Section[]: (required) An array of sections - _See **`section`** below_ **`sections`** - Section[]: (required) An array of sections - _See **`section`** below_
@ -104,11 +107,16 @@ All fields are optional, unless otherwise stated.
- `items` - Item[]: (required) An array of items - _See **`item`** below_ - `items` - Item[]: (required) An array of items - _See **`item`** below_
- `displayData`: An object with the following fields (all optional) - `displayData`: An object with the following fields (all optional)
- `collapsed` - Boolean: If true, the section will be collapsed initially (defaults to `false`) - `collapsed` - Boolean: If true, the section will be collapsed initially (defaults to `false`)
- `rows` - Int: Number of rows the section should span vertically, e.g. 2 (defaults to `1`)
- `cols` - Int: Number of columns the section should span horizontally, e.g. 2 (defaults to `1`)
- `color` - String: A custom accent color for the section, as a hex code or HTML color (e.g. `#fff`) - `color` - String: A custom accent color for the section, as a hex code or HTML color (e.g. `#fff`)
- `customStyles` - String: Custom CSS properties that should be applied to that section, e.g. `border: 2px dashed #ff0000;` - `customStyles` - String: Custom CSS properties that should be applied to that section, e.g. `border: 2px dashed #ff0000;`
- `itemSize` - String: Specify the size for items within this group, either `small`, `medium` or `large` - `itemSize` - String: Specify the size for items within this group, either `small`, `medium` or `large`
- `rows` - Int: Number of rows the section should span vertically, e.g. 2 (defaults to `1`)
- `cols` - Int: Number of columns the section should span horizontally, e.g. 2 (defaults to `1`)
- `layout` - Enum: `auto` or `grid`. If `grid` is selected, then the number of items per row can be set
- `itemCountX` - Int: Number of items horizontally (for `layout: grid`)
- `itemCountY` - Int: Number of items vertically (for `layout: grid`)
Note about `rows` and `cols`: These are defined as a proportion of the screen (rather than by number of child items), and is built using [`grid-layout`](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Grid_Layout). For more info, see [this example](https://i.ibb.co/HXRWVRK/how-rows-and-cols-work-in-dashy.png). In order to set the number of items that will display horizontally or vertically within a section, first set `display: grid`, and then specify values for `itemCountX`, and optionally `itemCountY`.
**`item`** **`item`**
- `title` - String: The text to display on the item - `title` - String: The text to display on the item
@ -174,8 +182,9 @@ This wouldn't have been quite so possible without the following components, kudo
- [`vue-material-tabs`](https://github.com/jairoblatt/vue-material-tabs) - Tab view component by @jairoblatt `MIT` - [`vue-material-tabs`](https://github.com/jairoblatt/vue-material-tabs) - Tab view component by @jairoblatt `MIT`
- [`VJsoneditor`](https://github.com/yansenlei/VJsoneditor) - Interactive JSON editor component by @yansenlei `MIT` - [`VJsoneditor`](https://github.com/yansenlei/VJsoneditor) - Interactive JSON editor component by @yansenlei `MIT`
- Forked from [`JsonEditor`](https://github.com/josdejong/jsoneditor) by @josdejong `Apache-2.0 License` - Forked from [`JsonEditor`](https://github.com/josdejong/jsoneditor) by @josdejong `Apache-2.0 License`
- And using [`ajv`](https://github.com/ajv-validator/ajv) `MIT` JSON schema Validator [`ace`](https://github.com/ajaxorg/ace) `BSD` code editor
- [`vue-toasted`](https://github.com/shakee93/vue-toasted) - Toast notification component by @shakee93 `MIT` - [`vue-toasted`](https://github.com/shakee93/vue-toasted) - Toast notification component by @shakee93 `MIT`
- [`vue-prism-editor`](https://github.com/koca/vue-prism-editor) - Lightweight code editor by @koca `MIT`
- Forked from [`prism.js`](https://github.com/PrismJS/prism) `MIT`
Utils: Utils:
- [`crypto-js`](https://github.com/brix/crypto-js) - Encryption implementations by @evanvosberg and community `MIT` - [`crypto-js`](https://github.com/brix/crypto-js) - Encryption implementations by @evanvosberg and community `MIT`

View File

@ -11,6 +11,8 @@
"axios": "^0.21.1", "axios": "^0.21.1",
"connect": "^3.7.0", "connect": "^3.7.0",
"crypto-js": "^4.0.0", "crypto-js": "^4.0.0",
"highlight.js": "^11.0.0",
"prismjs": "^1.23.0",
"register-service-worker": "^1.6.2", "register-service-worker": "^1.6.2",
"remedial": "^1.0.8", "remedial": "^1.0.8",
"serve-static": "^1.14.1", "serve-static": "^1.14.1",
@ -20,6 +22,7 @@
"vue-cli-plugin-yaml": "^1.0.2", "vue-cli-plugin-yaml": "^1.0.2",
"vue-js-modal": "^2.0.0-rc.6", "vue-js-modal": "^2.0.0-rc.6",
"vue-material-tabs": "^0.0.7", "vue-material-tabs": "^0.0.7",
"vue-prism-editor": "^1.2.2",
"vue-router": "^3.0.3", "vue-router": "^3.0.3",
"vue-select": "^3.11.2", "vue-select": "^3.11.2",
"vue-toasted": "^1.1.28" "vue-toasted": "^1.1.28"

View File

@ -509,6 +509,6 @@
], ],
"start_url": "./index.html", "start_url": "./index.html",
"display": "standalone", "display": "standalone",
"background_color": "#000000", "background_color": "#0b1021",
"theme_color": "#4DBA87" "theme_color": "#4DBA87"
} }

View File

@ -20,11 +20,21 @@ export default {
}, },
data: () => ({ data: () => ({
// pageInfo: this.getPageInfo(conf.pageInfo), // pageInfo: this.getPageInfo(conf.pageInfo),
appConfig: conf.appConfig || Defaults.appConfig,
showFooter: Defaults.visibleComponents.footer, showFooter: Defaults.visibleComponents.footer,
}), }),
computed: { computed: {
pageInfo: function pi() { return this.getPageInfo(conf.pageInfo); }, pageInfo() {
return this.getPageInfo(conf.pageInfo);
},
appConfig() {
if (localStorage[localStorageKeys.APP_CONFIG]) {
return JSON.parse(localStorage[localStorageKeys.APP_CONFIG]);
} else if (conf.appConfig) {
return conf.appConfig;
} else {
return Defaults.appConfig;
}
},
}, },
methods: { methods: {
/* Returns either page info from the config, or default values */ /* Returns either page info from the config, or default values */
@ -53,6 +63,17 @@ export default {
} }
return ''; return '';
}, },
injectCustomStyles(usersCss) {
const style = document.createElement('style');
style.textContent = usersCss;
document.head.append(style);
},
},
mounted() {
if (this.appConfig.customCss) {
const cleanedCss = this.appConfig.customCss.replace(/<\/?[^>]+(>|$)/g, '');
this.injectCustomStyles(cleanedCss);
}
}, },
}; };
</script> </script>

View File

@ -0,0 +1 @@
<svg aria-hidden="true" focusable="false" data-prefix="fab" data-icon="css3-alt" class="svg-inline--fa fa-css3-alt fa-w-12" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path fill="currentColor" d="M0 32l34.9 395.8L192 480l157.1-52.2L384 32H0zm313.1 80l-4.8 47.3L193 208.6l-.3.1h111.5l-12.8 146.6-98.2 28.7-98.8-29.2-6.4-73.9h48.9l3.2 38.3 52.6 13.3 54.7-15.4 3.7-61.6-166.3-.5v-.1l-.2.1-3.6-46.3L193.1 162l6.5-2.7H76.7L70.9 112h242.2z"></path></svg>

After

Width:  |  Height:  |  Size: 473 B

View File

@ -3,7 +3,7 @@
<div class="section intro"> <div class="section intro">
<h2>Cloud Backup & Restore</h2> <h2>Cloud Backup & Restore</h2>
<p class="intro"> <p class="intro">
Cloud backup and restore is an optional feature, that enabled you to upload your 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. config to the internet, and then restore it on any other device or instance of Dashy.
<br><br> <br><br>
All data is fully end-to-end encrypted with AES, using your password as the key. All data is fully end-to-end encrypted with AES, using your password as the key.

View File

@ -1,6 +1,6 @@
<template> <template>
<Tabs :navAuto="true" name="Add Item" ref="tabView"> <Tabs :navAuto="true" name="Add Item" ref="tabView">
<TabItem name="Config"> <TabItem name="Config" class="main-tab">
<div class="main-options-container"> <div class="main-options-container">
<h2>Configuration Options</h2> <h2>Configuration Options</h2>
<a href="/conf.yml" download class="hyperlink-wrapper"> <a href="/conf.yml" download class="hyperlink-wrapper">
@ -17,10 +17,25 @@
<MetaDataIcon class="button-icon"/> <MetaDataIcon class="button-icon"/>
Edit Meta Data Edit Meta Data
</button> </button>
<button class="config-button center" @click="goToCustomCss()">
<CustomCssIcon class="button-icon"/>
Edit Custom CSS
</button>
<button class="config-button center" @click="openCloudSync()">
<CloudIcon class="button-icon"/>
{{backupId ? 'Edit Cloud Sync' : 'Enable Cloud Sync'}}
</button>
<button class="config-button center" @click="resetLocalSettings()"> <button class="config-button center" @click="resetLocalSettings()">
<DeleteIcon class="button-icon"/> <DeleteIcon class="button-icon"/>
Reset Local Settings Reset Local Settings
</button> </button>
<div class="config-note">
<p class="sub-title">Note:</p>
<span>
All changes made here are stored locally. To apply globally, either export your config
into your conf.yml file, or use the cloud backup/ restore feature.
</span>
</div>
</div> </div>
</TabItem> </TabItem>
<TabItem name="Backup Config" class="code-container"> <TabItem name="Backup Config" class="code-container">
@ -38,24 +53,36 @@
<TabItem name="Edit Site Meta"> <TabItem name="Edit Site Meta">
<EditSiteMeta :config="config" /> <EditSiteMeta :config="config" />
</TabItem> </TabItem>
<TabItem name="Custom Styles">
<CustomCssEditor :config="config" initialCss="hello" />
</TabItem>
</Tabs> </Tabs>
</template> </template>
<script> <script>
import hljs from 'highlight.js/lib/core';
import yaml from 'highlight.js/lib/languages/yaml';
import 'highlight.js/styles/mono-blue.css';
import JsonToYaml from '@/utils/JsonToYaml'; import JsonToYaml from '@/utils/JsonToYaml';
import { localStorageKeys, modalNames } from '@/utils/defaults';
import EditSiteMeta from '@/components/Configuration/EditSiteMeta'; import EditSiteMeta from '@/components/Configuration/EditSiteMeta';
import JsonEditor from '@/components/Configuration/JsonEditor'; import JsonEditor from '@/components/Configuration/JsonEditor';
import CustomCssEditor from '@/components/Configuration/CustomCss';
import DownloadIcon from '@/assets/interface-icons/config-download-file.svg'; import DownloadIcon from '@/assets/interface-icons/config-download-file.svg';
import DeleteIcon from '@/assets/interface-icons/config-delete-local.svg'; import DeleteIcon from '@/assets/interface-icons/config-delete-local.svg';
import EditIcon from '@/assets/interface-icons/config-edit-json.svg'; import EditIcon from '@/assets/interface-icons/config-edit-json.svg';
import MetaDataIcon from '@/assets/interface-icons/config-meta-data.svg'; import MetaDataIcon from '@/assets/interface-icons/config-meta-data.svg';
import CustomCssIcon from '@/assets/interface-icons/config-custom-css.svg';
import CloudIcon from '@/assets/interface-icons/cloud-backup-restore.svg';
export default { export default {
name: 'ConfigContainer', name: 'ConfigContainer',
data() { data() {
return { return {
jsonParser: JsonToYaml, jsonParser: JsonToYaml,
backupId: localStorage[localStorageKeys.BACKUP_ID] || '',
}; };
}, },
props: { props: {
@ -69,10 +96,13 @@ export default {
components: { components: {
EditSiteMeta, EditSiteMeta,
JsonEditor, JsonEditor,
CustomCssEditor,
DownloadIcon, DownloadIcon,
DeleteIcon, DeleteIcon,
EditIcon, EditIcon,
CloudIcon,
MetaDataIcon, MetaDataIcon,
CustomCssIcon,
}, },
methods: { methods: {
/* Seletcs the edit tab of the tab view */ /* Seletcs the edit tab of the tab view */
@ -84,6 +114,13 @@ export default {
const itemToSelect = this.$refs.tabView.navItems[3]; const itemToSelect = this.$refs.tabView.navItems[3];
this.$refs.tabView.activeTabItem({ tabItem: itemToSelect, byUser: true }); this.$refs.tabView.activeTabItem({ tabItem: itemToSelect, byUser: true });
}, },
goToCustomCss() {
const itemToSelect = this.$refs.tabView.navItems[4];
this.$refs.tabView.activeTabItem({ tabItem: itemToSelect, byUser: true });
},
openCloudSync() {
this.$modal.show(modalNames.CLOUD_BACKUP);
},
copyConfigToClipboard() { copyConfigToClipboard() {
navigator.clipboard.writeText(this.jsonParser(this.config)); navigator.clipboard.writeText(this.jsonParser(this.config));
// event.target.textContent = 'Copied to clipboard'; // event.target.textContent = 'Copied to clipboard';
@ -104,13 +141,20 @@ export default {
} }
}, },
}, },
mounted() {
hljs.registerLanguage('yaml', yaml);
const highlighted = hljs.highlight(this.jsonParser(this.config), { language: 'yaml' }).value;
document.getElementById('conf-yaml').innerHTML = highlighted;
},
}; };
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@import '@/styles/style-helpers.scss';
pre { pre {
color: var(--config-code-color); color: var(--config-code-color);
font-weight: bold !important;
padding: 0.5rem 1rem; padding: 0.5rem 1rem;
} }
@ -148,6 +192,12 @@ a.config-button, button.config-button {
div.code-container { div.code-container {
background: var(--config-code-background); background: var(--config-code-background);
#conf-yaml {
font-family: 'Inconsolata', sans-serif;
.hljs-attr {
font-weight: bold !important;
}
}
.yaml-action-buttons { .yaml-action-buttons {
position: absolute; position: absolute;
top: 0.5rem; top: 0.5rem;
@ -186,7 +236,11 @@ div.code-container {
.tab-item { .tab-item {
overflow-y: auto; overflow-y: auto;
@extend .scroll-bar;
background: var(--config-settings-background); background: var(--config-settings-background);
&.main-tab {
min-height: 500px;
}
} }
a.hyperlink-wrapper { a.hyperlink-wrapper {
@ -205,6 +259,25 @@ a.hyperlink-wrapper {
color: var(--config-settings-color); color: var(--config-settings-color);
} }
} }
.config-note {
width: 80%;
position: absolute;
bottom: 1rem;
left: 10%;
margin: 0.5rem auto;
padding: 0.5rem 0.75rem;
border: 1px dashed var(--config-settings-color);
border-radius: var(--curve-factor);
text-align: left;
opacity: var(--dimming-factor);
background: var(--config-settings-background);
p.sub-title {
font-weight: bold;
margin: 0;
display: inline;
}
}
</style> </style>
<style lang="scss"> <style lang="scss">
@ -235,4 +308,8 @@ a.hyperlink-wrapper {
background: var(--config-settings-color); background: var(--config-settings-color);
} }
} }
#conf-yaml .hljs-attr {
color: #9c03f5;
}
</style> </style>

View File

@ -0,0 +1,88 @@
<template>
<div class="json-editor-outer">
<prism-editor class="my-editor" v-model="customCss" :highlight="highlighter" line-numbers />
<button class="save-button" @click="save()">Save Changes</button>
<p>Note, you will need to refresh the page for your changes to take effect</p>
</div>
</template>
<script>
import { PrismEditor } from 'vue-prism-editor';
import { highlight, languages } from 'prismjs/components/prism-core';
import 'prismjs/components/prism-css';
import 'prismjs/themes/prism-funky.css';
import 'vue-prism-editor/dist/prismeditor.min.css';
import { localStorageKeys } from '@/utils/defaults';
export default {
name: 'JsonEditor',
props: {
config: Object,
},
components: {
PrismEditor,
},
data() {
return {
customCss: this.config.appConfig.customCss || '\n\n\n\n\n',
};
},
methods: {
validate(css) {
return css.match(/((?:^\s*)([\w#.@*,:\-.:>,*\s]+)\s*{(?:[\s]*)((?:[A-Za-z\- \s]+[:]\s*['"0-9\w .,/()\-!%]+;?)*)*\s*}(?:\s*))/gmi);
},
save() {
let msg = '';
if (this.validate(this.customCss)) {
const appConfig = { ...this.config.appConfig };
appConfig.customCss = this.customCss;
localStorage.setItem(localStorageKeys.APP_CONFIG, JSON.stringify(appConfig));
msg = 'Changes saved succesfully';
this.inject(this.customCss);
} else {
msg = 'Error - Invalid CSS';
}
this.$toasted.show(msg);
},
inject(userStyles) {
const cleanedCss = userStyles.replace(/<\/?[^>]+(>|$)/g, '');
const style = document.createElement('style');
style.textContent = cleanedCss;
document.head.append(style);
},
highlighter(code) {
return highlight(code, languages.css);
},
},
};
</script>
<style lang="scss">
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);
border-color: var(--config-settings-color);
}
}
.prism-editor-wrapper {
min-height: 200px;
border: 1px solid var(--transparent-70);
border-radius: var(--curve-factor);
width: 90%;
margin: 0.5rem auto;
background: var(--transparent-50);
}
</style>

View File

@ -77,7 +77,7 @@ export default {
title: this.config.pageInfo.title, title: this.config.pageInfo.title,
description: this.config.pageInfo.description, description: this.config.pageInfo.description,
footerText: this.config.pageInfo.footerText, footerText: this.config.pageInfo.footerText,
navLinks: this.config.pageInfo.navLinks, navLinks: this.config.pageInfo.navLinks || [],
}, },
}; };
}, },

View File

@ -38,7 +38,7 @@ export default {
methods: { methods: {
save() { save() {
localStorage.setItem(localStorageKeys.CONF_SECTIONS, JSON.stringify(this.jsonData)); localStorage.setItem(localStorageKeys.CONF_SECTIONS, JSON.stringify(this.jsonData));
this.$toasted.show('Changes seved succesfully'); this.$toasted.show('Changes saved succesfully');
}, },
}, },
}; };

View File

@ -1,5 +1,5 @@
<template> <template>
<modal :name="name" :resizable="true" width="80%" height="80%" @closed="$emit('closed')"> <modal :name="name" :resizable="true" width="80%" height="80%" @closed="modalClosed()">
<div slot="top-right" @click="hide()">Close</div> <div slot="top-right" @click="hide()">Close</div>
<a @click="hide()" class="close-button" title="Close">x</a> <a @click="hide()" class="close-button" title="Close">x</a>
<iframe v-if="url" :src="url" @keydown.esc="close" class="frame"/> <iframe v-if="url" :src="url" @keydown.esc="close" class="frame"/>
@ -20,10 +20,14 @@ export default {
show: function show(url) { show: function show(url) {
this.url = url; this.url = url;
this.$modal.show(this.name); this.$modal.show(this.name);
this.$emit('modalChanged', true);
}, },
hide: function hide() { hide: function hide() {
this.$modal.hide(this.name); this.$modal.hide(this.name);
}, },
modalClosed() {
this.$emit('modalChanged', false);
},
}, },
}; };
</script> </script>

View File

@ -11,7 +11,10 @@
<div v-if="!items || items.length < 1" class="no-items"> <div v-if="!items || items.length < 1" class="no-items">
No Items to Show Yet No Items to Show Yet
</div> </div>
<div v-else class="there-are-items"> <div v-else
:class="`there-are-items ${isGridLayout? 'item-group-grid': ''}`"
:style="gridStyle"
>
<Item <Item
v-for="(item, index) in items" v-for="(item, index) in items"
:id="`${index}_${makeId(item.title)}`" :id="`${index}_${makeId(item.title)}`"
@ -33,6 +36,7 @@
:ref="`iframeModal-${groupId}`" :ref="`iframeModal-${groupId}`"
:name="`iframeModal-${groupId}`" :name="`iframeModal-${groupId}`"
@closed="$emit('itemClicked')" @closed="$emit('itemClicked')"
@modalChanged="modalChanged"
/> />
</Collapsable> </Collapsable>
</template> </template>
@ -50,6 +54,7 @@ export default {
displayData: Object, displayData: Object,
items: Array, items: Array,
itemSize: String, itemSize: String,
modalOpen: Boolean,
}, },
components: { components: {
Collapsable, Collapsable,
@ -57,9 +62,21 @@ export default {
IframeModal, IframeModal,
}, },
computed: { computed: {
newItemSize: function size() { newItemSize() {
return this.displayData.itemSize || this.itemSize; return this.displayData.itemSize || this.itemSize;
}, },
isGridLayout() {
return this.displayData.layout === 'grid'
|| !!(this.displayData.itemCountX || this.displayData.itemCountY);
},
gridStyle() {
let styles = '';
styles += this.displayData.itemCountX
? `grid-template-columns: repeat(${this.displayData.itemCountX}, 1fr);` : '';
styles += this.displayData.itemCountY
? `grid-template-rows: repeat(${this.displayData.itemCountY}, 1fr);` : '';
return styles;
},
}, },
methods: { methods: {
/* Returns a unique lowercase string, based on name, for section ID */ /* Returns a unique lowercase string, based on name, for section ID */
@ -70,11 +87,16 @@ export default {
triggerModal(url) { triggerModal(url) {
this.$refs[`iframeModal-${this.groupId}`].show(url); this.$refs[`iframeModal-${this.groupId}`].show(url);
}, },
modalChanged(changedTo) {
this.$emit('change-modal-visibility', changedTo);
},
}, },
}; };
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@import '@/styles/media-queries.scss';
@import '@/styles/style-helpers.scss';
.no-items { .no-items {
width: 100px; width: 100px;
@ -92,6 +114,17 @@ export default {
height: 100%; height: 100%;
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
&.item-group-grid {
display: grid;
overflow: auto;
@extend .scroll-bar;
@include phone { grid-template-columns: repeat(1, 1fr); }
@include tablet { grid-template-columns: repeat(2, 1fr); }
@include laptop { grid-template-columns: repeat(2, 1fr); }
@include monitor { grid-template-columns: repeat(3, 1fr); }
@include big-screen { grid-template-columns: repeat(4, 1fr); }
@include big-screen-up { grid-template-columns: repeat(5, 1fr); }
}
} }
</style> </style>

View File

@ -27,16 +27,13 @@ import IconSpanner from '@/assets/interface-icons/config-editor.svg';
import IconCloud from '@/assets/interface-icons/cloud-backup-restore.svg'; import IconCloud from '@/assets/interface-icons/cloud-backup-restore.svg';
import ConfigContainer from '@/components/Configuration/ConfigContainer'; import ConfigContainer from '@/components/Configuration/ConfigContainer';
import CloudBackupRestore from '@/components/Configuration/CloudBackupRestore'; import CloudBackupRestore from '@/components/Configuration/CloudBackupRestore';
import { topLevelConfKeys, localStorageKeys } from '@/utils/defaults'; import { topLevelConfKeys, localStorageKeys, modalNames } from '@/utils/defaults';
export default { export default {
name: 'ConfigLauncher', name: 'ConfigLauncher',
data() { data() {
return { return {
modalNames: { modalNames,
CONF_EDITOR: 'CONF_EDITOR',
CLOUD_BACKUP: 'CLOUD_BACKUP',
},
}; };
}, },
components: { components: {
@ -52,11 +49,11 @@ export default {
}, },
methods: { methods: {
showEditor: function show() { showEditor: function show() {
this.$modal.show(this.modalNames.CONF_EDITOR); this.$modal.show(modalNames.CONF_EDITOR);
this.$emit('modalChanged', true); this.$emit('modalChanged', true);
}, },
showCloudModal: function show() { showCloudModal: function show() {
this.$modal.show(this.modalNames.CLOUD_BACKUP); this.$modal.show(modalNames.CLOUD_BACKUP);
this.$emit('modalChanged', true); this.$emit('modalChanged', true);
}, },
combineConfig() { combineConfig() {

View File

@ -5,7 +5,6 @@ import VModal from 'vue-js-modal'; // Modal component
import VSelect from 'vue-select'; // Select dropdown component import VSelect from 'vue-select'; // Select dropdown component
import VTabs from 'vue-material-tabs'; // Tab view component, used on the config page 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 Toasted from 'vue-toasted'; // Toast component, used to show confirmation notifications
import { toastedOptions } from './utils/defaults'; import { toastedOptions } from './utils/defaults';
import App from './App.vue'; import App from './App.vue';
import router from './router'; import router from './router';

View File

@ -14,6 +14,13 @@ try {
localPageInfo = undefined; localPageInfo = undefined;
} }
let localAppConfig;
try {
localAppConfig = JSON.parse(localStorage[localStorageKeys.APP_CONFIG]);
} catch (e) {
localAppConfig = undefined;
}
const router = new Router({ const router = new Router({
routes: [ routes: [
{ {
@ -23,7 +30,7 @@ const router = new Router({
props: { props: {
sections: sections || [], sections: sections || [],
pageInfo: localPageInfo || pageInfo || defaultPageInfo, pageInfo: localPageInfo || pageInfo || defaultPageInfo,
appConfig: appConfig || {}, appConfig: localAppConfig || appConfig || {},
}, },
meta: { meta: {
title: 'Home Page', title: 'Home Page',

View File

@ -35,9 +35,10 @@
--outline-color: none; --outline-color: none;
--curve-factor: 5px; // Border radius for most components --curve-factor: 5px; // Border radius for most components
--curve-factor-navbar: 16px; // Border radius for header --curve-factor-navbar: 16px; // Border radius for header
--dimming-factor: 0.8; // Opacity for semi-transparent components --dimming-factor: 0.7; // Opacity for semi-transparent components
/* Settings for specific components */ /* Settings for specific components */
--scroll-bar-width: 8px;
--item-group-padding: 5px; // Determines width of item-group outline --item-group-padding: 5px; // Determines width of item-group outline
--item-shadow: 1px 1px 2px #130f23; --item-shadow: 1px 1px 2px #130f23;
--item-hover-shadow: 1px 2px 4px #373737; --item-hover-shadow: 1px 2px 4px #373737;
@ -74,4 +75,6 @@
--config-settings-background: var(--background-darker); --config-settings-background: var(--background-darker);
--toast-background: var(--primary); --toast-background: var(--primary);
--toast-color: var(--background); --toast-color: var(--background);
--scroll-bar-color: var(--primary);
--scroll-bar-background: var(--background-darker);
} }

View File

@ -1,4 +1,6 @@
@import '@/styles/style-helpers.scss';
@font-face { @font-face {
font-family: 'Inconsolata'; font-family: 'Inconsolata';
src: url('./assets/fonts/Inconsolata-Light.ttf'); src: url('./assets/fonts/Inconsolata-Light.ttf');
@ -8,6 +10,8 @@ html {
margin: 0; margin: 0;
padding: 0; padding: 0;
transition: all 1s; transition: all 1s;
margin-top: -3px;
@extend .scroll-bar;
} }
/* Default app font face */ /* Default app font face */
@ -19,33 +23,3 @@ body, div, p, a, span, label, input, button {
h1, h2, h3, h4, h5 { h1, h2, h3, h4, h5 {
font-family: 'Inconsolata', sans-serif; font-family: 'Inconsolata', sans-serif;
} }
.bold { font-weight: bold; }
.light { font-weight: lighter; }
.text-left { text-align: left;}
.text-right { text-align: right;}
.text-center { text-align: center;}
.horizontal-center { margin: 0 auto; }
.border-box { box-sizing: border-box; }
/* 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;
}
.toast-error {
background: var(--danger) !important;
color: var(--white) !important;
font-size: 1.25rem !important;
}
.toast-success {
background: var(--success) !important;
color: var(--white) !important;
font-size: 1.25rem !important;
}

View File

@ -0,0 +1,44 @@
/* Fancy scrollbar */
.scroll-bar {
&::-webkit-scrollbar {
width: var(--scroll-bar-width);
height: var(--scroll-bar-width);
}
&::-webkit-scrollbar-track {
border-radius: var(--curve-factor);
background-color: var(--scroll-bar-background);
}
&::-webkit-scrollbar-thumb {
background: var(--scroll-bar-color);
border-radius: var(--curve-factor);
}
}
/* Single-style helpers */
.bold { font-weight: bold; }
.light { font-weight: lighter; }
.text-left { text-align: left;}
.text-right { text-align: right;}
.text-center { text-align: center;}
.horizontal-center { margin: 0 auto; }
.border-box { box-sizing: border-box; }
/* 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;
}
.toast-error {
background: var(--danger) !important;
color: var(--white) !important;
font-size: 1.25rem !important;
}
.toast-success {
background: var(--success) !important;
color: var(--white) !important;
font-size: 1.25rem !important;
}

View File

@ -37,7 +37,6 @@ const encodeGetParams = p => Object.entries(p).map(kv => kv.map(encodeURICompone
/* Restores the backup */ /* Restores the backup */
export const restore = (backupId, password) => { export const restore = (backupId, password) => {
const params = encodeGetParams({ backupId, subHash: makeSubHash(password) }); const params = encodeGetParams({ backupId, subHash: makeSubHash(password) });
console.log(makeSubHash(password));
const url = `${ENDPOINT}/?${params}`; const url = `${ENDPOINT}/?${params}`;
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
axios.get(url).then((response) => { axios.get(url).then((response) => {

View File

@ -50,6 +50,10 @@ module.exports = {
BACKUP_ID: 'backupId', BACKUP_ID: 'backupId',
BACKUP_HASH: 'backupHash', BACKUP_HASH: 'backupHash',
}, },
modalNames: {
CONF_EDITOR: 'CONF_EDITOR',
CLOUD_BACKUP: 'CLOUD_BACKUP',
},
topLevelConfKeys: { topLevelConfKeys: {
PAGE_INFO: 'pageInfo', PAGE_INFO: 'pageInfo',
APP_CONFIG: 'appConfig', APP_CONFIG: 'appConfig',

View File

@ -25,8 +25,9 @@
:displayData="getDisplayData(section)" :displayData="getDisplayData(section)"
:groupId="`section-${index}`" :groupId="`section-${index}`"
:items="filterTiles(section.items)" :items="filterTiles(section.items)"
:itemSize="itemSizeBound"
@itemClicked="finishedSearching()" @itemClicked="finishedSearching()"
:itemSize="itemSizeBound" @change-modal-visibility="updateModalVisibility"
:class="(filterTiles(section.items).length === 0 && searchValue) ? 'no-results' : ''" :class="(filterTiles(section.items).length === 0 && searchValue) ? 'no-results' : ''"
/> />
</div> </div>

View File

@ -2,6 +2,7 @@ module.exports = {
chainWebpack: config => { chainWebpack: config => {
config.module.rules.delete('svg'); config.module.rules.delete('svg');
}, },
configureWebpack: { configureWebpack: {
module: { module: {
rules: [ rules: [
@ -9,4 +10,11 @@ module.exports = {
], ],
}, },
}, },
pwa: {
name: 'Dashy',
themeColor: '#00CCB4',
msTileColor: '#0b1021',
manifestCrossorigin: 'use-credentials',
},
}; };

View File

@ -2563,6 +2563,15 @@ cli-width@^3.0.0:
resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-3.0.0.tgz#a2f48437a2caa9a22436e794bf071ec9e61cedf6" resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-3.0.0.tgz#a2f48437a2caa9a22436e794bf071ec9e61cedf6"
integrity sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw== integrity sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==
clipboard@^2.0.0:
version "2.0.8"
resolved "https://registry.yarnpkg.com/clipboard/-/clipboard-2.0.8.tgz#ffc6c103dd2967a83005f3f61976aa4655a4cdba"
integrity sha512-Y6WO0unAIQp5bLmk1zdThRhgJt/x3ks6f30s3oE3H1mgIEU33XyQjEf8gsf6DxC7NPX8Y1SsNWjUjL/ywLnnbQ==
dependencies:
good-listener "^1.2.2"
select "^1.1.2"
tiny-emitter "^2.0.0"
clipboardy@^2.3.0: clipboardy@^2.3.0:
version "2.3.0" version "2.3.0"
resolved "https://registry.yarnpkg.com/clipboardy/-/clipboardy-2.3.0.tgz#3c2903650c68e46a91b388985bc2774287dba290" resolved "https://registry.yarnpkg.com/clipboardy/-/clipboardy-2.3.0.tgz#3c2903650c68e46a91b388985bc2774287dba290"
@ -3255,6 +3264,11 @@ delayed-stream@~1.0.0:
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk=
delegate@^3.1.2:
version "3.2.0"
resolved "https://registry.yarnpkg.com/delegate/-/delegate-3.2.0.tgz#b66b71c3158522e8ab5744f720d8ca0c2af59166"
integrity sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==
depd@~1.1.2: depd@~1.1.2:
version "1.1.2" version "1.1.2"
resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9"
@ -4434,6 +4448,13 @@ globby@^9.2.0:
pify "^4.0.1" pify "^4.0.1"
slash "^2.0.0" slash "^2.0.0"
good-listener@^1.2.2:
version "1.2.2"
resolved "https://registry.yarnpkg.com/good-listener/-/good-listener-1.2.2.tgz#d53b30cdf9313dffb7dc9a0d477096aa6d145c50"
integrity sha1-1TswzfkxPf+33JoNR3CWqm0UXFA=
dependencies:
delegate "^3.1.2"
graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.2: graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.2:
version "4.2.6" version "4.2.6"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee"
@ -4565,6 +4586,11 @@ highlight.js@^10.7.1:
resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.7.2.tgz#89319b861edc66c48854ed1e6da21ea89f847360" resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.7.2.tgz#89319b861edc66c48854ed1e6da21ea89f847360"
integrity sha512-oFLl873u4usRM9K63j4ME9u3etNF0PLiJhSQ8rdfuL51Wn3zkD6drf9ZW0dOzjnZI22YYG24z30JcmfCZjMgYg== integrity sha512-oFLl873u4usRM9K63j4ME9u3etNF0PLiJhSQ8rdfuL51Wn3zkD6drf9ZW0dOzjnZI22YYG24z30JcmfCZjMgYg==
highlight.js@^11.0.0:
version "11.0.0"
resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-11.0.0.tgz#e22ac9ca45edc4f87a2187685d591a108ceb8449"
integrity sha512-ByaTMfsSuoqerTwemOgpIhfULEIaK52JJYhky/sK7/Yqc0+t7Uh5DHay9vIC94YXSupnQ1Vqfc9VXrYP4eXW3Q==
hmac-drbg@^1.0.1: hmac-drbg@^1.0.1:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1"
@ -7069,6 +7095,13 @@ pretty-error@^2.0.2:
lodash "^4.17.20" lodash "^4.17.20"
renderkid "^2.0.4" renderkid "^2.0.4"
prismjs@^1.23.0:
version "1.23.0"
resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.23.0.tgz#d3b3967f7d72440690497652a9d40ff046067f33"
integrity sha512-c29LVsqOaLbBHuIbsTxaKENh1N2EQBOHaWv7gkHN4dgRbxSREqDnDbtFJYdpPauS4YCplMSNCABQ6Eeor69bAA==
optionalDependencies:
clipboard "^2.0.0"
process-nextick-args@~2.0.0: process-nextick-args@~2.0.0:
version "2.0.1" version "2.0.1"
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"
@ -7643,6 +7676,11 @@ select-hose@^2.0.0:
resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca"
integrity sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo= integrity sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo=
select@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/select/-/select-1.1.2.tgz#0e7350acdec80b1108528786ec1d4418d11b396d"
integrity sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0=
selfsigned@^1.10.8: selfsigned@^1.10.8:
version "1.10.8" version "1.10.8"
resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-1.10.8.tgz#0d17208b7d12c33f8eac85c41835f27fc3d81a30" resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-1.10.8.tgz#0d17208b7d12c33f8eac85c41835f27fc3d81a30"
@ -8383,6 +8421,11 @@ timsort@^0.3.0:
resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4" resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4"
integrity sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q= integrity sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=
tiny-emitter@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/tiny-emitter/-/tiny-emitter-2.1.0.tgz#1d1a56edfc51c43e863cbb5382a72330e3555423"
integrity sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==
tmp@^0.0.33: tmp@^0.0.33:
version "0.0.33" version "0.0.33"
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"
@ -8852,6 +8895,11 @@ vue-material-tabs@^0.0.7:
resolved "https://registry.yarnpkg.com/vue-material-tabs/-/vue-material-tabs-0.0.7.tgz#5f3fa04ad35384af68582f7c89ad4cecac89207b" resolved "https://registry.yarnpkg.com/vue-material-tabs/-/vue-material-tabs-0.0.7.tgz#5f3fa04ad35384af68582f7c89ad4cecac89207b"
integrity sha512-02X5paTksYKrGvSRpMdkctRO9qhvJFD5VEGxd0xjOX4sYz6mZSAez0Z/+aYf7Z5ziY+eJ9dMQmxaLn9DVKQRJw== integrity sha512-02X5paTksYKrGvSRpMdkctRO9qhvJFD5VEGxd0xjOX4sYz6mZSAez0Z/+aYf7Z5ziY+eJ9dMQmxaLn9DVKQRJw==
vue-prism-editor@^1.2.2:
version "1.2.2"
resolved "https://registry.yarnpkg.com/vue-prism-editor/-/vue-prism-editor-1.2.2.tgz#023cfd4329848f191aac851f2f5e6c7a8c2e059f"
integrity sha512-Lq2VgVygTx3Whn/tC8gD4m1ajA4lzSyCTqPLZA1Dq/ErbBaZA93FWRblwCoDR7AD2nXhGWuiTzb5ih3guzB7DA==
vue-resize@^1.0.1: vue-resize@^1.0.1:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/vue-resize/-/vue-resize-1.0.1.tgz#c120bed4e09938771d622614f57dbcf58a5147ee" resolved "https://registry.yarnpkg.com/vue-resize/-/vue-resize-1.0.1.tgz#c120bed4e09938771d622614f57dbcf58a5147ee"