mirror of
https://github.com/danieldemus/openhab-core.git
synced 2026-07-29 12:34:22 +02:00
HomeBuilder - Initial contribution (#172)
openHAB Home Builder - home structure generator Also-by: Thomas Dietrich <thomas.dietrich@tu-ilmenau.de> (github: ThomDietrich) Also-by: Demyan Rogozhin <dmitriy.rogozhin@gmail.com> (github: demyanrogozhin) Signed-off-by: Kuba Wolanin <hi@kubawolanin.com> (github: kubawolanin)
This commit is contained in:
committed by
Kai Kreuzer
parent
714bc1d2a8
commit
3d811ecede
@@ -4,3 +4,5 @@
|
||||
/*features*/*/src/main/history/
|
||||
maven-metadata-local.xml
|
||||
dependency-reduced-pom.xml
|
||||
**/node_modules/
|
||||
npm-debug.log
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="src" path="src/main/java"/>
|
||||
<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
|
||||
<classpathentry kind="output" path="target/classes"/>
|
||||
</classpath>
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"commonjs": true,
|
||||
"es6": true,
|
||||
"node": true
|
||||
},
|
||||
"parserOptions": {
|
||||
"ecmaFeatures": {
|
||||
"jsx": false
|
||||
},
|
||||
"sourceType": "module"
|
||||
},
|
||||
"rules": {
|
||||
"no-cond-assign": 1,
|
||||
"no-debugger": 1,
|
||||
"no-dupe-args": 1,
|
||||
"no-dupe-keys": 1,
|
||||
"no-duplicate-case": 1,
|
||||
"no-unreachable": 1,
|
||||
"use-isnan": 1,
|
||||
"valid-typeof": 1,
|
||||
"block-scoped-var": 1,
|
||||
"curly": [1,"all"],
|
||||
"default-case": 1,
|
||||
"eqeqeq": [1,"smart"],
|
||||
"guard-for-in": 1,
|
||||
"no-alert": 1,
|
||||
"no-else-return": 1,
|
||||
"no-empty-label": 1,
|
||||
"no-eval": 1,
|
||||
"no-labels": 1,
|
||||
"radix": 1,
|
||||
"no-undef": 1,
|
||||
"no-unused-vars": 1,
|
||||
"no-const-assign": 1,
|
||||
"no-this-before-super": 1,
|
||||
"constructor-super": 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
* text=auto
|
||||
@@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>org.openhab.ui.homebuilder</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.pde.ManifestBuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.pde.SchemaBuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.pde.ds.core.builder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.pde.PluginNature</nature>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
||||
@@ -0,0 +1,22 @@
|
||||
Manifest-Version: 1.0
|
||||
Bundle-ManifestVersion: 2
|
||||
Bundle-Name: HomeBuilder User Interface
|
||||
Bundle-SymbolicName: org.openhab.ui.homebuilder
|
||||
Bundle-Version: 2.2.0.qualifier
|
||||
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
|
||||
Service-Component: OSGI-INF/*.xml
|
||||
Import-Package: com.google.gson,
|
||||
com.google.gson.annotations,
|
||||
javax.servlet,
|
||||
javax.servlet.http,
|
||||
javax.ws.rs,
|
||||
javax.ws.rs.core,
|
||||
org.eclipse.smarthome.io.rest,
|
||||
org.openhab.ui.dashboard,
|
||||
org.osgi.framework,
|
||||
org.osgi.service.cm,
|
||||
org.osgi.service.http,
|
||||
org.osgi.service.component.annotations;resolution:=optional,
|
||||
org.slf4j
|
||||
Bundle-Vendor: openHAB
|
||||
Bundle-ActivationPolicy: lazy
|
||||
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
|
||||
Copyright (c) 2015-2017 by the respective copyright holders.
|
||||
|
||||
All rights reserved. This program and the accompanying materials
|
||||
are made available under the terms of the Eclipse Public License v1.0
|
||||
which accompanies this distribution, and is available at
|
||||
http://www.eclipse.org/legal/epl-v10.html
|
||||
|
||||
-->
|
||||
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="activate" deactivate="deactivate" immediate="true" name="org.openhab.homebuilder">
|
||||
<implementation class="org.openhab.ui.homebuilder.internal.HomeBuilderDashboardTile"/>
|
||||
<reference bind="setHttpService" cardinality="1..1" interface="org.osgi.service.http.HttpService" name="HttpService" policy="static" unbind="unsetHttpService"/>
|
||||
<service>
|
||||
<provide interface="org.openhab.ui.dashboard.DashboardTile"/>
|
||||
</service>
|
||||
</scr:component>
|
||||
@@ -0,0 +1,32 @@
|
||||
# Home Builder
|
||||
|
||||
> Boilerplate for the [Items](http://docs.openhab.org/configuration/items.html), [sitemap](http://docs.openhab.org/configuration/sitemaps.html) files and [HABPanel](http://docs.openhab.org/addons/uis/habpanel/readme.html) dashboard.
|
||||
|
||||
## Usage
|
||||
|
||||
See [USAGE.md](USAGE.md) file for reference.
|
||||
|
||||
## Development
|
||||
|
||||
HomeBuilder UI is an app based on [Vue.js](http://vuejs.org) framework.
|
||||
In order to setup the development environment simply run:
|
||||
|
||||
``` bash
|
||||
cd web
|
||||
npm install
|
||||
npm run dev
|
||||
```
|
||||
|
||||
The first command will install all necessary dependencies (including Webpack build system).
|
||||
`npm run dev`, however, will serve the build with hot reload at `http://localhost:8080`.
|
||||
|
||||
It's recommended to debug the app on Chrome or Firefox with [Vue.js devtools](https://chrome.google.com/webstore/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd) extension.
|
||||
|
||||
When you're done with your changes and would like to test HomeBuilder on a real environment, run the following command:
|
||||
|
||||
``` bash
|
||||
# build for production with minification
|
||||
npm run build
|
||||
```
|
||||
|
||||
It will build the app inside `/web/dist/` folder with source map included.
|
||||
@@ -0,0 +1,18 @@
|
||||
# Home Builder
|
||||
|
||||
> Boilerplate for the [Items](http://docs.openhab.org/configuration/items.html), [sitemap](http://docs.openhab.org/configuration/sitemaps.html) files and [HABPanel](http://docs.openhab.org/addons/uis/habpanel/readme.html) dashboard.
|
||||
|
||||
## Items
|
||||
|
||||
The tool allows you to generate openHAB Items for your home structure.
|
||||
You can choose to generate textual `*.items` file content or construct a request directly to the REST API that'll create the items for you.
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
- Classifies the objects within each room and creates groups for them
|
||||
- Optionally adds icons from [Classic Icon Set](http://docs.openhab.org/addons/iconsets/classic/readme.html) to the items
|
||||
- Optionally adds Tags to the items - convenient for [HomeKit](http://docs.openhab.org/addons/io/homekit/readme.html)/[Hue Emulation](http://docs.openhab.org/addons/io/hueemulation/readme.html#device-tagging) add-ons users
|
||||
- Automatically aligns the items vertically
|
||||
- Generates a [Sitemap](http://docs.openhab.org/configuration/sitemaps.html) file
|
||||
- Generates a set of HABPanel Dashboards corresponding with the Items
|
||||
@@ -0,0 +1,68 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
||||
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"/>
|
||||
<title>About</title>
|
||||
</head>
|
||||
<body lang="EN-US">
|
||||
<h2>About This Content</h2>
|
||||
|
||||
<p><<em>August 10, 2017</em>></p>
|
||||
<h3>License</h3>
|
||||
|
||||
<p>The openHAB community makes available all content in this plug-in ("Content"). Unless otherwise
|
||||
indicated below, the Content is provided to you under the terms and conditions of the
|
||||
Eclipse Public License Version 1.0 ("EPL"). A copy of the EPL is available
|
||||
at <a href="http://www.eclipse.org/legal/epl-v10.html">http://www.eclipse.org/legal/epl-v10.html</a>.
|
||||
For purposes of the EPL, "Program" will mean the Content.</p>
|
||||
|
||||
<p>If you did not receive this Content directly from the openHAB community, the Content is
|
||||
being redistributed by another party ("Redistributor") and different terms and conditions may
|
||||
apply to your use of any object code in the Content. Check the Redistributor's license that was
|
||||
provided with the Content. If no such license exists, contact the Redistributor. Unless otherwise
|
||||
indicated below, the terms and conditions of the EPL still apply to any source code in the Content
|
||||
and such source code may be obtained at <a href="http://www.openhab.org/">openhab.org</a>.</p>
|
||||
|
||||
|
||||
<h3>Third Party Content</h3>
|
||||
<p>The Content includes items that have been sourced from third parties as set out below. If you
|
||||
did not receive this Content directly from the openHAB community, the following is provided
|
||||
for informational purposes only, and you should look to the Redistributor's license for
|
||||
terms and conditions of use.</p>
|
||||
|
||||
<p><em>
|
||||
<strong>ascii-table</strong> <br/><br/>
|
||||
<a href="https://www.npmjs.com/package/ascii-table">ascii-table</a> - a subject to the <a href="https://spdx.org/licenses/MIT.html">MIT License</a>
|
||||
</em></p>
|
||||
<p><em>
|
||||
<strong>bootstrap-vue</strong> <br/><br/>
|
||||
<a href="https://www.npmjs.com/package/bootstrap-vue">bootstrap-vue</a> - a subject to the <a href="https://spdx.org/licenses/MIT.html">MIT License</a>
|
||||
</em></p>
|
||||
<p><em>
|
||||
<strong>lodash</strong> <br/><br/>
|
||||
<a href="https://www.npmjs.com/package/lodash">lodash</a> - a subject to the <a href="https://spdx.org/licenses/MIT.html">MIT License</a>
|
||||
</em></p>
|
||||
<p><em>
|
||||
<strong>underscore.string</strong> <br/><br/>
|
||||
<a href="https://www.npmjs.com/package/underscore.string">underscore.string</a> - a subject to the <a href="https://spdx.org/licenses/MIT.html">MIT License</a>
|
||||
</em></p>
|
||||
<p><em>
|
||||
<strong>vue</strong> <br/><br/>
|
||||
<a href="https://www.npmjs.com/package/vue">vue</a> - a subject to the <a href="https://spdx.org/licenses/MIT.html">MIT License</a>
|
||||
</em></p>
|
||||
<p><em>
|
||||
<strong>vue-form-generator</strong> <br/><br/>
|
||||
<a href="https://www.npmjs.com/package/vue-form-generator">vue-form-generator</a> - a subject to the <a href="https://spdx.org/licenses/MIT.html">MIT License</a>
|
||||
</em></p>
|
||||
<p><em>
|
||||
<strong>vue-i18n</strong> <br/><br/>
|
||||
<a href="https://www.npmjs.com/package/vue-i18n">vue-i18n</a> - a subject to the <a href="https://spdx.org/licenses/MIT.html">MIT License</a>
|
||||
</em></p>
|
||||
<p><em>
|
||||
<strong>vue-multiselect</strong> <br/><br/>
|
||||
<a href="https://www.npmjs.com/package/vue-multiselect">vue-multiselect</a> - a subject to the <a href="https://spdx.org/licenses/MIT.html">MIT License</a>
|
||||
</em></p>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,14 @@
|
||||
bin.includes = META-INF/,\
|
||||
.,\
|
||||
about.html,\
|
||||
OSGI-INF/,\
|
||||
web/
|
||||
output = target/classes/
|
||||
source.. = src/main/java/
|
||||
bin.excludes = web/node_modules/,\
|
||||
web/src/,\
|
||||
web/dist/build.js.map,\
|
||||
web/webpack.config.js,\
|
||||
web/.babelrc,\
|
||||
web/.eslintrc.json,\
|
||||
web/package.json
|
||||
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
|
||||
<parent>
|
||||
<groupId>org.openhab.core</groupId>
|
||||
<artifactId>pom-bundles</artifactId>
|
||||
<version>2.2.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>org.openhab.ui.homebuilder</artifactId>
|
||||
|
||||
<name>openHAB HomeBuilder UI Dashboard Integration</name>
|
||||
<packaging>eclipse-plugin</packaging>
|
||||
|
||||
</project>
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* Copyright (c) 2015-2017 by the respective copyright holders.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.openhab.ui.homebuilder.internal;
|
||||
|
||||
import org.openhab.ui.dashboard.DashboardTile;
|
||||
import org.osgi.service.http.HttpService;
|
||||
import org.osgi.service.http.NamespaceException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The dashboard tile and resource registering for Home Builder
|
||||
*
|
||||
* @author Kuba Wolanin - Initial contribution
|
||||
*
|
||||
*/
|
||||
public class HomeBuilderDashboardTile implements DashboardTile {
|
||||
|
||||
public static final String HOMEBUILDER_ALIAS = "/homebuilder";
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(HomeBuilderDashboardTile.class);
|
||||
|
||||
protected HttpService httpService;
|
||||
|
||||
protected void activate() {
|
||||
try {
|
||||
httpService.registerResources(HOMEBUILDER_ALIAS, "web", null);
|
||||
logger.info("Started Home Builder at {}", HOMEBUILDER_ALIAS);
|
||||
} catch (NamespaceException e) {
|
||||
logger.error("Error during Home Builder startup: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
protected void deactivate() {
|
||||
httpService.unregister(HOMEBUILDER_ALIAS);
|
||||
logger.info("Stopped Home Builder");
|
||||
}
|
||||
|
||||
protected void setHttpService(HttpService httpService) {
|
||||
this.httpService = httpService;
|
||||
}
|
||||
|
||||
protected void unsetHttpService(HttpService httpService) {
|
||||
this.httpService = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Home Builder";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUrl() {
|
||||
return "../homebuilder/index.html";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getOverlay() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getImageUrl() {
|
||||
return "../homebuilder/tile.png";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"presets": [
|
||||
["env", { "modules": false }]
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
node_modules/
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,65 @@
|
||||
|
||||
{
|
||||
"GroundFloor": "Erdgeschoss",
|
||||
"FirstFloor": "Erster Stock",
|
||||
"SecondFloor": "Zweiter Stock",
|
||||
"ThirdFloor": "Dritter Stock",
|
||||
"FourthFloor": "Vierter Stock",
|
||||
|
||||
"Attic": "Dachgeschoss",
|
||||
"Balcony": "Balkon",
|
||||
"Backyard": "Hinterhof",
|
||||
"Basement": "Keller",
|
||||
"Bathroom": "Badezimmer",
|
||||
"Bedroom": "Schlafzimmer",
|
||||
"Boiler": "Heizungsraum",
|
||||
"Wardrobe": "Garderobe",
|
||||
"Cellar": "Keller",
|
||||
"Corridor": "Gang",
|
||||
"Deck": "Deck",
|
||||
"Dining": "Esszimmer",
|
||||
"Downstairs": "Unten",
|
||||
"Driveway": "Auffahrt",
|
||||
"Entryway": "Zufahrt",
|
||||
"FamilyRoom": "Familienzimmer",
|
||||
"FrontYard": "Vorgarten",
|
||||
"Garage": "Garage",
|
||||
"GuestHouse": "Gästehaus",
|
||||
"GuestRoom": "Gästezimmer",
|
||||
"Hallway": "Flur",
|
||||
"HomeCinema": "Heimkino",
|
||||
"KidsRoom": "Kinderzimmer",
|
||||
"Kitchen": "Küche",
|
||||
"LaundryRoom": "Waschküche",
|
||||
"Library": "Bibliothek",
|
||||
"LivingRoom": "Wohnzimmer",
|
||||
"LivingDining": "Wohn-Esszimmer",
|
||||
"Loft": "Loft",
|
||||
"Lounge": "Salon",
|
||||
"MasterBedroom": "Hauptschlafzimmer",
|
||||
"NannyRoom": "Kindermädchenzimmer",
|
||||
"Office": "Büro",
|
||||
"Outside": "Draußen",
|
||||
"Patio": "Patio",
|
||||
"Porch": "Veranda",
|
||||
"Stairwell": "Treppenhaus",
|
||||
"StorageRoom": "Lagerraum",
|
||||
"Studio": "Studio",
|
||||
"Shed": "Schuppen",
|
||||
"Toilet": "Toilette",
|
||||
"Terrace": "Terrasse",
|
||||
"Upstairs": "Oben",
|
||||
|
||||
"Light": "Licht",
|
||||
"Window": "Fenster",
|
||||
"Door": "Tür",
|
||||
"Motion": "Bewegungsmelder",
|
||||
"Power": "Steckdose",
|
||||
"Shutter": "Rolladen",
|
||||
"Blind": "Jalousie",
|
||||
"Fan": "Ventilator",
|
||||
"AirCon": "Klimaanlage",
|
||||
"Heating": "Heizung",
|
||||
"Temperature": "Temperatur",
|
||||
"Humidity": "Luftfeuchtigkeit"
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
{
|
||||
"GroundFloor": "Ground Floor",
|
||||
"FirstFloor": "First Floor",
|
||||
"SecondFloor": "Second Floor",
|
||||
"ThirdFloor": "Third Floor",
|
||||
"FourthFloor": "Fourth Floor",
|
||||
|
||||
"Attic": "Attic",
|
||||
"Balcony": "Balcony",
|
||||
"Backyard": "Backyard",
|
||||
"Basement": "Basement",
|
||||
"Bathroom": "Bathroom",
|
||||
"Bedroom": "Bedroom",
|
||||
"Boiler": "Boiler Room",
|
||||
"Wardrobe": "Wardrobe",
|
||||
"Cellar": "Cellar",
|
||||
"Corridor": "Corridor",
|
||||
"Deck": "Deck",
|
||||
"Dining": "Dining",
|
||||
"Downstairs": "Downstairs",
|
||||
"Driveway": "Driveway",
|
||||
"Entryway": "Entryway",
|
||||
"FamilyRoom": "Family Room",
|
||||
"FrontYard": "Front Yard",
|
||||
"Garage": "Garage",
|
||||
"GuestHouse": "Guest House",
|
||||
"GuestRoom": "Guest Room",
|
||||
"Hallway": "Hallway",
|
||||
"HomeCinema": "Home Cinema",
|
||||
"KidsRoom": "Kids Room",
|
||||
"Kitchen": "Kitchen",
|
||||
"LaundryRoom": "Laundry Room",
|
||||
"Library": "Library",
|
||||
"LivingRoom": "Living Room",
|
||||
"LivingDining": "Living & Dining Room",
|
||||
"Loft": "Loft",
|
||||
"Lounge": "Lounge",
|
||||
"MasterBedroom": "Master Bedroom",
|
||||
"NannyRoom": "Nanny Room",
|
||||
"Office": "Office",
|
||||
"Outside": "Outside",
|
||||
"Patio": "Patio",
|
||||
"Porch": "Porch",
|
||||
"Stairwell": "Stairwell",
|
||||
"StorageRoom": "Storage Room",
|
||||
"Studio": "Studio",
|
||||
"Shed": "Shed",
|
||||
"Toilet": "Toilet",
|
||||
"Terrace": "Terrace",
|
||||
"Upstairs": "Upstairs",
|
||||
|
||||
"Light": "Light",
|
||||
"Window": "Window",
|
||||
"Door": "Door",
|
||||
"Motion": "Motion Sensor",
|
||||
"Power": "Power Outlet",
|
||||
"Shutter": "Roller Shutter",
|
||||
"Blind": "Blind",
|
||||
"Fan": "Fan",
|
||||
"AirCon": "Air Conditioner",
|
||||
"Heating": "Heating",
|
||||
"Temperature": "Temperature",
|
||||
"Humidity": "Humidity"
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
|
||||
{
|
||||
"GroundFloor": "Planta Baja",
|
||||
"FirstFloor": "Primera Planta",
|
||||
"SecondFloor": "Segunda Planta",
|
||||
"ThirdFloor": "Tercera Planta",
|
||||
"FourthFloor": "Cuarta Planta",
|
||||
|
||||
"Attic": "Ático",
|
||||
"Balcony": "Terraza",
|
||||
"Backyard": "Patio trasero",
|
||||
"Basement": "Sótano",
|
||||
"Bathroom": "Baño",
|
||||
"Bedroom": "Dormitorio",
|
||||
"Boiler": "Cuarto de calderas",
|
||||
"Wardrobe": "Vestidor",
|
||||
"Cellar": "Bodega",
|
||||
"Corridor": "Pasillo",
|
||||
"Deck": "Cubierta",
|
||||
"Dining": "Comedor",
|
||||
"Downstairs": "Planta baja",
|
||||
"Driveway": "Entrada de coches",
|
||||
"Entryway": "Recibidor",
|
||||
"FamilyRoom": "Habitación familiar",
|
||||
"FrontYard": "Jardín",
|
||||
"Garage": "Garaje",
|
||||
"GuestHouse": "Casa de invitados",
|
||||
"GuestRoom": "Habitación de invitados",
|
||||
"Hallway": "Pasillo",
|
||||
"HomeCinema": "Cine en casa",
|
||||
"KidsRoom": "Habitación de los niños",
|
||||
"Kitchen": "Cocina",
|
||||
"LaundryRoom": "Despensa",
|
||||
"Library": "Biblioteca",
|
||||
"LivingRoom": "Salón",
|
||||
"LivingDining": "Cocina americana",
|
||||
"Loft": "Desván",
|
||||
"Lounge": "Sala",
|
||||
"MasterBedroom": "Dormitorio principal",
|
||||
"NannyRoom": "Habitación de la niñera",
|
||||
"Office": "Oficina",
|
||||
"Outside": "Fuera",
|
||||
"Patio": "Patio",
|
||||
"Porch": "Porche",
|
||||
"Stairwell": "Hueco de la escalera",
|
||||
"StorageRoom": "Despensa",
|
||||
"Studio": "Estudio",
|
||||
"Shed": "Cobertizo",
|
||||
"Toilet": "Baño",
|
||||
"Terrace": "Terraza",
|
||||
"Upstairs": "Piso de arriba",
|
||||
|
||||
"Light": "Luz",
|
||||
"Window": "Ventana",
|
||||
"Door": "Puerta",
|
||||
"Motion": "Sensor de movimiento",
|
||||
"Power": "Enchufe",
|
||||
"Shutter": "Persiana",
|
||||
"Blind": "Cortina",
|
||||
"Fan": "Ventilador",
|
||||
"AirCon": "Aire Acondicionado",
|
||||
"Heating": "Calefacción",
|
||||
"Temperature": "Temperatura",
|
||||
"Humidity": "Humedad"
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
{
|
||||
"GroundFloor": "Rez-de-chaussée",
|
||||
"FirstFloor": "Premier étage",
|
||||
"SecondFloor": "Deuxième étage",
|
||||
"ThirdFloor": "Troisième étage",
|
||||
"FourthFloor": "Quatrième étage",
|
||||
|
||||
"Attic": "Grenier",
|
||||
"Balcony": "Balcon",
|
||||
"Backyard": "Jardin",
|
||||
"Basement": "Sous-sol",
|
||||
"Bathroom": "Salle de bain",
|
||||
"Bedroom": "Chambre",
|
||||
"Boiler": "Chaudière",
|
||||
"Wardrobe": "Armoire",
|
||||
"Cellar": "Sous-sol",
|
||||
"Corridor": "Couloir",
|
||||
|
||||
"Deck": "Terrasse en bois",
|
||||
"Dining": "Salle à manger",
|
||||
"Downstairs": "En bas",
|
||||
"Driveway": "Allée",
|
||||
"Entryway": "Entrée",
|
||||
"FamilyRoom": "Salon",
|
||||
"FrontYard": "Jardin",
|
||||
"Garage": "Garage",
|
||||
"GuestHouse": "Maison d’hôte",
|
||||
"GuestRoom": "Chambre d’amis",
|
||||
"Hallway": "Couloir",
|
||||
"HomeCinema": "Home cinéma",
|
||||
"KidsRoom": "Salle de jeux",
|
||||
"Kitchen": "Cuisine",
|
||||
"LaundryRoom": "Buanderie",
|
||||
"Library": "Bibliothèque",
|
||||
"LivingRoom": "Salon",
|
||||
"LivingDining": "Cuisine à l’américaine",
|
||||
"Loft": "Loft",
|
||||
"Lounge": "Salon",
|
||||
"MasterBedroom": "Chambre parentale",
|
||||
"NannyRoom": "Chambre de bonne",
|
||||
"Office": "Bureau",
|
||||
"Outside": "Dehors",
|
||||
"Patio": "Patio",
|
||||
"Porch": "Véranda",
|
||||
"Stairwell": "Cage d‘escalier",
|
||||
"StorageRoom": "Débarras",
|
||||
"Studio": "Studio",
|
||||
"Shed": "Cabane",
|
||||
"Toilet": "Toilettes",
|
||||
"Terrace": "Terrasse",
|
||||
"Upstairs": "En haut",
|
||||
|
||||
"Light": "Lumière",
|
||||
"Window": "Fenêtre",
|
||||
"Door": "Porte",
|
||||
"Motion": "Mouvement",
|
||||
"Power": "Courant",
|
||||
"Shutter": "Volet",
|
||||
"Blind": "Store",
|
||||
"Fan": "Ventilateur",
|
||||
"AirCon": "Air conditionné",
|
||||
"Heating": "Chauffage",
|
||||
"Temperature": "Temperature",
|
||||
"Humidity": "Humidité"
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
|
||||
{
|
||||
"GroundFloor": "Piano Terra",
|
||||
"FirstFloor": "Primo Piano",
|
||||
"SecondFloor": "Secondo Piano",
|
||||
"ThirdFloor": "Terzo Piano",
|
||||
"FourthFloor": "Quarto Piano",
|
||||
|
||||
"Attic": "Attico",
|
||||
"Balcony": "Balcone",
|
||||
"Backyard": "Cortile sul retro",
|
||||
"Basement": "Cantina",
|
||||
"Bathroom": "Stanza da Bagno",
|
||||
"Bedroom": "Camera da Letto",
|
||||
"Boiler": "Caldaia",
|
||||
"Wardrobe": "Armadio",
|
||||
"Cellar": "Scantinato",
|
||||
"Corridor": "Corridio",
|
||||
"Deck": "Deck",
|
||||
"Dining": "Sala da Pranzo",
|
||||
"Downstairs": "Piano di Sotto",
|
||||
"Driveway": "Vialetto",
|
||||
"Entryway": "Ingresso",
|
||||
"FamilyRoom": "Soggiorno",
|
||||
"FrontYard": "Giardino Frontale",
|
||||
"Garage": "Garage",
|
||||
"GuestHouse": "Casa per gli Ospiti",
|
||||
"GuestRoom": "Stanza per gli Ospiti",
|
||||
"Hallway": "Corridoio",
|
||||
"HomeCinema": "Home Cinema",
|
||||
"KidsRoom": "Stanza dei Bambini",
|
||||
"Kitchen": "Cucina",
|
||||
"LaundryRoom": "Lavanderia",
|
||||
"Library": "Biblioteca",
|
||||
"LivingRoom": "Sala",
|
||||
"LivingDining": "Cucina Open Space",
|
||||
"Loft": "Loft",
|
||||
"Lounge": "Lounge",
|
||||
"MasterBedroom": "Camera da letto principale",
|
||||
"NannyRoom": "Stanza della governante",
|
||||
"Office": "Ufficio",
|
||||
"Outside": "Esterno",
|
||||
"Patio": "Patio",
|
||||
"Porch": "Portico",
|
||||
"Stairwell": "Scalinata",
|
||||
"StorageRoom": "Dispensa",
|
||||
"Studio": "Studio",
|
||||
"Shed": "Capanno",
|
||||
"Toilet": "Bagno",
|
||||
"Terrace": "Terrazza",
|
||||
"Upstairs": "Piano di Sopra",
|
||||
|
||||
"Light": "Luce",
|
||||
"Window": "Finestra",
|
||||
"Door": "Porta",
|
||||
"Motion": "Sensore di movimento",
|
||||
"Power": "Elettricità",
|
||||
"Shutter": "Persiana",
|
||||
"Blind": "Tenda",
|
||||
"Fan": "Ventilatore",
|
||||
"AirCon": "Aria Condizionata",
|
||||
"Heating": "Riscaldamento",
|
||||
"Temperature": "Temperatura",
|
||||
"Humidity": "Umidità"
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
|
||||
{
|
||||
"GroundFloor": "1층",
|
||||
"FirstFloor": "2층",
|
||||
"SecondFloor": "3층",
|
||||
"ThirdFloor": "4층",
|
||||
"FourthFloor": "5층",
|
||||
|
||||
"Attic": "다락방",
|
||||
"Balcony": "발코니",
|
||||
"Backyard": "뒷마당",
|
||||
"Basement": "지하실",
|
||||
"Bathroom": "욕실",
|
||||
"Bedroom": "침실",
|
||||
"Boiler": "보일러실",
|
||||
"Wardrobe": "옷장",
|
||||
"Cellar": "지하실",
|
||||
"Corridor": "복도",
|
||||
"Deck": "갑판",
|
||||
"Dining": "식당",
|
||||
"Downstairs": "아래층",
|
||||
"Driveway": "입구",
|
||||
"Entryway": "정문",
|
||||
"FamilyRoom": "패밀리 룸",
|
||||
"FrontYard": "앞마당",
|
||||
"Garage": "차고",
|
||||
"GuestHouse": "게스트 하우스",
|
||||
"GuestRoom": "게스트실",
|
||||
"Hallway": "복도",
|
||||
"HomeCinema": "홈 시네마",
|
||||
"KidsRoom": "어린이방",
|
||||
"Kitchen": "부엌",
|
||||
"LaundryRoom": "세탁실",
|
||||
"Library": "도서관",
|
||||
"LivingRoom": "거실",
|
||||
"LivingDining": "거실",
|
||||
"Loft": "옥탑",
|
||||
"Lounge": "라운지",
|
||||
"MasterBedroom": "안방",
|
||||
"NannyRoom": "보모실",
|
||||
"Office": "사무실",
|
||||
"Outside": "외부",
|
||||
"Patio": "패티오",
|
||||
"Porch": "폴치",
|
||||
"Stairwell": "계단",
|
||||
"StorageRoom": "창고",
|
||||
"Studio": "스튜디오",
|
||||
"Shed": "창고",
|
||||
"Toilet": "화장실",
|
||||
"Terrace": "테라스",
|
||||
"Upstairs": "위층",
|
||||
|
||||
"Light": "라이트",
|
||||
"Window": "창",
|
||||
"Door": "문",
|
||||
"Motion": "모션 센서",
|
||||
"Power": "전원",
|
||||
"Shutter": "셔터",
|
||||
"Blind": "블라인드",
|
||||
"Fan": "팬",
|
||||
"AirCon": "에어컨",
|
||||
"Heating": "난방",
|
||||
"Temperature": "온동",
|
||||
"Humidity": "습도"
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
{
|
||||
"GroundFloor": "Parter",
|
||||
"FirstFloor": "Pierwsze Piętro",
|
||||
"SecondFloor": "Drugie Piętro",
|
||||
"ThirdFloor": "Trzecie Piętro",
|
||||
"FourthFloor": "Czwarte Piętro",
|
||||
|
||||
"Attic": "Poddasze",
|
||||
"Balcony": "Balkon",
|
||||
"Backyard": "Ogród",
|
||||
"Basement": "Piwnica",
|
||||
"Bathroom": "Łazienka",
|
||||
"Bedroom": "Sypialnia",
|
||||
"Boiler": "Kotłownia",
|
||||
"Wardrobe": "Garderoba",
|
||||
"Cellar": "Piwnica",
|
||||
"Corridor": "Korytarz",
|
||||
"Deck": "Pokład",
|
||||
"Dining": "Jadalnia",
|
||||
"Downstairs": "Na dole",
|
||||
"Driveway": "Podjazd",
|
||||
"Entryway": "Wejście",
|
||||
"FamilyRoom": "Pokój rodzinny",
|
||||
"FrontYard": "Ogródek",
|
||||
"Garage": "Garaż",
|
||||
"GuestHouse": "Dom gościnny",
|
||||
"GuestRoom": "Pokój gościnny",
|
||||
"Hallway": "Hol",
|
||||
"HomeCinema": "Kino Domowe",
|
||||
"KidsRoom": "Pokój Dziecięcy",
|
||||
"Kitchen": "Kuchnia",
|
||||
"LaundryRoom": "Pralnia",
|
||||
"Library": "Biblioteka",
|
||||
"LivingRoom": "Pokój Dzienny",
|
||||
"LivingDining": "Pokój Dzienny z Jadalnią",
|
||||
"Loft": "Loft",
|
||||
"Lounge": "Loża",
|
||||
"MasterBedroom": "Główna Sypialnia",
|
||||
"NannyRoom": "Pokój Niani",
|
||||
"Office": "Biuro",
|
||||
"Outside": "Posesja",
|
||||
"Patio": "Patio",
|
||||
"Porch": "Ganek",
|
||||
"Stairwell": "Klatka Schodowa",
|
||||
"StorageRoom": "Przechowalnia",
|
||||
"Studio": "Studio",
|
||||
"Shed": "Szopa",
|
||||
"Toilet": "Toaleta",
|
||||
"Terrace": "Taras",
|
||||
"Upstairs": "Na górze",
|
||||
|
||||
"Light": "Światło",
|
||||
"Window": "Okno",
|
||||
"Door": "Drzwi",
|
||||
"Motion": "Czujnik ruchu",
|
||||
"Shutter": "Roleta",
|
||||
"Blind": "Żaluzja",
|
||||
"Fan": "Wiatrak",
|
||||
"AirCon": "Klimatyzator",
|
||||
"Heating": "Ogrzewanie",
|
||||
"Temperature": "Temperatura",
|
||||
"Humidity": "Wilgotność",
|
||||
"Power": "Gniazdko"
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
{
|
||||
"GroundFloor": "Первый этаж",
|
||||
"FirstFloor": "Второй этаж",
|
||||
"SecondFloor": "Третий этаж",
|
||||
"ThirdFloor": "Четвертый этаж",
|
||||
"FourthFloor": "Пятый этаж",
|
||||
|
||||
"Attic": "Чердак",
|
||||
"Balcony": "Балкон",
|
||||
"Backyard": "Задний двор",
|
||||
"Basement": "Подвал",
|
||||
"Bathroom": "Ванная комната",
|
||||
"Bedroom": "Спальня",
|
||||
"Boiler": "Бойлерная",
|
||||
"Wardrobe": "Гардероб",
|
||||
"Cellar": "Подвал",
|
||||
"Corridor": "Коридор",
|
||||
"Deck": "Палуба",
|
||||
"Dining": "Столовая",
|
||||
"Downstairs": "Вниз",
|
||||
"Driveway": "Заезд",
|
||||
"Entryway": "Подъезд",
|
||||
"FamilyRoom": "Гостиная",
|
||||
"FrontYard": "Передний двор",
|
||||
"Garage": "Гараж",
|
||||
"GuestHouse": "Гостевой дом",
|
||||
"GuestRoom": "Гостевая комната",
|
||||
"Hallway": "Прихожая",
|
||||
"HomeCinema": "Домашний кинотеатр",
|
||||
"KidsRoom": "Детская комната",
|
||||
"Kitchen": "Кухня",
|
||||
"LaundryRoom": "Прачечная",
|
||||
"Library": "Библиотека",
|
||||
"LivingRoom": "Гостиная",
|
||||
"LivingDining": "Гостиная и столовая",
|
||||
"Loft": "Лофт",
|
||||
"Lounge": "Комната отдыха",
|
||||
"MasterBedroom": "Главная спальня",
|
||||
"NannyRoom": "Комната няни",
|
||||
"Office": "Офис",
|
||||
"Outside": "За домом",
|
||||
"Patio": "Внутренний дворик",
|
||||
"Porch": "Крыльцо",
|
||||
"Stairwell": "Лестничная площадка",
|
||||
"StorageRoom": "Кладовка",
|
||||
"Studio": "Студия",
|
||||
"Shed": "Сарай",
|
||||
"Toilet": "Туалет",
|
||||
"Terrace": "Терраса",
|
||||
"Upstairs": "Подъем",
|
||||
|
||||
"Light": "Освещение",
|
||||
"Window": "Окно",
|
||||
"Door": "Дверь",
|
||||
"Motion": "Датчик движения",
|
||||
"Power": "Розетка",
|
||||
"Shutter": "Шторы",
|
||||
"Blind": "Жалюзи",
|
||||
"Fan": "Вентилятор",
|
||||
"AirCon": "Кондиционер",
|
||||
"Heating": "Обогрев",
|
||||
"Temperature": "Температура",
|
||||
"Humidity": "Влажность"
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>HomeBuilder</title>
|
||||
<link rel="shortcut icon" href="favicon.ico" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script src="dist/build.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
+5746
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"name": "homebuilder",
|
||||
"description": "openHAB HomeBuilder - boilerplate for the Items, sitemap and more",
|
||||
"version": "1.0.0",
|
||||
"authors": [
|
||||
"Kuba Wolanin <hi@kubawolanin.com> (github: kubawolanin)"
|
||||
],
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "cross-env NODE_ENV=development webpack-dev-server --open --hot",
|
||||
"build": "cross-env NODE_ENV=production webpack --progress --hide-modules"
|
||||
},
|
||||
"dependencies": {
|
||||
"ascii-table": "0.0.9",
|
||||
"bootstrap-vue": "^0.18.0",
|
||||
"daemonite-material": "^4.0.0-alpha.6",
|
||||
"lodash": "^4.17.4",
|
||||
"underscore.string": "^3.3.4",
|
||||
"v-clipboard": "^1.0.4",
|
||||
"vue": "^2.3.3",
|
||||
"vue-form-generator": "^2.0.0",
|
||||
"vue-i18n": "^7.1.1",
|
||||
"vue-multiselect": "^2.0.2",
|
||||
"vue-resource": "^1.3.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-core": "^6.0.0",
|
||||
"babel-loader": "^6.0.0",
|
||||
"babel-preset-env": "^1.5.1",
|
||||
"cross-env": "^3.0.0",
|
||||
"css-loader": "^0.25.0",
|
||||
"file-loader": "^0.9.0",
|
||||
"node-sass": "^4.5.0",
|
||||
"sass-loader": "^5.0.1",
|
||||
"style-loader": "^0.18.2",
|
||||
"vue-affix": "^0.2.1",
|
||||
"vue-loader": "^12.1.0",
|
||||
"vue-template-compiler": "^2.3.3",
|
||||
"webpack": "^2.6.1",
|
||||
"webpack-dev-server": "^2.4.5"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<template>
|
||||
<div>
|
||||
<app-nav></app-nav>
|
||||
<div class="container" id="app">
|
||||
<app-form></app-form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AppNav from './AppNav.vue'
|
||||
import AppForm from './AppForm.vue'
|
||||
|
||||
export default {
|
||||
name: 'app',
|
||||
components: {
|
||||
AppNav,
|
||||
AppForm
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,201 @@
|
||||
<template>
|
||||
<div class='row'>
|
||||
<div class='col-sm-6' id="form">
|
||||
<vue-form-generator :schema='schema' :model='model' :options='formOptions'>
|
||||
</vue-form-generator>
|
||||
<pre class='language-json hidden-xs-up'><code>{{ model }}</code></pre>
|
||||
</div>
|
||||
<div class='col-sm-6'>
|
||||
<affix id="preview" relative-element-selector="#form" :offset="{ top: 65, bottom: 0 }">
|
||||
<collapse v-if="model && model.filesGenerated.includes('items')" :uid="'items'" :heading="'Items'" :content='model.itemsType === "text" ? generateItems(model) : generateItemsJson(model)'></collapse>
|
||||
|
||||
<collapse v-if="model && model.filesGenerated.includes('sitemap')" :uid="'sitemap'" :heading="'Sitemap'" :content='generateSitemap(model)'></collapse>
|
||||
|
||||
<collapse v-if="model && model.filesGenerated.includes('habpanel')" :uid="'habpanel'" :heading="'HABPanel Dashboard'" :content='generateDashboard(model)'></collapse>
|
||||
</affix>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" src="./scss/app.scss">
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import * as _ from 'lodash'
|
||||
import * as s from 'underscore.string'
|
||||
import Vue from 'vue'
|
||||
import Collapse from './Collapse.vue'
|
||||
import { component as VueFormGenerator } from 'vue-form-generator'
|
||||
import { floors, rooms, objects, languages, OBJECTS_SUFFIX } from './definitions'
|
||||
import * as schema from './formSchema'
|
||||
import { generateItems } from './textItems'
|
||||
import { generateItemsJson } from './restItems'
|
||||
import { generateDashboard } from './habpanel'
|
||||
import { sitemapName, generateSitemap } from './sitemap'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
VueFormGenerator,
|
||||
Collapse
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
model: {
|
||||
language: 'en-UK',
|
||||
homeName: 'Our Home',
|
||||
filesGenerated: ['items', 'sitemap'],
|
||||
itemsType: 'text',
|
||||
itemsChannel: true,
|
||||
itemsIcons: true,
|
||||
floorsCount: 1,
|
||||
GroundFloor: []
|
||||
},
|
||||
|
||||
schema: {
|
||||
groups: [
|
||||
{ legend: '', fields: schema.basicFields },
|
||||
{ legend: 'Floors', fields: schema.floorsFields },
|
||||
{ legend: 'Rooms', fields: schema.roomsFields },
|
||||
{ legend: 'Objects', fields: schema.objectsFields },
|
||||
{ legend: '', fields: schema.settingsFields }
|
||||
],
|
||||
},
|
||||
|
||||
formOptions: {
|
||||
validateAfterLoad: true,
|
||||
validateAfterChanged: true,
|
||||
fieldIdPrefix: 'user-'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
/**
|
||||
* Generates textual Items
|
||||
*/
|
||||
generateItems,
|
||||
|
||||
/**
|
||||
* Generates JSON array of Items,
|
||||
* understandable by REST API
|
||||
*/
|
||||
generateItemsJson,
|
||||
|
||||
/**
|
||||
* Generates textual Sitemap
|
||||
*/
|
||||
generateSitemap,
|
||||
|
||||
/**
|
||||
* Generates HABPanel dashboard JSON config
|
||||
*/
|
||||
generateDashboard,
|
||||
|
||||
/**
|
||||
* Gets i18n configuration from ESH service
|
||||
*/
|
||||
getLocale: function() {
|
||||
const DEFAULT_LOCALE = 'en-UK';
|
||||
this.$http
|
||||
.get('services/org.eclipse.smarthome.core.i18nprovider/config')
|
||||
.then(response => {
|
||||
const body = response.body;
|
||||
let selectedLang = DEFAULT_LOCALE;
|
||||
|
||||
if (body.language && body.region) {
|
||||
let langId = body.language + '-' + body.region;
|
||||
let lang = _.find(languages, { id: langId });
|
||||
selectedLang = lang ? langId : DEFAULT_LOCALE;
|
||||
}
|
||||
|
||||
this.$data.model.language = selectedLang;
|
||||
this.fetchTranslations(selectedLang);
|
||||
})
|
||||
.catch(reason => {
|
||||
this.$data.model.language = DEFAULT_LOCALE;
|
||||
this.fetchTranslations(DEFAULT_LOCALE);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Loads translation file from the `/i18n/` folder
|
||||
* and assigns new `name` properties to the definitions.
|
||||
*/
|
||||
fetchTranslations: function(language) {
|
||||
let root = window.location.href.replace('/index.html', '');
|
||||
this.$http
|
||||
.get(root + '/i18n/' + language + '.json')
|
||||
.then(response => {
|
||||
this.$i18n.locale = language;
|
||||
this.$i18n.setLocaleMessage(language, response.body);
|
||||
|
||||
let stack = [...rooms, ...objects];
|
||||
let roomsModel = _.chain(this.$data.model)
|
||||
.pickBy((value, key) => _.endsWith(key, OBJECTS_SUFFIX))
|
||||
.value();
|
||||
|
||||
floors.forEach(function(item) {
|
||||
stack = [...stack, item];
|
||||
|
||||
if (this.$data.model[item.value]) {
|
||||
stack = [...stack, ...this.$data.model[item.value]];
|
||||
}
|
||||
}.bind(this));
|
||||
|
||||
if (!_.isEmpty(roomsModel)) {
|
||||
_.forOwn(roomsModel, (room) => { stack = [...stack, ...room] });
|
||||
}
|
||||
|
||||
stack.forEach(item => {
|
||||
if (!item.custom) {
|
||||
item.name = this.$i18n.t(item.value);
|
||||
}
|
||||
});
|
||||
this.$forceUpdate();
|
||||
});
|
||||
},
|
||||
|
||||
resizeAffix(event) {
|
||||
let bodyWidth = document.body && document.body.clientWidth;
|
||||
let formEl = document.getElementById('form');
|
||||
let previewEl = document.getElementById('preview');
|
||||
let formWidth = formEl && form.clientWidth;
|
||||
let hidden = 'hidden-xs-up';
|
||||
|
||||
previewEl.style.width = formWidth + 'px';
|
||||
|
||||
if (bodyWidth <= 599) {
|
||||
previewEl.classList.add(hidden);
|
||||
document.querySelector('.navbar-toggler-icon').classList.remove(hidden);
|
||||
document.querySelector('.navbar-close-icon').classList.add(hidden);
|
||||
} else {
|
||||
previewEl.classList.remove(hidden);
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
http: {
|
||||
root: window.location.origin + '/rest/'
|
||||
},
|
||||
|
||||
mounted: function() {
|
||||
this.getLocale();
|
||||
|
||||
this.$nextTick(function() {
|
||||
window.addEventListener('resize', this.resizeAffix);
|
||||
this.resizeAffix();
|
||||
})
|
||||
},
|
||||
|
||||
watch: {
|
||||
locale(val) {
|
||||
this.$i18n.locale = val
|
||||
}
|
||||
},
|
||||
|
||||
beforeDestroy() {
|
||||
window.removeEventListener('resize', this.resizeAffix);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,41 @@
|
||||
<template>
|
||||
<nav class="navbar fixed-top">
|
||||
<a class="navbar-brand mr-auto" href="#">HomeBuilder</a>
|
||||
|
||||
<button @click="togglePreview" type="button" aria-label="Toggle preview" class="navbar-toggler navbar-toggler-right">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
<span class="navbar-close-icon hidden-xs-up">×</span>
|
||||
</button>
|
||||
|
||||
</nav>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.navbar {
|
||||
background: #E64A19;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.navbar-toggler {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.navbar-close-icon {
|
||||
color: #200f04;
|
||||
font-size: 36px;
|
||||
padding-left: 1px;
|
||||
margin-top: -8px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
methods: {
|
||||
togglePreview() {
|
||||
document.getElementById('preview').classList.toggle('hidden-xs-up');
|
||||
document.querySelector('.navbar-close-icon').classList.toggle('hidden-xs-up');
|
||||
document.querySelector('.navbar-toggler-icon').classList.toggle('hidden-xs-up');
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,78 @@
|
||||
<template>
|
||||
<div class="pb-1">
|
||||
<b-btn block v-b-toggle="uid" variant="info" v-html="heading"></b-btn>
|
||||
<b-collapse :id="uid" accordion="preview">
|
||||
<b-card>
|
||||
<button @click="createItems" v-if="uid === 'items' && content instanceof Array" class="btn-clipboard btn-create-items">{{ createText }}</button>
|
||||
<button @click="copy" class="btn-clipboard" title="Copy to clipboard">{{ copyText }}</button>
|
||||
|
||||
<span class="pb-1" v-if="uid === 'items' && content instanceof Array">
|
||||
Number of Items: {{content.length}}
|
||||
</span>
|
||||
|
||||
<span class="pb-1" v-if="uid === 'habpanel'">
|
||||
Paste the content in
|
||||
<a href="../../habpanel/index.html#/settings/localconfig" target="_blank">this config page</a>.
|
||||
</span>
|
||||
<pre v-html='content'></pre>
|
||||
</b-card>
|
||||
</b-collapse>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Vue from 'vue'
|
||||
import Clipboard from 'v-clipboard'
|
||||
|
||||
Vue.use(Clipboard);
|
||||
|
||||
export default {
|
||||
props: ['uid', 'heading', 'content'],
|
||||
data: () => ({
|
||||
copyText: 'Copy',
|
||||
createText: 'Create Items'
|
||||
}),
|
||||
http: {
|
||||
root: window.location.origin + '/rest/'
|
||||
},
|
||||
methods: {
|
||||
copy() {
|
||||
let content = this.content;
|
||||
|
||||
if (typeof content === 'object') {
|
||||
content = JSON.stringify(this.content, null, 2);
|
||||
} else {
|
||||
content = content
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>');
|
||||
}
|
||||
|
||||
this.$clipboard(content);
|
||||
|
||||
this.copyText = 'Copied!';
|
||||
|
||||
setTimeout(() => {
|
||||
this.copyText = 'Copy';
|
||||
}, 500);
|
||||
},
|
||||
createItems() {
|
||||
let content = this.content;
|
||||
if (typeof content === 'object') {
|
||||
this.$http
|
||||
.put('items', content)
|
||||
.then(response => {
|
||||
if (response.status === 200) {
|
||||
this.createText = 'Done!';
|
||||
} else {
|
||||
this.createText = 'Error!';
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
this.createText = 'Create Items';
|
||||
}, 500);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,91 @@
|
||||
/**
|
||||
* i18n definitions
|
||||
*/
|
||||
export var languages = [
|
||||
{ name: 'English', id: 'en-UK' },
|
||||
{ name: 'French', id: 'fr-FR' },
|
||||
{ name: 'German', id: 'de-DE' },
|
||||
{ name: 'Italian', id: 'it-IT' },
|
||||
{ name: 'Korean', id: 'ko-KR' },
|
||||
{ name: 'Polish', id: 'pl-PL' },
|
||||
{ name: 'Russian', id: 'ru-RU' },
|
||||
{ name: 'Spanish', id: 'es-ES' }
|
||||
];
|
||||
|
||||
/**
|
||||
* Structure definitions
|
||||
*/
|
||||
export var floors = [
|
||||
{ abbr: 'GF', value: 'GroundFloor', icon: 'groundfloor' },
|
||||
{ abbr: 'FF', value: 'FirstFloor', icon: 'firstfloor' },
|
||||
{ abbr: 'F2', value: 'SecondFloor', icon: 'attic' },
|
||||
{ abbr: 'F3', value: 'ThirdFloor', icon: 'attic' },
|
||||
{ abbr: 'F4', value: 'FourthFloor', icon: 'attic' }
|
||||
];
|
||||
|
||||
export var rooms = [
|
||||
{ value: 'Attic', icon: 'attic' },
|
||||
{ value: 'Balcony', icon: '' },
|
||||
{ value: 'Backyard', icon: 'lawnmower' },
|
||||
{ value: 'Basement', icon: 'cellar' },
|
||||
{ value: 'Bathroom', icon: 'bath' },
|
||||
{ value: 'Bedroom', icon: 'bedroom' },
|
||||
{ value: 'Boiler', icon: 'gas' },
|
||||
{ value: 'Wardrobe', icon: 'wardrobe' },
|
||||
{ value: 'Cellar', icon: 'cellar' },
|
||||
{ value: 'Corridor', icon: 'corridor' },
|
||||
{ value: 'Deck', icon: '' },
|
||||
{ value: 'Dining', icon: '' },
|
||||
{ value: 'Downstairs', icon: 'cellar' },
|
||||
{ value: 'Driveway', icon: '' },
|
||||
{ value: 'Entryway', icon: 'frontdoor' },
|
||||
{ value: 'FamilyRoom', icon: 'parents_2_4' },
|
||||
{ value: 'FrontYard', icon: 'lawnmower' },
|
||||
{ value: 'Garage', icon: 'garage' },
|
||||
{ value: 'GuestHouse', icon: 'house' },
|
||||
{ value: 'GuestRoom', icon: 'parents_4_3' },
|
||||
{ value: 'Hallway', icon: 'corridor' },
|
||||
{ value: 'HomeCinema', icon: 'screen' },
|
||||
{ value: 'KidsRoom', icon: 'girl_3' },
|
||||
{ value: 'Kitchen', icon: 'kitchen' },
|
||||
{ value: 'LaundryRoom', icon: 'washingmachine' },
|
||||
{ value: 'Library', icon: 'office' },
|
||||
{ value: 'LivingRoom', icon: 'sofa' },
|
||||
{ value: 'LivingDining', icon: 'sofa' },
|
||||
{ value: 'Loft', icon: 'attic' },
|
||||
{ value: 'Lounge', icon: 'sofa' },
|
||||
{ value: 'MasterBedroom', icon: 'bedroom_red' },
|
||||
{ value: 'NannyRoom', icon: 'woman_1' },
|
||||
{ value: 'Office', icon: 'office' },
|
||||
{ value: 'Outside', icon: 'garden' },
|
||||
{ value: 'Patio', icon: 'terrace' },
|
||||
{ value: 'Porch', icon: 'group' },
|
||||
{ value: 'Stairwell', icon: 'qualityofservice' },
|
||||
{ value: 'StorageRoom', icon: 'suitcase' },
|
||||
{ value: 'Studio', icon: 'pantry' },
|
||||
{ value: 'Shed', icon: 'greenhouse' },
|
||||
{ value: 'Toilet', icon: 'toilet' },
|
||||
{ value: 'Terrace', icon: 'terrace' },
|
||||
{ value: 'Upstairs', icon: 'firstfloor' }
|
||||
];
|
||||
|
||||
/**
|
||||
* Collection of objects (sensors, smart devices etc.) controllable by openHAB.
|
||||
*/
|
||||
export var objects = [
|
||||
{ value: 'Light', icon: 'light', type: 'Switch:OR(ON, OFF)', unit: '[(%d)]' },
|
||||
{ value: 'Window', icon: 'window', type: 'Contact:OR(OPEN, CLOSED)', unit: '[MAP(en.map):%s]' },
|
||||
{ value: 'Door', icon: 'door', type: 'Contact:OR(OPEN, CLOSED)', unit: '[MAP(en.map):%s]' },
|
||||
{ value: 'Motion', icon: 'motion', type: 'Switch:OR(ON, OFF)', unit: '[(%d)]' },
|
||||
{ value: 'Power', icon: 'poweroutlet', type: 'Switch:OR(ON, OFF)', unit: '[(%d)]' },
|
||||
{ value: 'Shutter', icon: 'rollershutter', type: 'Rollershutter:OR(UP, DOWN)', unit: '[(%d)]' },
|
||||
{ value: 'Blind', icon: 'blinds', type: 'Dimmer', unit: '[%d %%]' },
|
||||
{ value: 'Fan', icon: 'fan_ceiling', type: 'Switch:OR(ON, OFF)', unit: '[(%d)]' },
|
||||
{ value: 'AirCon', icon: 'snow', type: 'Switch:OR(ON, OFF)', unit: '[(%d)]' },
|
||||
{ value: 'Heating', icon: 'heating', type: 'Number:AVG', unit: '[%.1f °C]' },
|
||||
{ value: 'Temperature', icon: 'temperature', type: 'Number:AVG', unit: '[%.1f °C]' },
|
||||
{ value: 'Humidity', icon: 'humidity', type: 'Number:AVG', unit: '[%d %%]' },
|
||||
];
|
||||
|
||||
export const OBJECTS_SUFFIX = '_objects';
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
<template>
|
||||
<multiselect
|
||||
:id="selectOptions.id"
|
||||
:options="options"
|
||||
:value="value"
|
||||
:multiple="selectOptions.multiple"
|
||||
:track-by="selectOptions.trackBy || null"
|
||||
:label="selectOptions.label || null"
|
||||
:searchable="selectOptions.searchable"
|
||||
:clear-on-select="selectOptions.clearOnSelect"
|
||||
:hide-selected="selectOptions.hideSelected"
|
||||
:placeholder="schema.placeholder"
|
||||
:allow-empty="selectOptions.allowEmpty"
|
||||
:reset-after="selectOptions.resetAfter"
|
||||
:close-on-select="selectOptions.closeOnSelect"
|
||||
:custom-label="customLabel"
|
||||
:taggable="selectOptions.taggable"
|
||||
:tag-placeholder="selectOptions.tagPlaceholder"
|
||||
:max="schema.max || null"
|
||||
:options-limit="selectOptions.optionsLimit"
|
||||
:group-values="selectOptions.groupValues"
|
||||
:group-label="selectOptions.groupLabel"
|
||||
:block-keys="selectOptions.blockKeys"
|
||||
:internal-search="selectOptions.internalSearch"
|
||||
:select-label="selectOptions.selectLabel"
|
||||
:selected-label="selectOptions.selectedLabel"
|
||||
:deselect-label="selectOptions.deselectLabel"
|
||||
:show-labels="selectOptions.showLabels"
|
||||
:limit="selectOptions.limit"
|
||||
:limit-text="selectOptions.limitText"
|
||||
:loading="selectOptions.loading"
|
||||
:disabled="disabled"
|
||||
:max-height="selectOptions.maxHeight"
|
||||
:show-pointer="selectOptions.showPointer"
|
||||
@input="updateSelected"
|
||||
@select="onSelect"
|
||||
@remove="onRemove"
|
||||
@search-change="onSearchChange"
|
||||
@tag="addTag"
|
||||
@open="onOpen"
|
||||
@close="onClose"
|
||||
:option-height="selectOptions.optionHeight"
|
||||
>
|
||||
<template slot="tag" scope="props">
|
||||
<span class="multiselect__tag">
|
||||
<img :src="generateIcon(props.option.icon)" />
|
||||
<span>{{ props.option.name }}</span>
|
||||
<i aria-hidden="true"
|
||||
tabindex="1"
|
||||
@click="props.remove(props.option)"
|
||||
class="multiselect__tag-icon"></i>
|
||||
</span>
|
||||
</template>
|
||||
<template slot="option" scope="props">
|
||||
<img class="option__image" :src="generateIcon(props.option.icon)" />
|
||||
<div class="option__desc">
|
||||
<span class="option__title">{{ props.option.name }}</span>
|
||||
<span class="option__small text-muted">{{ props.option.type || ''}}</span>
|
||||
</div>
|
||||
</template>
|
||||
</multiselect>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.option__image,
|
||||
.option__desc {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
|
||||
.option__small {
|
||||
display: block;
|
||||
font-weight: normal;
|
||||
font-size: .725rem;
|
||||
}
|
||||
}
|
||||
|
||||
.multiselect__element {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.multiselect__option {
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.multiselect__tag {
|
||||
img {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
|
||||
&[src*="none.svg"] {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.option__image {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import { abstractField } from 'vue-form-generator';
|
||||
|
||||
export default {
|
||||
mixins: [abstractField],
|
||||
computed: {
|
||||
selectOptions() {
|
||||
return this.schema.selectOptions || {};
|
||||
},
|
||||
|
||||
options() {
|
||||
let values = this.schema.values;
|
||||
if (typeof(values) === 'function') {
|
||||
return values.apply(this, [this.model, this.schema]);
|
||||
} else {
|
||||
return values;
|
||||
}
|
||||
},
|
||||
customLabel(){
|
||||
if (typeof this.schema.selectOptions !== 'undefined' && typeof this.schema.selectOptions.customLabel !== 'undefined' && typeof this.schema.selectOptions.customLabel === 'function') {
|
||||
return this.schema.selectOptions.customLabel;
|
||||
} else {
|
||||
//this will let the multiselect library use the default behavior if customLabel is not specified
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
updateSelected(value/*, id*/) {
|
||||
this.value = value;
|
||||
},
|
||||
generateIcon(icon) {
|
||||
return window.location.origin + '/icon/' + (icon || 'none') + '.svg'
|
||||
},
|
||||
addTag(newTag, id) {
|
||||
let onNewTag = this.selectOptions.onNewTag;
|
||||
if (typeof(onNewTag) == 'function') {
|
||||
onNewTag(newTag, id, this.options, this.value);
|
||||
}
|
||||
},
|
||||
onSearchChange(searchQuery, id) {
|
||||
let onSearch = this.selectOptions.onSearch;
|
||||
if (typeof(onSearch) == 'function') {
|
||||
onSearch(searchQuery, id, this.options);
|
||||
}
|
||||
},
|
||||
onSelect(/*selectedOption, id*/) {
|
||||
// console.log("onSelect", selectedOption, id);
|
||||
},
|
||||
onRemove(/*removedOption, id*/) {
|
||||
// console.log("onRemove", removedOption, id);
|
||||
},
|
||||
onOpen(/*id*/) {
|
||||
// console.log("onOpen", id);
|
||||
},
|
||||
onClose(/*value, id*/) {
|
||||
// console.log("onClose", value, id);
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// Check if the component is loaded globally
|
||||
if (!this.$root.$options.components['multiselect']) {
|
||||
console.error("'vue-multiselect' is missing. Please download from https://github.com/monterail/vue-multiselect and register the component globally!");
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,330 @@
|
||||
import * as _ from 'lodash'
|
||||
import * as s from 'underscore.string'
|
||||
import { validators } from 'vue-form-generator'
|
||||
import { languages, floors, rooms, objects, OBJECTS_SUFFIX } from './definitions'
|
||||
|
||||
/**
|
||||
* Invoked when language select has changed its value.
|
||||
*
|
||||
* @param {Object} model
|
||||
* @param {Object} newVal
|
||||
*/
|
||||
function languageChanged(model, newVal) {
|
||||
this.$parent.$parent.fetchTranslations(newVal);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a custom room entry
|
||||
*
|
||||
* @param {string} newTag
|
||||
* @param {string} id
|
||||
* @param {Object} options
|
||||
* @param {string} value
|
||||
*/
|
||||
function newRoomTag(newTag, id, options, value) {
|
||||
const tag = {
|
||||
name: newTag,
|
||||
icon: 'none',
|
||||
custom: true,
|
||||
value: s(newTag)
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.cleanDiacritics()
|
||||
.classify()
|
||||
.value()
|
||||
}
|
||||
rooms.push(tag);
|
||||
value.push(tag);
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a group function for a given
|
||||
* Item's type
|
||||
* @param {*} type
|
||||
*/
|
||||
function getGroupFunc(type) {
|
||||
let func = '';
|
||||
|
||||
switch (type) {
|
||||
case 'Switch':
|
||||
func = 'OR(ON, OFF)';
|
||||
break;
|
||||
case 'Contact':
|
||||
func = 'OR(OPEN, CLOSED)';
|
||||
break;
|
||||
case 'Rollershutter':
|
||||
func = 'OR(UP, DOWN)';
|
||||
break;
|
||||
case 'Number':
|
||||
func = 'AVG';
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return func;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a custom object entry
|
||||
*
|
||||
* @param {string} newTag
|
||||
* @param {string} id
|
||||
* @param {Object} options
|
||||
* @param {string} value
|
||||
*/
|
||||
function newObjectTag(newTag, id, options, value) {
|
||||
let split = newTag.split(':');
|
||||
let type = split.length > 1 ? _.first(split).trim() : 'Switch';
|
||||
let name = split.length > 1 ? split[1].trim() : newTag;
|
||||
let groupFunc = getGroupFunc(type);
|
||||
|
||||
const tag = {
|
||||
name: name,
|
||||
icon: 'none',
|
||||
type: type + ':' + groupFunc,
|
||||
unit: '[(%d)]',
|
||||
custom: true,
|
||||
value: s(name)
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.cleanDiacritics()
|
||||
.classify()
|
||||
.value()
|
||||
}
|
||||
|
||||
objects.push(tag);
|
||||
value.push(tag);
|
||||
};
|
||||
|
||||
/**
|
||||
* Is being executed when
|
||||
* collection of rooms in floor multiselect field
|
||||
* has changed.
|
||||
*
|
||||
* If there's a new `room` in collection,
|
||||
* a new dynamic field is added to the floor object, e.g.
|
||||
* `"GroundFloor": [ { name: 'Bedroom', value: 'Bedroom', icon: 'bed' }]`
|
||||
*
|
||||
* If an entry was removed from the collection,
|
||||
* a dynamic field is removed accordingly.
|
||||
*
|
||||
* @param {Object} model
|
||||
* @param {Array} newVal
|
||||
* @param {Array} oldVal
|
||||
* @param {Object} field
|
||||
*/
|
||||
function roomsChanged(model, newVal, oldVal, field) {
|
||||
let objectsFields = _.find(this.$options.parent.groups, { legend: 'Objects' }).fields;
|
||||
let floor = field.model;
|
||||
let oldList = oldVal ? _.map(oldVal, 'value') : [];
|
||||
let newList = _.map(newVal, 'value');
|
||||
let lastRemoved = _.first(_.difference(oldList, newList));
|
||||
let lastItem = _.last(newList);
|
||||
let roomName = '';
|
||||
|
||||
if (lastItem && !lastRemoved) {
|
||||
let room = _.find(rooms, { value: lastItem });
|
||||
roomName = floor + '_' + room.value + OBJECTS_SUFFIX;
|
||||
objectsFields.push({
|
||||
type: 'multiselect',
|
||||
label: (room.name || room.value) + ' (' + _.find(floors, { value: floor }).name + ')',
|
||||
styleClasses: 'rooms-list',
|
||||
model: roomName,
|
||||
placeholder: 'Type to search object',
|
||||
selectOptions: {
|
||||
multiple: true,
|
||||
hideSelected: true,
|
||||
closeOnSelect: false,
|
||||
selectLabel: '',
|
||||
trackBy: 'value',
|
||||
label: 'name',
|
||||
searchable: true,
|
||||
taggable: true,
|
||||
onNewTag: newObjectTag
|
||||
},
|
||||
values: objects
|
||||
});
|
||||
}
|
||||
|
||||
if (lastRemoved) {
|
||||
roomName = floor + '_' + lastRemoved + OBJECTS_SUFFIX;
|
||||
_.remove(objectsFields, { model: roomName });
|
||||
delete model[roomName];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Schema describing the basic form
|
||||
* generated by vue-form-generator
|
||||
*/
|
||||
export var basicFields = [{
|
||||
type: 'select',
|
||||
model: 'language',
|
||||
label: 'Please select your language',
|
||||
values: function() {
|
||||
return languages;
|
||||
},
|
||||
selectOptions: {
|
||||
hideNoneSelectedText: true
|
||||
},
|
||||
onChanged: languageChanged
|
||||
},
|
||||
|
||||
{
|
||||
type: 'input',
|
||||
inputType: 'text',
|
||||
label: 'Home Setup Name',
|
||||
model: 'homeName',
|
||||
readonly: false,
|
||||
disabled: false,
|
||||
placeholder: 'Home name',
|
||||
validator: validators.string
|
||||
}
|
||||
];
|
||||
|
||||
export var floorsFields = [{
|
||||
type: 'radios',
|
||||
model: 'floorsCount',
|
||||
label: 'Number of floors',
|
||||
styleClasses: 'floors-number',
|
||||
values: [1, 2, 3, 4, 5],
|
||||
onChanged: function(model, newVal, oldVal, field) {
|
||||
let roomsFields = _.find(this.$options.parent.groups, { legend: 'Rooms' }).fields;
|
||||
if (newVal <= 5 && newVal > oldVal) {
|
||||
for (var i = oldVal; i < newVal; i++) {
|
||||
let floor = floors[i];
|
||||
let floorName = floor.value;
|
||||
let fieldExists = _.find(roomsFields, (field) => field.model === floorName);
|
||||
|
||||
if (!fieldExists) {
|
||||
roomsFields.push(roomsSelect(floorName, floor.name || floor.value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (newVal < oldVal) {
|
||||
for (var j = oldVal; j > newVal; j--) {
|
||||
let floorName = floors[j - 1].value;
|
||||
_.remove(roomsFields, { model: floorName });
|
||||
delete model[floorName];
|
||||
}
|
||||
}
|
||||
}
|
||||
}];
|
||||
|
||||
export var settingsFields = [{
|
||||
type: 'checklist',
|
||||
model: 'filesGenerated',
|
||||
label: 'What would you like to generate?',
|
||||
multi: true,
|
||||
listBox: true,
|
||||
multiSelect: true,
|
||||
values: [
|
||||
{ name: 'Items', value: 'items' },
|
||||
{ name: 'Sitemap', value: 'sitemap' },
|
||||
{ name: 'Dashboard', value: 'habpanel' }
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
type: 'radios',
|
||||
label: 'How would you like to store the Items?',
|
||||
model: 'itemsType',
|
||||
visible(model) {
|
||||
return model && model.filesGenerated.includes('items');
|
||||
},
|
||||
values: [
|
||||
{ name: 'Textual Configuration Files', value: 'text' },
|
||||
{ name: 'Internal Database', value: 'rest' }
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
type: 'switch',
|
||||
label: 'Append channel to the non-Group items',
|
||||
model: 'itemsChannel',
|
||||
default: true,
|
||||
textOn: 'Yes',
|
||||
textOff: 'No',
|
||||
valueOn: true,
|
||||
valueOff: false,
|
||||
visible(model) {
|
||||
return model &&
|
||||
model.filesGenerated.includes('items') &&
|
||||
model.itemsType === 'text';
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
type: 'switch',
|
||||
label: 'Include icons',
|
||||
model: 'itemsIcons',
|
||||
default: true,
|
||||
textOn: 'Yes',
|
||||
textOff: 'No',
|
||||
valueOn: true,
|
||||
valueOff: false,
|
||||
visible(model) {
|
||||
return model &&
|
||||
(model.filesGenerated.includes('items') || model.filesGenerated.includes('sitemap'));
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
type: 'switch',
|
||||
label: 'Include tags',
|
||||
model: 'itemsTags',
|
||||
default: false,
|
||||
textOn: 'Yes',
|
||||
textOff: 'No',
|
||||
valueOn: true,
|
||||
valueOff: false,
|
||||
visible(model) {
|
||||
return model && model.filesGenerated.includes('items');
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
/**
|
||||
* Generates a multiselect input with
|
||||
* rooms for specific floor (model)
|
||||
*
|
||||
* @param {string} model
|
||||
* @param {string} label
|
||||
*/
|
||||
function roomsSelect(model, label) {
|
||||
return {
|
||||
type: 'multiselect',
|
||||
label: label,
|
||||
styleClasses: 'rooms-list',
|
||||
model: model,
|
||||
values: rooms,
|
||||
placeholder: 'Type to search or add room',
|
||||
selectOptions: {
|
||||
multiple: true,
|
||||
trackBy: 'value',
|
||||
label: 'name',
|
||||
selectLabel: '',
|
||||
searchable: true,
|
||||
taggable: true,
|
||||
closeOnSelect: false,
|
||||
hideSelected: true,
|
||||
tagPlaceholder: 'Add this as a new room',
|
||||
onNewTag: newRoomTag
|
||||
},
|
||||
onChanged: roomsChanged
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Field group schema for the rooms
|
||||
*/
|
||||
export var roomsFields = [
|
||||
roomsSelect('GroundFloor', 'Ground Floor')
|
||||
];
|
||||
|
||||
/**
|
||||
* Field group schema for the objects
|
||||
*/
|
||||
export var objectsFields = [];
|
||||
@@ -0,0 +1,114 @@
|
||||
import * as _ from 'lodash'
|
||||
import { floors, objects, OBJECTS_SUFFIX } from './definitions'
|
||||
import { getChosenObjects } from './restItems'
|
||||
|
||||
/**
|
||||
* Generates a HABPanel's widget object
|
||||
* @param {*} options
|
||||
*/
|
||||
function makeWidget(options) {
|
||||
let row = options.row > 0 ? options.row * 2 : 0;
|
||||
let widget = {
|
||||
item: options.item,
|
||||
name: options.name,
|
||||
sizeX: 2,
|
||||
sizeY: 2,
|
||||
type: options.type,
|
||||
row: row,
|
||||
col: options.col,
|
||||
font_size: '24',
|
||||
useserverformat: true
|
||||
};
|
||||
|
||||
if (options.type === 'switch') {
|
||||
widget = _.extend({}, widget, {
|
||||
iconset: 'eclipse-smarthome-classic',
|
||||
icon: options.icon,
|
||||
icon_size: 64
|
||||
});
|
||||
} else {
|
||||
widget = _.extend({}, widget, {
|
||||
backdrop_iconset: 'eclipse-smarthome-classic',
|
||||
backdrop_icon: options.icon,
|
||||
backdrop_center: true
|
||||
});
|
||||
}
|
||||
|
||||
return widget;
|
||||
}
|
||||
|
||||
function getWidgetType(type) {
|
||||
let widget = '';
|
||||
switch (type) {
|
||||
case 'Dimmer':
|
||||
widget = 'slider';
|
||||
break;
|
||||
case 'Switch':
|
||||
widget = 'switch';
|
||||
break;
|
||||
default:
|
||||
widget ='dummy';
|
||||
break;
|
||||
}
|
||||
|
||||
return widget;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an array widgets for specific Dashboard
|
||||
* @param {*} object
|
||||
* @param {*} model
|
||||
*/
|
||||
function makeWidgets(object, model) {
|
||||
let widgets = [];
|
||||
|
||||
for (var i = 0; i < model.floorsCount; i++) {
|
||||
var floor = floors[i];
|
||||
|
||||
if (floor && floor.value && !_.isUndefined(model[floor.value])) {
|
||||
model[floor.value].forEach(function(room) {
|
||||
let roomObjects = floor.value + '_' + room.value + OBJECTS_SUFFIX;
|
||||
let objectCollection = model[roomObjects] || [];
|
||||
let obj = objectCollection.find(o => o.value === object.value);
|
||||
|
||||
if (obj) {
|
||||
widgets.push(makeWidget({
|
||||
item: (model.floorsCount > 1 ? floor.abbr + '_' : '') + room.value + '_' + obj.value,
|
||||
name: room.name,
|
||||
type: getWidgetType(_.first(object.type.split(':'))),
|
||||
row: _.chunk(widgets, 6).length - 1,
|
||||
col: (widgets.length * 2) % 12,
|
||||
icon: object.icon
|
||||
}));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return widgets;
|
||||
};
|
||||
|
||||
/**
|
||||
* Generates a full HABPanel dashboard set
|
||||
* @param {*} model
|
||||
*/
|
||||
export function generateDashboard(model) {
|
||||
var chosenObjects = getChosenObjects(model);
|
||||
|
||||
return chosenObjects.length ? chosenObjects.map((obj) => {
|
||||
let object = _.find(objects, { value: obj });
|
||||
return {
|
||||
id: object.value,
|
||||
name: object.name || object.value,
|
||||
row: 0,
|
||||
col: 0,
|
||||
tile: {
|
||||
backdrop_iconset: 'eclipse-smarthome-classic',
|
||||
backdrop_icon: object.icon,
|
||||
icon_size: 32
|
||||
},
|
||||
widgets: makeWidgets(object, model)
|
||||
}
|
||||
}) : '';
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import Vue from 'vue'
|
||||
import BootstrapVue from 'bootstrap-vue/dist/bootstrap-vue.esm'
|
||||
import VueFormGenerator from 'vue-form-generator'
|
||||
import Affix from 'vue-affix'
|
||||
import Multiselect from 'vue-multiselect'
|
||||
import VueResource from 'vue-resource'
|
||||
import VueI18n from 'vue-i18n'
|
||||
import 'daemonite-material/css/material.css'
|
||||
import 'bootstrap-vue/dist/bootstrap-vue.css'
|
||||
import 'vue-form-generator/dist/vfg.css'
|
||||
import 'vue-multiselect/dist/vue-multiselect.min.css'
|
||||
import App from './App.vue'
|
||||
import fieldMultiselect from './fieldMultiselect.vue'
|
||||
|
||||
Vue.use(BootstrapVue);
|
||||
Vue.use(VueFormGenerator);
|
||||
Vue.use(Affix);
|
||||
Vue.use(VueResource);
|
||||
Vue.use(VueI18n);
|
||||
Vue.component('multiselect', Multiselect);
|
||||
Vue.component('fieldMultiselect', fieldMultiselect);
|
||||
|
||||
let i18n = new VueI18n({
|
||||
locale: 'en-UK'
|
||||
});
|
||||
|
||||
const vm = new Vue({
|
||||
i18n,
|
||||
el: '#app',
|
||||
render: h => h(App)
|
||||
});
|
||||
|
||||
@@ -0,0 +1,213 @@
|
||||
import * as _ from 'lodash'
|
||||
import { floors, objects, OBJECTS_SUFFIX } from './definitions'
|
||||
|
||||
export const GROUP_PREFIX = 'g';
|
||||
|
||||
export function addFloors(floor, model) {
|
||||
let items = [];
|
||||
if (model.floorsCount > 1) {
|
||||
items.push({
|
||||
type: 'Group',
|
||||
name: floor.abbr,
|
||||
label: floor.name || floor.value,
|
||||
category: model.itemsIcons ? floor.icon : '',
|
||||
groupNames: ['Home'],
|
||||
entryType: 'floor'
|
||||
});
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
export function addRooms(floor, model) {
|
||||
let items = [];
|
||||
|
||||
if (floor && floor.value && !_.isUndefined(model[floor.value])) {
|
||||
model[floor.value].forEach((room) => {
|
||||
let roomObjects = floor.value + '_' + room.value + OBJECTS_SUFFIX;
|
||||
let floorPrefix = model.floorsCount > 1 ? floor.abbr + '_' : '';
|
||||
|
||||
items.push({
|
||||
type: 'Group',
|
||||
name: floorPrefix + room.value,
|
||||
label: room.name || room.value,
|
||||
category: model.itemsIcons ? room.icon : '',
|
||||
groupNames: _.compact([
|
||||
'Home',
|
||||
model.floorsCount > 1 ? floor.abbr : ''
|
||||
]),
|
||||
entryType: 'room'
|
||||
});
|
||||
|
||||
items = [
|
||||
...items,
|
||||
...addObjects(room, model, floorPrefix, roomObjects)
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
export function addObjects(room, model, floorPrefix, roomObjects) {
|
||||
let objectCollection = model[roomObjects] || [];
|
||||
|
||||
if (!room.value && _.isEmpty(objectCollection)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return objectCollection.map(object => ({
|
||||
type: _.first(object.type.split(':')),
|
||||
name: floorPrefix + room.value + '_' + object.value,
|
||||
label: object.name || object.value,
|
||||
category: model.itemsIcons ? object.icon : '',
|
||||
groupNames: [
|
||||
floorPrefix + room.value,
|
||||
GROUP_PREFIX + object.value
|
||||
],
|
||||
tags: addTags(object, model),
|
||||
entryType: 'object'
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a list of object groups
|
||||
*
|
||||
* @param {Object} model
|
||||
* @return {string}
|
||||
*/
|
||||
export function addObjectGroups(model) {
|
||||
let items = [];
|
||||
let chosenObjects = getChosenObjects(model);
|
||||
|
||||
chosenObjects.forEach(function(dev) {
|
||||
let object = _.find(objects, { value: dev });
|
||||
|
||||
if (object) {
|
||||
let type = object.type.split(':');
|
||||
let groupType = _.first(type);
|
||||
let groupFuncName = type[1] ? type[1].split('(')[0] : '';
|
||||
let groupFuncArgs = type[1] && type[1].split('(')[1] ? type[1].split('(')[1]
|
||||
.split(',')
|
||||
.map((arg) => arg.trim().replace(/\W/g, '')) : [];
|
||||
|
||||
let item = {
|
||||
type: 'Group',
|
||||
name: GROUP_PREFIX + object.value,
|
||||
label: object.name || object.value,
|
||||
category: model.itemsIcons ? object.icon : '',
|
||||
groupNames: ['Home'],
|
||||
groupType: groupType,
|
||||
entryType: 'objectGroup'
|
||||
};
|
||||
|
||||
if (groupFuncName) {
|
||||
item = _.extend({}, item, {
|
||||
function: {
|
||||
name: groupFuncName
|
||||
}
|
||||
});
|
||||
|
||||
if (!_.isEmpty(groupFuncArgs)) {
|
||||
item.function.params = groupFuncArgs;
|
||||
}
|
||||
}
|
||||
|
||||
items.push(item);
|
||||
}
|
||||
});
|
||||
|
||||
return _.uniq(items);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets list of objects chosen
|
||||
* from collection
|
||||
*
|
||||
* @param {*} model
|
||||
* @return {Array}
|
||||
*/
|
||||
export function getChosenObjects(model) {
|
||||
return _.chain(model)
|
||||
.pickBy((value, key) => _.endsWith(key, OBJECTS_SUFFIX))
|
||||
.flatMap()
|
||||
.map((item) => item.value)
|
||||
.uniq()
|
||||
.value() || [];
|
||||
}
|
||||
|
||||
/**
|
||||
* For a given object it creates a HomeKit-compatible
|
||||
* set of tags.
|
||||
*
|
||||
* @param {Object} object
|
||||
* @param {Object} model
|
||||
* @return {Array}
|
||||
*/
|
||||
function addTags(object, model) {
|
||||
var type = _.first(object.type.split(':'));
|
||||
var tags = [];
|
||||
|
||||
switch (type) {
|
||||
case 'Switch':
|
||||
case 'Dimmer':
|
||||
case 'Color':
|
||||
tags.push('Switchable');
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
switch (object.value) {
|
||||
case 'Lamp':
|
||||
case 'Light':
|
||||
tags = ['Lighting'];
|
||||
break;
|
||||
case 'Motion':
|
||||
tags = [];
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return model.itemsTags ? tags : [];
|
||||
}
|
||||
|
||||
export function getItems(model) {
|
||||
let items = [{
|
||||
type: 'Group',
|
||||
name: 'Home',
|
||||
label: model.homeName,
|
||||
category: model.itemsIcons ? 'house' : '',
|
||||
entryType: 'home'
|
||||
}];
|
||||
|
||||
for (var i = 0; i < model.floorsCount; i++) {
|
||||
var floor = floors[i];
|
||||
|
||||
items = [
|
||||
...items,
|
||||
...addFloors(floor, model),
|
||||
...addRooms(floor, model)
|
||||
];
|
||||
}
|
||||
|
||||
items = [
|
||||
...items,
|
||||
...addObjectGroups(model)
|
||||
]
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns array of Items
|
||||
* without `entryType` which is not a valid parameter
|
||||
* for the request
|
||||
*
|
||||
* @param {*} model
|
||||
*/
|
||||
export function generateItemsJson(model) {
|
||||
let items = getItems(model);
|
||||
return _.map(items, item => _.omit(item, ['entryType']));
|
||||
}
|
||||
@@ -0,0 +1,223 @@
|
||||
#app {
|
||||
font-family: 'Avenir', Helvetica, Arial, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
color: #2c3e50;
|
||||
margin-top: 60px;
|
||||
--color-primary: #E64A19;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2 {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
li {
|
||||
display: inline-block;
|
||||
margin: 0 10px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
pre {
|
||||
padding: 1em;
|
||||
margin: 0;
|
||||
max-height: 400px;
|
||||
}
|
||||
|
||||
.vue-form-generator .form-group label {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.field-checklist .wrapper {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.vue-form-generator .form-control {
|
||||
border-radius: 0;
|
||||
border: 1px solid #e8e8e8;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.vue-form-generator .field-checklist .listbox {
|
||||
background: none;
|
||||
border: 0;
|
||||
box-shadow: none;
|
||||
max-height: none;
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
@mixin checked-label() {
|
||||
background: var(--color-primary);
|
||||
color: #fff;
|
||||
-webkit-box-shadow: 0 8px 10px 1px rgba(0, 0, 0, 0.14), 0 3px 14px 2px rgba(0, 0, 0, 0.12), 0 5px 5px -3px rgba(0, 0, 0, 0.4);
|
||||
box-shadow: 0 8px 10px 1px rgba(0, 0, 0, 0.14), 0 3px 14px 2px rgba(0, 0, 0, 0.12), 0 5px 5px -3px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
.list-row {
|
||||
width: 33%;
|
||||
display: inline-block;
|
||||
padding: 10px;
|
||||
input[type="checkbox"] {
|
||||
display: none;
|
||||
}
|
||||
&.is-checked label {
|
||||
@include checked-label();
|
||||
}
|
||||
}
|
||||
|
||||
.floors-number .radio-list label,
|
||||
.list-row label {
|
||||
width: 100%;
|
||||
background-color: transparent;
|
||||
background-image: none;
|
||||
border: 0;
|
||||
-webkit-box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12), 0 3px 1px -2px rgba(0, 0, 0, 0.2);
|
||||
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12), 0 3px 1px -2px rgba(0, 0, 0, 0.2);
|
||||
color: rgba(0, 0, 0, 0.87);
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
line-height: 1;
|
||||
margin: 0;
|
||||
max-width: 100%;
|
||||
min-width: 5.5rem;
|
||||
padding: 0.6875rem 1rem;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
text-transform: uppercase;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
vertical-align: middle;
|
||||
white-space: nowrap;
|
||||
border-radius: 2px;
|
||||
-webkit-transition-duration: 0.3s;
|
||||
-o-transition-duration: 0.3s;
|
||||
transition-duration: 0.3s;
|
||||
-webkit-transition-property: background-color, color, -webkit-box-shadow;
|
||||
transition-property: background-color, color, -webkit-box-shadow;
|
||||
-o-transition-property: background-color, box-shadow, color;
|
||||
transition-property: background-color, box-shadow, color;
|
||||
transition-property: background-color, box-shadow, color, -webkit-box-shadow;
|
||||
-webkit-transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
-o-transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.field-switch input:checked~.label,
|
||||
.multiselect__option--highlight,
|
||||
.multiselect__tag {
|
||||
background: var(--color-primary) !important;
|
||||
}
|
||||
|
||||
.floors-number {
|
||||
input[type="radio"] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.radio-list {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.radio-list label {
|
||||
border-radius: 0;
|
||||
-webkit-box-shadow: none;
|
||||
box-shadow: none;
|
||||
display: inline-block !important;
|
||||
width: 20% !important;
|
||||
background: #ff9f5e;
|
||||
color: #fff;
|
||||
|
||||
&.is-checked {
|
||||
@include checked-label();
|
||||
}
|
||||
}
|
||||
|
||||
.radio-list label.is-checked ~ label {
|
||||
background: transparent;
|
||||
color: #000;
|
||||
}
|
||||
}
|
||||
|
||||
.multiselect__content-wrapper,
|
||||
.multiselect__tags {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.multiselect__option--highlight:after {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.multiselect__tag-icon {
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
padding-top: 2px;
|
||||
&:after {
|
||||
color: #fff;
|
||||
font-size: 16px;
|
||||
}
|
||||
&:focus,
|
||||
&:hover {
|
||||
background: rgba(0, 0, 0, .15);
|
||||
}
|
||||
}
|
||||
|
||||
.card {
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
margin: 5px 0.25rem;
|
||||
}
|
||||
|
||||
@mixin btn-muted() {
|
||||
position: absolute;
|
||||
top: .5rem;
|
||||
z-index: 10;
|
||||
display: block;
|
||||
padding: .25rem .5rem;
|
||||
font-size: 75%;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
background-color: #ccc;
|
||||
border: 0;
|
||||
border-radius: .25rem;
|
||||
&:hover {
|
||||
background-color: #027de7;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-clipboard {
|
||||
@include btn-muted();
|
||||
right: .5rem;
|
||||
}
|
||||
|
||||
.btn-create-items {
|
||||
@include btn-muted();
|
||||
right: 5em;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 599px) {
|
||||
.navbar-toggler {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
#preview {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: #fff;
|
||||
left: 0;
|
||||
top: 56px !important;
|
||||
padding-top: 3px;
|
||||
position: fixed !important;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
import * as _ from 'lodash'
|
||||
import * as s from 'underscore.string'
|
||||
import { floors } from './definitions'
|
||||
import { getChosenObjects, getItems, GROUP_PREFIX } from './restItems'
|
||||
|
||||
export let sitemapName = '';
|
||||
|
||||
function getFloorItem(floor, model) {
|
||||
let floorFrame = 'Frame ';
|
||||
|
||||
if (model.floorsCount > 1) {
|
||||
let icon = model.itemsIcons && floor.icon ? '" icon="' + floor.icon : '';
|
||||
floorFrame += 'label="' + (floor.name || floor.value) + icon + '" ';
|
||||
}
|
||||
|
||||
return floorFrame + '{';
|
||||
}
|
||||
|
||||
function getRoomGroups(floor, model, floorPrefix) {
|
||||
if (floor && floor.value && !_.isUndefined(model[floor.value])) {
|
||||
return model[floor.value].map(room =>
|
||||
s.pad(' ', 8) + 'Group item=' + floorPrefix + room.value
|
||||
);
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
function addFloorFrames(model) {
|
||||
let lines = [];
|
||||
|
||||
for (let i = 0; i < model.floorsCount; i++) {
|
||||
let floor = floors[i];
|
||||
let floorPrefix = '';
|
||||
|
||||
if (model.floorsCount > 1) {
|
||||
floorPrefix = floor.abbr + '_';
|
||||
}
|
||||
|
||||
lines = [
|
||||
...lines,
|
||||
s.pad(' ', 4) + getFloorItem(floor, model),
|
||||
...getRoomGroups(floor, model, floorPrefix),
|
||||
s.pad(' ', 4) + '}'
|
||||
];
|
||||
|
||||
if (i < model.floorsCount) {
|
||||
lines.push('');
|
||||
}
|
||||
}
|
||||
|
||||
return lines;
|
||||
}
|
||||
|
||||
function getTextGroup(group) {
|
||||
let textGroup = `Text label="${group.label}"`;
|
||||
textGroup += group.category && group.category !== 'none' ? ` icon="${group.category}" {` : ` {`;
|
||||
return textGroup;
|
||||
}
|
||||
|
||||
function getDefaultItems(groupItems, items) {
|
||||
return groupItems.map(item => {
|
||||
let room = _.find(items, { name: item.groupNames[0] });
|
||||
return s.pad(' ', 12) + `Default item=${item.name} label="${room.label}"`;
|
||||
});
|
||||
}
|
||||
|
||||
function getObjectItems(model) {
|
||||
const items = getItems(model);
|
||||
let chosenObjects = getChosenObjects(model);
|
||||
let objects = chosenObjects.map((object, index) => {
|
||||
let groupName = GROUP_PREFIX + object;
|
||||
let group = _.find(items, { name: groupName });
|
||||
let groupItems = _.filter(items, item =>
|
||||
item.groupNames && item.groupNames.includes(groupName)
|
||||
);
|
||||
|
||||
let result = [
|
||||
s.pad(' ', 8) + getTextGroup(group),
|
||||
...getDefaultItems(groupItems, items),
|
||||
s.pad(' ', 8) + '}'
|
||||
];
|
||||
|
||||
if (index < chosenObjects.length - 1) {
|
||||
result.push('');
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
||||
|
||||
return _.flatten(objects);
|
||||
}
|
||||
|
||||
function addObjectsFrame(model) {
|
||||
let objectItems = getObjectItems(model);
|
||||
|
||||
if (objectItems.length) {
|
||||
return [
|
||||
s.pad(' ', 4) + 'Frame {',
|
||||
...objectItems,
|
||||
s.pad(' ', 4) + '}'
|
||||
];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
export function generateSitemap(model) {
|
||||
sitemapName = s(model.homeName)
|
||||
.slugify()
|
||||
.value()
|
||||
.replace(/-/g, '_');
|
||||
|
||||
return [
|
||||
'sitemap ' + sitemapName + ' label="' + model.homeName + '" {',
|
||||
...addFloorFrames(model),
|
||||
...addObjectsFrame(model),
|
||||
'}'
|
||||
].join('\n');
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
import * as _ from 'lodash'
|
||||
import * as AsciiTable from 'ascii-table'
|
||||
import { getItems } from './restItems'
|
||||
|
||||
/**
|
||||
* Generates item's type
|
||||
* e.g. `Switch` or `Group`
|
||||
* or `Group:Switch:OR(ON, OFF)`
|
||||
*
|
||||
* @param {Object} item
|
||||
* @param {Object} model
|
||||
* @return {string}
|
||||
*/
|
||||
function generateType(item) {
|
||||
let type = item.type;
|
||||
|
||||
if (item.entryType === 'objectGroup') {
|
||||
type += ':' + item.groupType;
|
||||
if (item.function) {
|
||||
type += item.function.name ? ':' + item.function.name : '';
|
||||
type += item.function.params ? '(' + item.function.params.join(', ') + ')' : '';
|
||||
}
|
||||
}
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a label for the Item
|
||||
* @param {Object} item
|
||||
* @return {string}
|
||||
*/
|
||||
function generateLabel(item) {
|
||||
return '"' + (item.label || item.name) + '"';
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an icon if there's any.
|
||||
*
|
||||
* @param {Object} item
|
||||
* @param {Object} model
|
||||
* @return {string}
|
||||
*/
|
||||
function generateIcon(item, model) {
|
||||
return item.category && model.itemsIcons ? '<' + item.category + '>' : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a list of groups for the item.
|
||||
* e.g. (Home, GF_Bedroom, gTemperature)
|
||||
* @param {Object} item
|
||||
* @return {string}
|
||||
*/
|
||||
function generateGroups(item) {
|
||||
return _.isEmpty(item.groupNames) ? '' : '(' + item.groupNames.join(', ') + ')';
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a list of tags for the item.
|
||||
* e.g. ["Switchable"]
|
||||
* @param {Object} item
|
||||
* @return {string}
|
||||
*/
|
||||
function generateTags(item) {
|
||||
return !_.isEmpty(item.tags) ? '["' + item.tags.join('", "') + '"]' : '';
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generates a "channel" string for the object item
|
||||
* e.g. `{channel=""}`
|
||||
* @param {Object} item
|
||||
* @param {Object} model
|
||||
* @return {string}
|
||||
*/
|
||||
function generateChannel(item, model) {
|
||||
return item.entryType === 'object' && model.itemsChannel ? '{channel=""}' : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an array or items
|
||||
* to be later processed by AsciiTable
|
||||
*
|
||||
* @param {*} items
|
||||
*/
|
||||
function generateTextualItems(items, model) {
|
||||
let result = items.map(item => {
|
||||
return [
|
||||
generateType(item),
|
||||
item.name,
|
||||
generateLabel(item),
|
||||
generateIcon(item, model),
|
||||
generateGroups(item),
|
||||
generateTags(item),
|
||||
generateChannel(item, model)
|
||||
]
|
||||
});
|
||||
|
||||
// Add some spacing between blocks
|
||||
if (!_.isEmpty(result)) {
|
||||
result.push(['']);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an array or items
|
||||
* for a given type
|
||||
*
|
||||
* @param {string} entryType
|
||||
* @param {Object} model
|
||||
* @return {Array}
|
||||
*/
|
||||
function getItemsOfType(entryType, model) {
|
||||
let allItems = getItems(model);
|
||||
let items = _(allItems)
|
||||
.filter({ entryType: entryType })
|
||||
.uniq()
|
||||
.value() || [];
|
||||
|
||||
return generateTextualItems(items, model);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms array of lines
|
||||
* into column-aligned table
|
||||
* @param {Array} lines
|
||||
*/
|
||||
function toTable(lines) {
|
||||
let table = new AsciiTable();
|
||||
|
||||
// Create the ascii-table to auto-format file
|
||||
let result = table.addRowMatrix(lines)
|
||||
.removeBorder()
|
||||
.toString();
|
||||
|
||||
// Cleanup the lines
|
||||
result = result.split('\n')
|
||||
.map((line) => line.slice(2).trimRight())
|
||||
.join('\n');
|
||||
|
||||
return result
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>');
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a textual Items file based on user input
|
||||
* Needs several datapoints from the model such as
|
||||
* floorsCount, rooms collection, objects etc.
|
||||
* @param {*} model
|
||||
* @return {string}
|
||||
*/
|
||||
export function generateItems(model) {
|
||||
// On the very top add the home item
|
||||
let lines = [
|
||||
...getItemsOfType('home', model),
|
||||
...getItemsOfType('floor', model),
|
||||
...getItemsOfType('room', model),
|
||||
...getItemsOfType('object', model)
|
||||
];
|
||||
|
||||
return toTable(lines) + '\n' +
|
||||
toTable(getItemsOfType('objectGroup', model));
|
||||
}
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 47 KiB |
@@ -0,0 +1,83 @@
|
||||
var path = require('path')
|
||||
var webpack = require('webpack')
|
||||
|
||||
module.exports = {
|
||||
entry: './src/main.js',
|
||||
output: {
|
||||
path: path.resolve(__dirname, './dist'),
|
||||
publicPath: '/dist/',
|
||||
filename: 'build.js'
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.vue$/,
|
||||
loader: 'vue-loader',
|
||||
options: {
|
||||
loaders: {
|
||||
// Since sass-loader (weirdly) has SCSS as its default parse mode, we map
|
||||
// the "scss" and "sass" values for the lang attribute to the right configs here.
|
||||
// other preprocessors should work out of the box, no loader config like this necessary.
|
||||
'scss': 'vue-style-loader!css-loader!sass-loader',
|
||||
'sass': 'vue-style-loader!css-loader!sass-loader?indentedSyntax'
|
||||
}
|
||||
// other vue-loader options go here
|
||||
}
|
||||
},
|
||||
{
|
||||
test: /\.js$/,
|
||||
loader: 'babel-loader',
|
||||
exclude: /node_modules/
|
||||
},
|
||||
{
|
||||
test: /\.(png|jpg|gif|svg)$/,
|
||||
loader: 'file-loader',
|
||||
options: {
|
||||
name: '[name].[ext]?[hash]'
|
||||
}
|
||||
},
|
||||
{
|
||||
test: /\.(scss|css)$/,
|
||||
loader: 'style-loader!css-loader!sass-loader'
|
||||
},
|
||||
{
|
||||
test: /\.(otf|eot|woff|woff2|ttf|svg)$/,
|
||||
loader: 'file-loader'
|
||||
}
|
||||
]
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
'vue$': 'vue/dist/vue.esm.js'
|
||||
}
|
||||
},
|
||||
devServer: {
|
||||
historyApiFallback: true,
|
||||
noInfo: true
|
||||
},
|
||||
performance: {
|
||||
hints: false
|
||||
},
|
||||
devtool: '#eval-source-map'
|
||||
}
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
module.exports.devtool = '#source-map'
|
||||
// http://vue-loader.vuejs.org/en/workflow/production.html
|
||||
module.exports.plugins = (module.exports.plugins || []).concat([
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': {
|
||||
NODE_ENV: '"production"'
|
||||
}
|
||||
}),
|
||||
new webpack.optimize.UglifyJsPlugin({
|
||||
sourceMap: true,
|
||||
compress: {
|
||||
warnings: false
|
||||
}
|
||||
}),
|
||||
new webpack.LoaderOptionsPlugin({
|
||||
minimize: true
|
||||
})
|
||||
])
|
||||
}
|
||||
@@ -27,6 +27,7 @@
|
||||
<module>org.openhab.ui.dashboard</module>
|
||||
<module>org.openhab.ui.basicui</module>
|
||||
<module>org.openhab.ui.classicui</module>
|
||||
<module>org.openhab.ui.homebuilder</module>
|
||||
<module>org.openhab.ui.paperui</module>
|
||||
</modules>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user