mirror of
https://github.com/home-assistant/home-assistant.io.git
synced 2025-05-04 18:18:58 +00:00
342 lines
13 KiB
HTML
342 lines
13 KiB
HTML
---
|
|
title: "Integrations"
|
|
description: "List of the built-in integrations of Home Assistant."
|
|
sidebar: false
|
|
is_homepage: true
|
|
feedback: false
|
|
body_id: components-page
|
|
regenerate: false
|
|
---
|
|
|
|
{%- comment -%}Can't use where to count nil because of https://github.com/jekyll/jekyll/issues/6038{%- endcomment -%}
|
|
{%- assign tot = 0 -%}
|
|
{%- for comp in site.integrations -%}
|
|
{%- if comp.ha_category -%}
|
|
{%- if comp.ha_category.first -%}
|
|
{%- assign tot = tot | plus: comp.ha_category.size -%}
|
|
{%- else -%}
|
|
{%- assign tot = tot | plus: 1 -%}
|
|
{%- endif -%}
|
|
{%- endif %}
|
|
{%- endfor -%}
|
|
|
|
{%- assign components = site.integrations | sort: 'title' -%}
|
|
{%- assign components_by_version = site.integrations | group_components_by_release -%}
|
|
{%- assign categories = components | map: 'ha_category' | join: ',' | downcase | split: ',' | uniq | sort -%}
|
|
|
|
<div class="grid">
|
|
<div class="grid__item one-fifth lap-one-whole palm-one-whole">
|
|
|
|
<div class="filter-button-group">
|
|
<div class="all">
|
|
<a href='#all' class="btn">All <span class="count">{{tot}}</span></a>
|
|
</div>
|
|
|
|
<div class="featured">
|
|
<a href='#featured' class="btn">Featured</a>
|
|
<a href='#works-with-home-assistant' class="btn">Partner brands</a>
|
|
</div>
|
|
|
|
<div class="version_select">
|
|
<label>By release</label>
|
|
<select id="versions" name="versions">
|
|
<option value="#"></option>
|
|
{%- for group in components_by_version -%}
|
|
<optgroup label="{{ group.label }} ({{group.new_components_count}})">
|
|
{%- for version in group.versions -%}
|
|
<option value="#version/{{ version.label }}">{{ version.label }} ({{ version.new_components_count }})
|
|
</option>
|
|
{%- endfor -%}
|
|
</optgroup>
|
|
{%- endfor -%}
|
|
</select>
|
|
</div>
|
|
|
|
<div class="category_list">
|
|
<label>By Categories</label>
|
|
{%- for category in categories -%}
|
|
{%- assign category_name = "" -%}
|
|
{%- assign components_count = 0 -%}
|
|
{%- for comp in components -%}
|
|
{%- assign comp_categories = comp.ha_category | join: ',' | downcase -%}
|
|
{%- if comp_categories contains category -%}
|
|
{%- if category_name == "" -%}
|
|
{%- for cat in comp.ha_category -%}
|
|
{%- assign lower_cat = cat | downcase -%}
|
|
{%- if lower_cat == category -%}
|
|
{%- assign category_name = cat -%}
|
|
{%- endif -%}
|
|
{%- endfor -%}
|
|
{%- endif -%}
|
|
{%- assign components_count = components_count | plus: 1 -%}
|
|
{%- endif -%}
|
|
{%- endfor -%}
|
|
{%- if category != 'other' and components_count != 0 -%}
|
|
{%- if category_name == "" -%}
|
|
{%- assign category_name = category | capitalize -%}
|
|
{%- endif -%}
|
|
<a href='#{{ category_name | slugify }}' class="btn" onclick="document.querySelector('.page-content').scrollTop = 0">{{ category_name }} <span class="count">{{ components_count }}</span></a>
|
|
{%- endif -%}
|
|
{%- endfor -%}
|
|
<a href='#other' class="btn" onclick="document.querySelector('.page-content').scrollTop = 0">Other <span class="count">{{ components | where: 'ha_category', 'Other' | size }}</span></a>
|
|
</div>
|
|
|
|
<div class="category_select">
|
|
<label>By Categories</label>
|
|
<select id="categories" name="categories">
|
|
<option value="#"></option>
|
|
{%- for category in categories -%}
|
|
{%- assign category_name = "" -%}
|
|
{%- assign components_count = 0 -%}
|
|
{%- for comp in components -%}
|
|
{%- assign comp_categories = comp.ha_category | join: ',' | downcase -%}
|
|
{%- if comp_categories contains category -%}
|
|
{%- if category_name == "" -%}
|
|
{%- for cat in comp.ha_category -%}
|
|
{%- assign lower_cat = cat | downcase -%}
|
|
{%- if lower_cat == category -%}
|
|
{%- assign category_name = cat -%}
|
|
{%- endif -%}
|
|
{%- endfor -%}
|
|
{%- endif -%}
|
|
{%- assign components_count = components_count | plus: 1 -%}
|
|
{%- endif -%}
|
|
{%- endfor -%}
|
|
{%- if category != 'other' and components_count != 0 -%}
|
|
{%- if category_name == "" -%}
|
|
{%- assign category_name = category | capitalize -%}
|
|
{%- endif -%}
|
|
<option value="#{{ category_name | slugify }}">{{ category_name }} ({{ components_count }})
|
|
</option>
|
|
{%- endif -%}
|
|
{%- endfor -%}
|
|
<option value="#other">Other ({{ components | where: 'ha_category', 'Other' | size }})
|
|
</option>
|
|
</select>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
<div class="grid__item four-fifths lap-one-whole palm-one-whole">
|
|
<div class="component-search">
|
|
<form onsubmit="event.preventDefault(); return false">
|
|
<input type="text" name="search" id="search" class="search text-input" placeholder="Search integrations..." autofocus />
|
|
</form>
|
|
</div>
|
|
<div class="hass-option-cards" id="componentContainer"> </div>
|
|
</div>
|
|
</div>
|
|
|
|
<script type="text/javascript">
|
|
// This object contains all components we have
|
|
const allComponents = [
|
|
{%- for component in components -%}
|
|
{%- if component.ha_category -%}
|
|
{%- assign sliced_version = component.ha_release | split: '.' -%}
|
|
{%- assign minor_version = sliced_version[1]|plus: 0 -%}
|
|
{%- assign major_version = sliced_version[0]|plus: 0 -%}
|
|
{% assign categories = "" | split: ',' %}
|
|
{%- for ha_category in component.ha_category -%}
|
|
{% capture category %}"{{ ha_category | slugify | downcase }}"{% endcapture %}
|
|
{% assign categories = categories | push: category %}
|
|
{%- endfor -%}
|
|
{url:"{{ component.url }}", title:"{{component.title}}", cat: [{{categories|join: ","}}], featured: {% if component.featured %}true{% else %}false{% endif %}, v: "{{major_version}}.{{minor_version}}", logo: "{{component.logo}}", domain: "{{component.ha_domain}}", ha_brand: "{{component.ha_brand}}", wwha: {% if component.works_with %}true{% else %}false{% endif %}},
|
|
{% endif -%}
|
|
{%- endfor -%}
|
|
false
|
|
];
|
|
allComponents.pop(); // remove placeholder element at the end
|
|
</script>
|
|
|
|
<script type="text/javascript">
|
|
(function () {
|
|
const SEARCH_PREFIX = '#search/';
|
|
const VERSION_PREFIX = '#version/';
|
|
|
|
const searchInputEl = document.querySelector('.component-search input');
|
|
const componentContainerEl = document.querySelector('#componentContainer');
|
|
|
|
function init() {
|
|
// do the lowerCase transformation once
|
|
for (let component of allComponents) {
|
|
const title = component.title.toLowerCase();
|
|
const domain = component.domain;
|
|
const titleNormalized = title
|
|
.normalize("NFD")
|
|
.replace(/[\u0300-\u036f]/g, "");
|
|
const titleDedashed = title.replace(/[-_]/g, " ");
|
|
const titleNormalizedDedashed = titleNormalized.replace(/[-_]/g, " ");
|
|
|
|
component.titleLC = title;
|
|
component.search = `${title} ${titleNormalized} ${titleDedashed} ${titleNormalizedDedashed} ${domain}`;
|
|
}
|
|
|
|
// sort the components alphabetically
|
|
allComponents.sort((a, b) => a.titleLC.localeCompare(b.titleLC));
|
|
|
|
if (location.hash !== '' && location.hash.indexOf(SEARCH_PREFIX) === 0) {
|
|
// set default value in search from URL
|
|
const search = decodeURIComponent(location.hash).substring(SEARCH_PREFIX.length);
|
|
searchInputEl.value = search;
|
|
}
|
|
|
|
// add focus to the search field
|
|
setTimeout(() => searchInputEl.focus(), 1);
|
|
}
|
|
init();
|
|
|
|
/**
|
|
* filter all components, based on the location's hash and render them into the component box
|
|
*/
|
|
function applyFilter() {
|
|
const hash = location.hash || '';
|
|
let components = [];
|
|
|
|
// fade-out css effect on the old elements. This is actually not visible on fast browsers
|
|
componentContainerEl.classList.add('remove-items');
|
|
|
|
if (hash.indexOf('#search/') === -1) {
|
|
// reset search box when not searching
|
|
searchInputEl.value = null;
|
|
}
|
|
|
|
if (hash === '#all') {
|
|
// shortcut: no need to filter
|
|
components = allComponents;
|
|
} else {
|
|
let filter, search;
|
|
if (hash.indexOf(SEARCH_PREFIX) === 0) {
|
|
// search through title and category
|
|
search = decodeURIComponent(hash).substring(SEARCH_PREFIX.length).toLowerCase();
|
|
filter = comp =>
|
|
comp.search.indexOf(search) !== -1 ||
|
|
comp.cat.find((c) => c.includes("#")) != undefined;
|
|
|
|
} else if (hash === '#featured' || hash === '') {
|
|
// only show those with featured = true
|
|
filter = comp => comp.featured;
|
|
|
|
} else if (hash === '#works-with-home-assistant') {
|
|
// only show those partners of the Works with Home Assistant program
|
|
filter = comp => comp.wwha;
|
|
|
|
} else if (hash.indexOf(VERSION_PREFIX) === 0) {
|
|
// compare against a version
|
|
search = decodeURIComponent(hash).substring(VERSION_PREFIX.length).toLowerCase();
|
|
// compare version string against version js
|
|
filter = comp => comp.v === search;
|
|
|
|
} else {
|
|
// regular filter categories
|
|
search = hash.substring(1);
|
|
filter = comp => comp.cat.includes(search);
|
|
}
|
|
|
|
// filter all components using the filter function
|
|
components = allComponents.filter(filter);
|
|
}
|
|
|
|
let rendered;
|
|
if (components.length > 0) {
|
|
// Note: Assumes all data has already been sanitized
|
|
rendered = components.map(component => `
|
|
<a href="${component.url}" class="option-card">
|
|
<div class="img-container">${buildImageEl(component)}</div>
|
|
<div class='title'>${component.title}</div>
|
|
</a>
|
|
`).join('\n');
|
|
} else {
|
|
rendered = '<div class="alert alert-note"><p class="alert-content">Nothing found!</p></div>';
|
|
}
|
|
|
|
// set active class on active menu item
|
|
document.querySelector('.filter-button-group a.active')?.classList?.remove?.('active');
|
|
document.querySelector(`.filter-button-group a[href="${hash}"]`)?.classList?.add?.('active');
|
|
if (hash === "") {
|
|
document.querySelector('.filter-button-group a[href*="#featured"]').classList.add('active');
|
|
}
|
|
|
|
// remove previous elements and css classes, add the new stuff and then trigger the fade-in css animation
|
|
componentContainerEl.innerHTML = '';
|
|
componentContainerEl.classList.remove('show-items');
|
|
componentContainerEl.classList.remove('remove-items');
|
|
componentContainerEl.innerHTML = rendered;
|
|
componentContainerEl.classList.add('show-items');
|
|
}
|
|
|
|
function buildImageEl(component) {
|
|
const urlBase = [
|
|
'https://brands.home-assistant.io',
|
|
component.ha_brand ? 'brands' : '_',
|
|
component.domain,
|
|
'icon'
|
|
].join('/');
|
|
|
|
return `<img src="${urlBase}.png" srcset="${urlBase}@2x.png 2x" loading="lazy">`;
|
|
}
|
|
|
|
// update view by filter selection
|
|
for (let filterLink of document.querySelectorAll('.filter-button-group a')) {
|
|
filterLink.addEventListener('click', () => {
|
|
history.pushState('', '', filterLink.getAttribute('href'));
|
|
applyFilter();
|
|
return false;
|
|
});
|
|
}
|
|
|
|
// update view on select change
|
|
const versionsEl = document.getElementById('versions');
|
|
versionsEl.addEventListener('change', () => {
|
|
history.pushState('', '', versionsEl.value);
|
|
applyFilter();
|
|
});
|
|
|
|
const categoriesEl = document.getElementById('categories');
|
|
categoriesEl.addEventListener('change', () => {
|
|
history.pushState('', '', categoriesEl.value);
|
|
applyFilter();
|
|
});
|
|
|
|
/**
|
|
* Simple debounce implementation
|
|
*/
|
|
function debounce(func, wait) {
|
|
let timeout;
|
|
return () => {
|
|
clearTimeout(timeout);
|
|
timeout = setTimeout(func, wait);
|
|
};
|
|
};
|
|
|
|
// update view by search text
|
|
searchInputEl.addEventListener('keyup', debounce(() => {
|
|
const text = searchInputEl.value
|
|
// sanitize input
|
|
.replace(/[(\?|\&\{\}\(\))]/gi, '')
|
|
.trim();
|
|
|
|
let newHash = typeof text === "string" && text.length >= 1
|
|
? SEARCH_PREFIX + text
|
|
: '#all';
|
|
// Only apply filter if hash has changed
|
|
if (newHash !== window.location.hash) {
|
|
history.pushState('', '', newHash);
|
|
applyFilter();
|
|
}
|
|
}, 500));
|
|
|
|
window.addEventListener('hashchange', applyFilter);
|
|
applyFilter();
|
|
})();
|
|
</script>
|
|
|
|
<noscript>
|
|
<ul>
|
|
{%- for component in components -%}
|
|
{%- if component.ha_category -%}
|
|
<li><a href='{{ component.url }}'>{{ component.title }}</a></li>
|
|
{%- endif -%}
|
|
{%- endfor -%}
|
|
</ul>
|
|
</noscript>
|