Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEATURE] - Support for Material Icons #141

Merged
merged 5 commits into from
Aug 9, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .github/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## ✨ 1.5.7 - Adds Support for Material Design Icons [PR #141](https://github.com/Lissy93/dashy/pull/141)
- Enables user to use any icon from [materialdesignicons.com](https://dev.materialdesignicons.com/icons), Re: #139
- Also adds support for [simpleicons.org](https://simpleicons.org/)
- Assets only loaded when needed
- Adds docs for using MDI icons

## ⚡️ 1.5.6 - Refactor + Couple of small things [PR #135](https://github.com/Lissy93/dashy/pull/135)
- The main Dockerfile now uses yarn.lock instead of package-lock.json
- Adds a check to verify password is not empty in cloud backup screen
Expand Down
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ Are using Dashy? Want to share your dashboard here too - [Submit your Screenshot

> For full setup instructions, see: [**Deployment**](./docs/deployment.md)

#### Deploying from Docker Hub 🐳
### Deploying from Docker Hub 🐳

You will need [Docker](https://docs.docker.com/get-docker/) installed on your system

Expand All @@ -122,13 +122,12 @@ docker run -d \
```

If you prefer to use Docker Compose, [here is an example](./docs/deployment.md#using-docker-compose).
You can also build the Docker container from source, by cloning the repo, cd'ing into it and running `docker build .` and `docker compose up`.

> Once you've got Dashy running, you can take a look at [App Management Docs](./docs/management.md), for info on using health checks, provisioning assets, configuring web servers, securing your app, logs, performance and more.

#### Deploying from Source 🚀
### Deploying from Source 🚀

You will need both [git](https://git-scm.com/downloads) and the latest or LTS version of [Node.js](https://nodejs.org/) installed on your system
You will need [git](https://git-scm.com/downloads), the latest or LTS version of [Node.js](https://nodejs.org/) and (optionally) [Yarn](https://yarnpkg.com/) installed on your system.

- Get Code: `git clone [email protected]:Lissy93/dashy.git` and `cd dashy`
- Configuration: Fill in you're settings in `./public/conf.yml`
Expand All @@ -138,7 +137,7 @@ You will need both [git](https://git-scm.com/downloads) and the latest or LTS ve

> See docs [Full list of Dashy's commands](./docs/management.md#basic-commands)

#### Deploy to the Cloud ☁️
### Deploy to the Cloud ☁️

Dashy supports 1-Click deployments on several popular cloud platforms. To spin up a new instance, just click a link below:
- [<img src="https://i.ibb.co/ZxtzrP3/netlify.png" width="18"/> Deploy to Netlify](https://app.netlify.com/start/deploy?repository=https://github.com/lissy93/dashy)
Expand Down Expand Up @@ -207,7 +206,8 @@ Both sections and items can have an icon associated with them, and defined under
- **Generative**: Setting `icon: generative`, will generate a unique for a given service, based on it's URL or IP
- **Emoji**: Use an emoji as a tile icon, by putting the emoji's code as the icon attribute. Emojis can be specified either as emojis (`🚀`), unicode (`'U+1F680'`) or shortcode (`':rocket:'`).
- **URL**: You can also pass in a URL to an icon asset, hosted either locally or using any CDN service. E.g. `icon: https://i.ibb.co/710B3Yc/space-invader-x256.png`.
- **Local Image**: To use a local image, store it in `./public/item-icons/` (or create a volume in Docker: `-v /local/image/directory:/app/public/item-icons/`) , and reference it by name and extension - e.g. set `icon: image.png` to use `./public/item-icon/image.png`. You can also use sub-folders here if you have a lot of icons, to keep them organized.
- **Local Image**: To use a local image, store it in `./public/item-icons/` (or create a volume in Docker: `-v /local/image/directory:/app/public/item-icons/`) , and reference it by name and extension - e.g. set `icon: image.png` to use `./public/item-icon/image.png`. You can also use sub-folders here.
- **Material Design Icons**: You can also use any icon from [materialdesignicons.com](https://dev.materialdesignicons.com/icons) by setting the icon to `mdi-[icon-name]`.

**[⬆️ Back to Top](#dashy)**

Expand Down
2 changes: 1 addition & 1 deletion docs/configuring.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ To disallow any changes from being written to disk via the UI config editor, set

**Field** | **Type** | **Required**| **Description**
--- | --- | --- | ---
**`icon`** | `string` | _Optional_ | The icon for a given item or section. Can be a font-awesome icon, favicon, remote URL or local URL. If set to `favicon`, the icon will be automatically fetched from the items website URL. To use font-awesome, specify the category, followed by the icon name, e.g. `fas fa-rocket`, `fab fa-monero` or `fal fa-duck` - note that to use pro icons, you mut specify `appConfig.fontAwesomeKey`. If set to `generative`, then a unique icon is generated from the apps URL or IP. You can also use hosted any by specifying it's URL, e.g. `https://i.ibb.co/710B3Yc/space-invader-x256.png`. To use a local image, first store it in `./public/item-icons/` (or `-v /app/public/item-icons/` in Docker) , and reference it by name and extension - e.g. set `image.png` to use `./public/item-icon/image.png`, you can also use sub-folders if you have a lot of icons, to keep them organised.
**`icon`** | `string` | _Optional_ | The icon for a given item or section. Can be a font-awesome icon, favicon, remote URL or local URL. If set to `favicon`, the icon will be automatically fetched from the items website URL. To use font-awesome, specify the category, followed by the icon name, e.g. `fas fa-rocket`, `fab fa-monero` or `fal fa-duck` - note that to use pro icons, you mut specify `appConfig.fontAwesomeKey`. Similarly, you can also use [simple-icons](https://simpleicons.org/) by setting icon to `si-[icon-name]` or [material-design-icons](https://dev.materialdesignicons.com/icons) by setting icon to `mdi-[icon-name]`. If set to `generative`, then a unique icon is generated from the apps URL or IP. You can also use hosted any by specifying it's URL, e.g. `https://i.ibb.co/710B3Yc/space-invader-x256.png`. To use a local image, first store it in `./public/item-icons/` (or `-v /app/public/item-icons/` in Docker) , and reference it by name and extension - e.g. set `image.png` to use `./public/item-icon/image.png`, you can also use sub-folders if you have a lot of icons, to keep them organised.

**[⬆️ Back to Top](#configuring)**

Expand Down
24 changes: 23 additions & 1 deletion docs/icons.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ Both sections and items can have an icon, which is specified using the `icon` at
<img width="500" src="https://i.ibb.co/GTVmZnc/dashy-example-icons.png" />
</p>

Note that, if you are using icons from an external source (like font-awesome or material-design-icons), then the relevant font file will be loaded in automatically if and when needed, but combining icons from multiple services may have a negative impact on performance.

### Font Awesome
You can use any [Font Awesome Icon](https://fontawesome.com/icons) simply by specifying it's identifier. This is in the format of `[category] [name]` and can be found on the page for any given icon on the Font Awesome site. For example: `fas fa-rocket`, `fab fa-monero` or `fas fa-unicorn`.

Expand Down Expand Up @@ -54,7 +56,7 @@ You can use almost any emoji as an icon for items or sections. You can specify t
<img width="580" src="https://i.ibb.co/YLwgTf9/emoji-icons-1.png" />
</p>

The following example shows the unicode options available, all three will render the 🚀 emoji.
The following examples will all render the same rocket (🚀) emoji:

```yaml
items:
Expand All @@ -74,5 +76,25 @@ You may also want to store your icons locally, bundled within Dashy so that ther

You can also use sub-folders within the `item-icons` directory to keep things organised. You would then specify an icon with it's folder name slash image name. For example: `networking/monit.png`

### Material Design Icons
Dashy also supports 5000+ [material-design-icons](https://github.com/Templarian/MaterialDesign). To use these, first find the name/ slug for your icon [here](https://dev.materialdesignicons.com/icons), and then prefix is with `mdi-`.

For example:
```yaml
sections:
- name: Material Design Icons Example
items:
- title: Alien Icon
icon: mdi-alien
- title: Fire Icon
icon: mdi-fire
- title: Dino Icon
icon: mdi-google-downasaur

```

### Simple Icons
To use glyphs from [SimpleIcons.org](https://simpleicons.org/), first find the icon slug, and then prefix it with `si-`. The image will be loaded directly from the Simple Icons

### No Icon
If you don't wish for a given item or section to have an icon, just leave out the `icon` attribute.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "Dashy",
"version": "1.5.6",
"version": "1.5.7",
"license": "MIT",
"main": "server",
"scripts": {
Expand Down
53 changes: 44 additions & 9 deletions src/components/LinkItems/ItemIcon.vue
Original file line number Diff line number Diff line change
@@ -1,18 +1,27 @@
<template>
<div class="item-icon">
<!-- Font-Awesome Icon -->
<i v-if="iconType === 'font-awesome'" :class="`${icon} ${size}`" ></i>
<!-- Emoji Icon -->
<i v-else-if="iconType === 'emoji'" :class="`emoji-icon ${size}`" >{{getEmoji(iconPath)}}</i>
<!-- Material Design Icon -->
<span v-else-if="iconType === 'mdi'" :class=" `mdi ${icon} ${size}`"></span>
<!-- Simple-Icons -->
<object v-else-if="iconType === 'si'" :class="`simple-icons ${size}`"
type="image/svg+xml" :data="getSimpleIcon(icon)"></object>
<!-- Standard image asset icon -->
<img v-else-if="icon" :src="iconPath" @error="imageNotFound"
:class="`tile-icon ${size} ${broken ? 'broken' : ''}`"
/>
<!-- Icon could not load/ broken url -->
<BrokenImage v-if="broken" class="missing-image" />
</div>
</template>

<script>
import BrokenImage from '@/assets/interface-icons/broken-icon.svg';
import ErrorHandler from '@/utils/ErrorHandler';
import { faviconApi as defaultFaviconApi, faviconApiEndpoints } from '@/utils/defaults';
import { faviconApi as defaultFaviconApi, faviconApiEndpoints, iconCdns } from '@/utils/defaults';
import EmojiUnicodeRegex from '@/utils/EmojiUnicodeRegex';
import emojiLookup from '@/utils/emojis.json';

Expand All @@ -28,17 +37,18 @@ export default {
BrokenImage,
},
computed: {
/* Determines the type of icon */
iconType: function iconType() {
return this.determineImageType(this.icon);
},
/* Gets the icon path, dependent on icon type */
iconPath: function iconPath() {
return this.getIconPath(this.icon, this.url);
},
},
data() {
return {
broken: false,
// faviconApi: this.config.appConfig.faviconApi || defaultFaviconApi,
broken: false, // If true, was unable to resolve icon
};
},
methods: {
Expand Down Expand Up @@ -80,7 +90,7 @@ export default {
getFavicon(fullUrl) {
if (this.shouldUseDefaultFavicon(fullUrl)) { // Check if we should use local icon
const urlParts = fullUrl.split('/');
if (urlParts.length >= 2) return `${urlParts[0]}/${urlParts[1]}/${urlParts[2]}/favicon.ico`;
if (urlParts.length >= 2) return `${urlParts[0]}/${urlParts[1]}/${urlParts[2]}/${iconCdns.faviconName}`;
} else if (fullUrl.includes('http')) { // Service is running publicly
const host = this.getHostName(fullUrl);
const faviconApi = this.config.appConfig.faviconApi || defaultFaviconApi;
Expand All @@ -95,11 +105,18 @@ export default {
const isLocalIP = /(127\.)|(192\.168\.)|(10\.)|(172\.1[6-9]\.)|(172\.2[0-9]\.)|(172\.3[0-1]\.)|(::1$)|([fF][cCdD])|(localhost)/;
return (isLocalIP.test(fullUrl) || this.config.appConfig.faviconApi === 'local');
},
/* Fetches the path of local images, from Docker container */
getLocalImagePath(img) {
return `/item-icons/${img}`;
return `${iconCdns.localPath}/${img}`;
},
/* Formats the URL for fetching the generative icons */
getGenerativeIcon(url) {
return `https://ipsicon.io/${this.getHostName(url)}.svg`;
return `${iconCdns.generative}/${this.getHostName(url)}.svg`;
},
/* Formats the URL for getting Simple-Icons SVG asset */
getSimpleIcon(img) {
const imageName = img.replace('si-', '');
return `${iconCdns.si}/${imageName}.svg`;
},
/* Checks if the icon is from a local image, remote URL, SVG or font-awesome */
getIconPath(img, url) {
Expand All @@ -108,8 +125,10 @@ export default {
case 'img': return this.getLocalImagePath(img);
case 'favicon': return this.getFavicon(url);
case 'generative': return this.getGenerativeIcon(url);
case 'svg': return img;
case 'emoji': return img;
case 'mdi': return img; // Material design icons
case 'simple-icons': return this.getSimpleIcon(img);
case 'svg': return img; // Local SVG icon
case 'emoji': return img; // Emoji/ unicode
default: return '';
}
},
Expand All @@ -121,12 +140,15 @@ export default {
else if (this.isUrl(img)) imgType = 'url';
else if (this.isImage(img)) imgType = 'img';
else if (img.includes('fa-')) imgType = 'font-awesome';
else if (img.includes('mdi-')) imgType = 'mdi';
else if (img.includes('si-')) imgType = 'si';
else if (img === 'favicon') imgType = 'favicon';
else if (img === 'generative') imgType = 'generative';
else if (this.isEmoji(img).isEmoji) imgType = 'emoji';
else imgType = 'none';
return imgType;
},
/* For a given URL, return the hostname only. Used for favicon and generative icons */
getHostName(url) {
try { return new URL(url).hostname; } catch (e) { return url; }
},
Expand All @@ -140,6 +162,7 @@ export default {
</script>

<style lang="scss">
/* Default Image Icon */
.tile-icon {
width: 2rem;
// filter: var(--item-icon-transform);
Expand All @@ -152,7 +175,8 @@ export default {
width: 3rem;
}
}
i.fas, i.fab, i.far, i.fal, i.fad {
/* Font-Awesome and Material Design Icons */
i.fas, i.fab, i.far, i.fal, i.fad, span.mdi {
font-size: 2rem;
color: currentColor;
margin: 1px 4px;
Expand All @@ -163,13 +187,23 @@ export default {
font-size: 2.5rem;
}
}
span.mdi {
font-size: 2.5rem;
}
object.tile-icon {
width: 55px;
height: 55px;
svg, svg g, svg g path {
fill: currentColor;
}
}
/* Simple Icons */
object.simple-icons {
width: 2rem;
&.small { width: 1.5rem; }
&.large { width: 2.5rem; }
}
/* Emoji Icons */
i.emoji-icon {
font-style: normal;
font-size: 2rem;
Expand All @@ -181,6 +215,7 @@ export default {
font-size: 2.5rem;
}
}
/* Icon Not Found */
.missing-image {
width: 3.5rem;
path {
Expand Down
9 changes: 9 additions & 0 deletions src/utils/defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,15 @@ module.exports = {
allesedv: 'https://f1.allesedv.com/128/$URL',
webmasterapi: 'https://api.webmasterapi.com/v1/favicon/yEwx0ZFs0CSPshHq/$URL',
},
/* The URL to CDNs used for external icons. These are only loaded when required */
iconCdns: {
fa: 'https://kit.fontawesome.com',
mdi: 'https://cdn.jsdelivr.net/npm/@mdi/[email protected]/css/materialdesignicons.min.css',
si: 'https://unpkg.com/simple-icons@v5/icons',
generative: 'https://ipsicon.io',
localPath: '/item-icons',
faviconName: 'favicon.ico',
},
/* Available built-in colors for the theme builder */
swatches: [
['#eb5cad', '#985ceb', '#5346f3', '#5c90eb'],
Expand Down
31 changes: 25 additions & 6 deletions src/views/Home.vue
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@

import SettingsContainer from '@/components/Settings/SettingsContainer.vue';
import ItemGroup from '@/components/LinkItems/ItemGroup.vue';
import Defaults, { localStorageKeys } from '@/utils/defaults';
import Defaults, { localStorageKeys, iconCdns } from '@/utils/defaults';

export default {
name: 'home',
Expand Down Expand Up @@ -160,16 +160,21 @@ export default {
availibleThemes.Default = '#';
return availibleThemes;
},
/* Checks if any of the icons are Font Awesome glyphs */
checkIfFontAwesomeNeeded() {
/* Checks if any sections or items use icons from a given CDN */
checkIfIconLibraryNeeded(prefix) {
let isNeeded = false;
if (!this.sections) return false;
this.sections.forEach((section) => {
if (section.icon && section.icon.includes('fa-')) isNeeded = true;
if (section.icon && section.icon.includes(prefix)) isNeeded = true;
section.items.forEach((item) => {
if (item.icon && item.icon.includes('fa-')) isNeeded = true;
if (item.icon && item.icon.includes(prefix)) isNeeded = true;
});
});
return isNeeded;
},
/* Checks if any of the icons are Font Awesome glyphs */
checkIfFontAwesomeNeeded() {
let isNeeded = this.checkIfIconLibraryNeeded('fa-');
const currentTheme = localStorage[localStorageKeys.THEME]; // Some themes require FA
if (['material', 'material-dark'].includes(currentTheme)) isNeeded = true;
return isNeeded;
Expand All @@ -179,10 +184,23 @@ export default {
if (this.appConfig.enableFontAwesome || this.checkIfFontAwesomeNeeded()) {
const fontAwesomeScript = document.createElement('script');
const faKey = this.appConfig.fontAwesomeKey || Defaults.fontAwesomeKey;
fontAwesomeScript.setAttribute('src', `https://kit.fontawesome.com/${faKey}.js`);
fontAwesomeScript.setAttribute('src', `${iconCdns.fa}/${faKey}.js`);
document.head.appendChild(fontAwesomeScript);
}
},
/* Checks if any of the icons are Material Design Icons */
checkIfMdiNeeded() {
return this.checkIfIconLibraryNeeded('mdi-');
},
/* Injects Material Design Icons, only if needed */
initiateMaterialDesignIcons() {
if (this.checkIfMdiNeeded()) {
const mdiStylesheet = document.createElement('link');
mdiStylesheet.setAttribute('rel', 'stylesheet');
mdiStylesheet.setAttribute('href', iconCdns.mdi);
document.head.appendChild(mdiStylesheet);
}
},
/* Returns true if there is more than 1 sub-result visible during searching */
checkIfResults() {
if (!this.sections) return false;
Expand All @@ -204,6 +222,7 @@ export default {
},
mounted() {
this.initiateFontAwesome();
this.initiateMaterialDesignIcons();
this.layout = this.layoutOrientation;
this.itemSizeBound = this.iconSize;
},
Expand Down