mirror of
https://github.com/Lissy93/dashy.git
synced 2024-11-23 12:43:52 +03:00
Adds additional safeguards for edge cases, and improves theme coverage
This commit is contained in:
parent
473639dd5e
commit
3aba7f23da
10
README.md
10
README.md
@ -21,6 +21,12 @@
|
||||
- Many options for icons, including full Font-Awesome support and the ability to auto-fetch icon from URLs favicon
|
||||
- Plus lots more...
|
||||
|
||||
## Live Demo
|
||||
|
||||
- [Demo 1](https://dashy-demo-1.as93.net)
|
||||
- [Demo 2](https://dashy-demo-2.as93.net)
|
||||
- [Demo 3](https://dashy-demo-3.as93.net)
|
||||
|
||||
---
|
||||
|
||||
## Running the App 🏃♂️
|
||||
@ -140,6 +146,10 @@ appConfig:
|
||||
- [ ] Add support for custom widgets
|
||||
- [ ] Convert JavaScript to TypeScript
|
||||
|
||||
### Alternatives 🙌
|
||||
|
||||
There are a few self-hosted web apps, that serve a similar purpose to Dashy. Including, but not limited to: [HomeDash2](https://lamarios.github.io/Homedash2), [Homer](https://github.com/bastienwirtz/homer), [Organizr](https://organizr.app/) and [Heimdall](https://github.com/linuxserver/Heimdall).
|
||||
|
||||
### Credits 🏆
|
||||
|
||||
The app makes use of the following components, kudos to their respective authors
|
||||
|
50
src/assets/interface-icons/broken-icon.svg
Normal file
50
src/assets/interface-icons/broken-icon.svg
Normal file
@ -0,0 +1,50 @@
|
||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
|
||||
<rect x="96.397" y="284.966" transform="matrix(-0.9401 0.341 -0.341 -0.9401 320.6888 545.5591)" width="31.998" height="31.998"/>
|
||||
<polygon points="271.552,301.072 193.696,254.352 152.08,269.504 163.024,299.568 190.304,289.648 272.448,338.928
|
||||
370.048,273.856 480,315.088 480,480 32,480 32,347.2 72.8,332.352 61.872,302.304 0,324.8 0,512 512,512 512,292.912
|
||||
365.952,238.144 "/>
|
||||
<polygon points="0,0 0,278.848 66.576,254.624 55.648,224.56 32,233.152 32,32 480,32 480,200.912 365.952,158.144
|
||||
271.552,221.072 193.696,174.352 145.856,191.744 156.8,221.808 190.304,209.648 272.448,258.928 370.048,193.856 512,247.088
|
||||
512,0 "/>
|
||||
<rect x="90.237" y="207.181" transform="matrix(-0.3419 -0.9397 0.9397 -0.3419 -67.18 399.3166)" width="31.984" height="32"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.3 KiB |
@ -2,7 +2,8 @@
|
||||
<modal :name="name" :resizable="true" width="80%" height="80%" @closed="$emit('closed')">
|
||||
<div slot="top-right" @click="hide()">Close</div>
|
||||
<a @click="hide()" class="close-button" title="Close">x</a>
|
||||
<iframe :src="url" @keydown.esc="close" class="frame"/>
|
||||
<iframe v-if="url" :src="url" @keydown.esc="close" class="frame"/>
|
||||
<div v-else class="no-url">No URL Specified</div>
|
||||
</modal>
|
||||
</template>
|
||||
|
||||
@ -35,6 +36,16 @@ export default {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.no-url {
|
||||
margin: 4rem auto;
|
||||
width: fit-content;
|
||||
font-size: 2rem;
|
||||
padding: 0.5rem;
|
||||
border: 1px dashed #ff0000;
|
||||
border-radius: 3px;
|
||||
background: #f4f2f2;
|
||||
}
|
||||
|
||||
.close-button {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
@ -46,7 +57,6 @@ export default {
|
||||
border-left: 1px solid var(--primary);
|
||||
border-bottom: 1px solid var(--primary);
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background: var(--background);
|
||||
color: var(--primary);
|
||||
|
@ -96,7 +96,7 @@ export default {
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
<style lang="scss">
|
||||
|
||||
.item {
|
||||
flex-grow: 1;
|
||||
@ -200,7 +200,7 @@ export default {
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
height: 2rem;
|
||||
img {
|
||||
div img, div svg.missing-image {
|
||||
width: 2rem;
|
||||
}
|
||||
.tile-title {
|
||||
@ -217,8 +217,8 @@ export default {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
height: auto;
|
||||
img {
|
||||
width: 2rem;
|
||||
div img, div svg.missing-image {
|
||||
width: 2.5rem;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
.tile-title {
|
||||
|
@ -1,9 +1,16 @@
|
||||
<template>
|
||||
<i v-if="iconType === 'font-awesome'" :class="`${icon} ${size}`" ></i>
|
||||
<img v-else-if="icon" :src="iconPath" :class="`tile-icon ${size}`" />
|
||||
<div>
|
||||
<i v-if="iconType === 'font-awesome'" :class="`${icon} ${size}`" ></i>
|
||||
<img v-else-if="icon" :src="iconPath" @error="imageNotFound"
|
||||
:class="`tile-icon ${size} ${broken ? 'broken' : ''}`"
|
||||
/>
|
||||
<BrokenImage v-if="broken" class="missing-image" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BrokenImage from '@/assets/interface-icons/broken-icon.svg';
|
||||
import ErrorHandler from '@/utils/ErrorHandler';
|
||||
|
||||
export default {
|
||||
name: 'Icon',
|
||||
@ -12,6 +19,9 @@ export default {
|
||||
url: String, // Used for fetching the favicon
|
||||
size: String, // Either small, medium or large
|
||||
},
|
||||
components: {
|
||||
BrokenImage,
|
||||
},
|
||||
computed: {
|
||||
iconType: function iconType() {
|
||||
return this.determineImageType(this.icon);
|
||||
@ -20,6 +30,11 @@ export default {
|
||||
return this.getIconPath(this.icon, this.url);
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
broken: false,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
/* Check if a string is in a URL format. Used to identify tile icon source */
|
||||
isUrl(str) {
|
||||
@ -72,6 +87,11 @@ export default {
|
||||
else imgType = 'none';
|
||||
return imgType;
|
||||
},
|
||||
/* Called when the path to the image cannot be resolved */
|
||||
imageNotFound() {
|
||||
this.broken = true;
|
||||
ErrorHandler(`The path to '${this.icon}' could not be resolved`);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@ -80,6 +100,7 @@ export default {
|
||||
.tile-icon {
|
||||
width: 60px;
|
||||
filter: var(--item-icon-transform);
|
||||
&.broken { display: none; }
|
||||
}
|
||||
i.fas, i.fab, i.far, i.fal, i.fad {
|
||||
font-size: 2rem;
|
||||
@ -99,4 +120,11 @@ export default {
|
||||
fill: currentColor;
|
||||
}
|
||||
}
|
||||
|
||||
.missing-image {
|
||||
width: 3.5rem;
|
||||
path {
|
||||
fill: currentColor;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -76,12 +76,12 @@ export default {
|
||||
padding: 0.1em 0.3em;
|
||||
z-index: 10;
|
||||
border-radius: 12px;
|
||||
border: 1px solid var(--background-darker);
|
||||
border: 1px solid var(--welcome-popup-background);
|
||||
-webkit-box-shadow: 2px 1px 5px #130f23;
|
||||
box-shadow: 2px 1px 5px #130f23;
|
||||
border: 1px solid var(--primary);
|
||||
color: var(--primary);
|
||||
background: var(--background-darker);
|
||||
border: 1px solid var(--welcome-popup-text-color);
|
||||
color: var(--welcome-popup-text-color);
|
||||
background: var(--welcome-popup-background);
|
||||
cursor: default;
|
||||
opacity: 0.94;
|
||||
@include phone {
|
||||
@ -91,9 +91,9 @@ export default {
|
||||
position: absolute;
|
||||
top: -35px;
|
||||
left: 20px;
|
||||
border: 1px solid var(--primary);
|
||||
color: var(--primary);
|
||||
background: var(--background-darker);
|
||||
border: 1px solid var(--welcome-popup-text-color);
|
||||
color: var(--welcome-popup-text-color);
|
||||
background: var(--welcome-popup-background);
|
||||
padding: 4px;
|
||||
border-radius: var(--curve-factor);
|
||||
}
|
||||
@ -108,7 +108,7 @@ export default {
|
||||
border: 1px solid transparent;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
border: 1px solid var(--primary);
|
||||
border: 1px solid var(--welcome-popup-text-color);
|
||||
opacity: var(--dimming-factor);
|
||||
}
|
||||
}
|
||||
|
@ -75,6 +75,7 @@ export default {
|
||||
align-items: center;
|
||||
align-items: stretch;
|
||||
background: linear-gradient(0deg, var(--background) 0%, var(--background-darker) 100%);
|
||||
box-shadow: var(--settings-container-shadow);
|
||||
}
|
||||
.options-container {
|
||||
display: flex;
|
||||
|
@ -29,6 +29,7 @@
|
||||
--item-icon-transform: drop-shadow(2px 4px 6px var(--transparent-50)) saturate(0.65);
|
||||
--item-icon-transform-hover: drop-shadow(4px 8px 3px var(--transparent-50)) saturate(2);
|
||||
--item-group-shadow: var(--item-shadow);
|
||||
--settings-container-shadow: none;
|
||||
|
||||
/* Specific components, using variables allows them to be overridden individually */
|
||||
--heading-text-color: var(--primary);
|
||||
@ -48,4 +49,6 @@
|
||||
--search-field-background: var(--background);
|
||||
--footer-text-color: var(--medium-grey);
|
||||
--footer-text-color-link: var(--primary);
|
||||
--welcome-popup-background: var(--background-darker);
|
||||
--welcome-popup-text-color: var(--primary);
|
||||
}
|
||||
|
@ -144,6 +144,9 @@ html[data-theme='material'] {
|
||||
--item-hover-shadow: 0 1px 4px #00000029, 0 2px 4px #0000002a;
|
||||
--item-icon-transform: drop-shadow(1px 2px 1px var(--transparent-30)) saturate(0.65);
|
||||
--item-icon-transform-hover: drop-shadow(1px 3px 2px var(--transparent-30)) saturate(2);
|
||||
--settings-container-shadow: 0 1px 3px #0000005e, 0 1px 2px #00000085;
|
||||
--welcome-popup-background: #01579b;
|
||||
--welcome-popup-text-color: #ffffff;
|
||||
}
|
||||
|
||||
html[data-theme='material-dark'] {
|
||||
@ -171,6 +174,8 @@ html[data-theme='material-dark'] {
|
||||
--item-hover-shadow: 4px 4px 3px #00000082, 0 1px 10px #00000040;
|
||||
--item-icon-transform: drop-shadow(1px 2px 1px var(--transparent-30)) saturate(0.65);
|
||||
--item-icon-transform-hover: drop-shadow(1px 3px 2px var(--transparent-30)) saturate(2);
|
||||
--welcome-popup-background: #131a1f;
|
||||
--welcome-popup-text-color: var(--primary);
|
||||
}
|
||||
|
||||
html[data-theme='colorful'] {
|
||||
|
@ -53,3 +53,8 @@ $extra-large: 2800px;
|
||||
@content;
|
||||
}
|
||||
}
|
||||
@mixin big-screen-up {
|
||||
@media (min-width: #{$extra-large}) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
11
src/utils/ErrorHandler.js
Normal file
11
src/utils/ErrorHandler.js
Normal file
@ -0,0 +1,11 @@
|
||||
/* eslint no-console: ["error", { allow: ["warn", "error"] }] */
|
||||
|
||||
/**
|
||||
* Function called when an error happens
|
||||
* If you wish to use an error logging service, put code for it here
|
||||
*/
|
||||
const ErrorHandler = function handler(msg) {
|
||||
console.warn(msg);
|
||||
};
|
||||
|
||||
export default ErrorHandler;
|
@ -23,6 +23,7 @@
|
||||
:items="filterTiles(section.items)"
|
||||
@itemClicked="finishedSearching()"
|
||||
:itemSize="itemSizeBound"
|
||||
:class="(filterTiles(section.items).length === 0 && searchValue) ? 'no-results' : ''"
|
||||
/>
|
||||
</div>
|
||||
<div v-else class="no-data">No Data Found Yet</div>
|
||||
@ -81,12 +82,14 @@ export default {
|
||||
},
|
||||
/* Extracts the site name from domain, used for the searching functionality */
|
||||
getDomainFromUrl(url) {
|
||||
if (!url) return '';
|
||||
const urlPattern = /^(?:https?:\/\/)?(?:w{3}\.)?([a-z\d.-]+)\.(?:[a-z.]{2,10})(?:[/\w.-]*)*/;
|
||||
const domainPattern = url.match(urlPattern);
|
||||
return domainPattern ? domainPattern[1] : '';
|
||||
},
|
||||
/* Returns only the tiles that match the users search query */
|
||||
filterTiles(allTiles) {
|
||||
if (!allTiles) return [];
|
||||
return allTiles.filter((tile) => {
|
||||
const {
|
||||
title, description, provider, url,
|
||||
@ -181,6 +184,7 @@ export default {
|
||||
flex-direction: column;
|
||||
}
|
||||
&.orientation-vertical {
|
||||
max-width: 100%;
|
||||
@include tablet-up {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
@ -203,6 +207,12 @@ export default {
|
||||
@include big-screen {
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
}
|
||||
@include big-screen-up {
|
||||
grid-template-columns: repeat(5, 1fr);
|
||||
}
|
||||
|
||||
/* Hide when search term returns nothing */
|
||||
.no-results { display: none; }
|
||||
}
|
||||
|
||||
.no-data {
|
||||
|
Loading…
Reference in New Issue
Block a user