Some updates to the HomeBuilder (#417)

* Added semantic labels to tags Floors, Rooms and objects
* Added a Cellar as a Floor
* Added support for more languages which where already available as translation
* Integrated the webpack build inside Maven
* Only allow minor updates in the package.json to make sure new version will not easily break the build, if semver fails again
* Remove dist folder because it should be generated through the build
* Switched from 'hidden-xs-up' to 'd-none' as proposed in bootstrap migration guide
* Switched from 'scope' to 'slot-scope' as suggested by vue 2.5 migration
* Added some additional parents in the vue schema because they were needed
* Removed osgi import from manifest
* Switched to a computed setup and the build in fieldMultiselect
* Switched to a 'computed' setup
* Remove the package fieldMultiselect and switch to the build-in
* Applied changes from review.
* Formatted JS files
* Re-introduced (customized) fieldMultiselect and minor fixes
* Re-introduce custom multiselect because this is the one that offers the icons
* Add the floor name to the label of room-based object selector
* Make sure that generating tags can be switched off
* Switched from floor-count to choosing custom floors
* Add no-save and corrected some js warnings

Signed-off-by: Martin van Wingerden <martin@martinvw.nl>
This commit is contained in:
Martin van Wingerden 2018-11-13 22:53:18 +01:00 committed by Wouter Born
parent 84d97270fd
commit deb2437a6b
19 changed files with 12324 additions and 5972 deletions

5
.gitignore vendored
View File

@ -4,5 +4,8 @@
/*features*/*/src/main/history/
maven-metadata-local.xml
dependency-reduced-pom.xml
**/node_modules/
npm-debug.log
bundles/org.openhab.ui.homebuilder/web/dist
bundles/org.openhab.ui.homebuilder/npm_cache
bundles/org.openhab.ui.homebuilder/web/node

View File

@ -17,7 +17,6 @@ Import-Package: com.google.gson,
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

View File

@ -13,5 +13,86 @@
<packaging>eclipse-plugin</packaging>
<name>openHAB HomeBuilder UI Dashboard Integration</name>
<build>
<plugins>
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.1.0</version>
<configuration>
<filesets>
<fileset>
<directory>${basedir}/web</directory>
<includes>
<include>node_modules/**</include>
<include>npm_cache/**</include>
<include>npm_tmp/**</include>
<include>dist/**</include>
</includes>
</fileset>
</filesets>
</configuration>
</plugin>
<plugin>
<groupId>com.github.eirslett</groupId>
<artifactId>frontend-maven-plugin</artifactId>
<version>1.0</version>
<configuration>
<nodeVersion>v10.4.0</nodeVersion>
<npmVersion>6.1.0</npmVersion>
<nodeDownloadRoot>http://nodejs.org/dist/</nodeDownloadRoot>
<npmDownloadRoot>http://registry.npmjs.org/npm/-/</npmDownloadRoot>
<environmentVariables>
<npm_config_cache>${project.basedir}/npm_cache</npm_config_cache>
<npm_config_tmp>${project.basedir}/npm_tmp</npm_config_tmp>
</environmentVariables>
<workingDirectory>web</workingDirectory>
</configuration>
<executions>
<execution>
<id>Install node and npm</id>
<goals>
<goal>install-node-and-npm</goal>
</goals>
</execution>
<execution>
<id>npm clean</id>
<goals>
<goal>install-node-and-npm</goal>
<goal>npm</goal>
</goals>
<phase>clean</phase>
<configuration>
<arguments>cache clean --force</arguments>
</configuration>
</execution>
<execution>
<id>npm install</id>
<goals>
<goal>npm</goal>
</goals>
<configuration>
<arguments>install --no-save</arguments>
</configuration>
</execution>
<execution>
<id>webpack build</id>
<goals>
<goal>npm</goal>
</goals>
<configuration>
<arguments>run build</arguments>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -12,16 +12,19 @@
},
"dependencies": {
"ascii-table": "0.0.9",
"bootstrap-vue": "^0.18.0",
"daemonite-material": "^4.0.0-alpha.6",
"bootstrap-vue": "~0.18.0",
"daemonite-material": "~4.1.1",
"lodash": "^4.17.4",
"pickadate": "^3.5.6",
"popper.js": "^1.14.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"
"vue": "~2.5.17",
"vue-form-generator": "~2.3.1",
"vue-i18n": "~7.8.1",
"vue-multiselect": "~2.1.3",
"vue-resource": "~1.5.1",
"vue-style-loader": "~1.0.0"
},
"devDependencies": {
"babel-core": "^6.0.0",
@ -30,6 +33,8 @@
"cross-env": "^3.0.0",
"css-loader": "^0.25.0",
"file-loader": "^0.9.0",
"jquery": "^1.12.4",
"lodash-webpack-plugin": "^0.11.5",
"node-sass": "^4.5.0",
"sass-loader": "^5.0.1",
"style-loader": "^0.18.2",

View File

@ -1,21 +1,21 @@
<template>
<div>
<app-nav></app-nav>
<div class="container" id="app">
<app-form></app-form>
</div>
</div>
<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'
import AppNav from './AppNav.vue'
import AppForm from './AppForm.vue'
export default {
name: 'app',
components: {
AppNav,
AppForm
}
}
export default {
name: 'app',
components: {
appNav: AppNav,
appForm: AppForm
}
}
</script>

View File

@ -1,201 +1,226 @@
<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>
<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 d-none'><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('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>
<collapse v-if="model && model.filesGenerated.includes('habpanel')" :uid="'habpanel'" :heading="'HABPanel Dashboard'"
:content='generateDashboard(model)'></collapse>
</affix>
</div>
</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'
import * as _ from 'lodash'
import Collapse from './Collapse.vue'
import {component as VueFormGenerator} from 'vue-form-generator'
import {floors, languages, objects, OBJECTS_SUFFIX, rooms} from './definitions'
import * as schema from './formSchema'
import {roomsSelect} from './formSchema'
import {objectSelect} from './formSchema'
import {generateItems} from './textItems'
import {generateItemsJson} from './restItems'
import {generateDashboard} from './habpanel'
import {generateSitemap} from './sitemap'
export default {
components: {
VueFormGenerator,
Collapse
},
export default {
components: {
vueFormGenerator: VueFormGenerator,
collapse: Collapse
},
data() {
return {
model: {
language: 'en-UK',
homeName: 'Our Home',
filesGenerated: ['items', 'sitemap'],
itemsType: 'text',
itemsChannel: true,
itemsIcons: true,
floorsCount: 1,
GroundFloor: []
},
data() {
return {
model: {
language: 'en-UK',
homeName: 'Our Home',
filesGenerated: ['items', 'sitemap'],
itemsType: 'text',
itemsChannel: true,
itemsIcons: true,
itemsTags: true,
floors: []
},
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]];
formOptions: {
validateAfterLoad: true,
validateAfterChanged: true,
fieldIdPrefix: 'user-'
}
}
}.bind(this));
},
if (!_.isEmpty(roomsModel)) {
_.forOwn(roomsModel, (room) => { stack = [...stack, ...room] });
}
computed: {
schema() {
let roomsFields = [];
this.model.floors.forEach((floor) => {
roomsFields.push(roomsSelect(floor.value, floor.name || floor.value));
});
stack.forEach(item => {
if (!item.custom) {
item.name = this.$i18n.t(item.value);
let objectsFields = [];
this.model.floors.forEach((floor) => {
if (this.model[floor.value]) {
this.model[floor.value].forEach((element) => {
let name = (element.name || element.value) + ' (' + floor.name + ')';
let fieldName = floor.value + '_' + element.value + OBJECTS_SUFFIX;
objectsFields.push(objectSelect(fieldName, name));
});
}
});
return {
groups: [
{legend: '', fields: schema.basicFields},
{legend: 'Floors', fields: schema.floorsFields},
{legend: 'Rooms', fields: roomsFields},
{legend: 'Objects', fields: objectsFields},
{legend: '', fields: schema.settingsFields}
]
};
}
});
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';
methods: {
/**
* Generates textual Items
*/
generateItems,
previewEl.style.width = formWidth + 'px';
/**
* Generates JSON array of Items,
* understandable by REST API
*/
generateItemsJson,
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);
}
},
},
/**
* Generates textual Sitemap
*/
generateSitemap,
http: {
root: window.location.origin + '/rest/'
},
/**
* Generates HABPanel dashboard JSON config
*/
generateDashboard,
mounted: function() {
this.getLocale();
/**
* 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;
this.$nextTick(function() {
window.addEventListener('resize', this.resizeAffix);
this.resizeAffix();
})
},
if (body.language && body.region) {
let langId = body.language + '-' + body.region;
let lang = _.find(languages, {id: langId});
selectedLang = lang ? langId : DEFAULT_LOCALE;
}
watch: {
locale(val) {
this.$i18n.locale = val
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 = 'd-none';
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);
}
}
},
beforeDestroy() {
window.removeEventListener('resize', this.resizeAffix);
}
}
</script>
</script>

View File

@ -1,41 +1,41 @@
<template>
<nav class="navbar fixed-top">
<a class="navbar-brand mr-auto" href="#">HomeBuilder</a>
<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">&times;</span>
</button>
<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 d-none">&times;</span>
</button>
</nav>
</nav>
</template>
<style lang="scss">
.navbar {
background: #E64A19;
color: #fff;
}
.navbar {
background: #E64A19;
color: #fff;
}
.navbar-toggler {
display: none;
}
.navbar-toggler {
display: none;
}
.navbar-close-icon {
color: #200f04;
font-size: 36px;
padding-left: 1px;
margin-top: -8px;
}
.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');
export default {
methods: {
togglePreview() {
document.getElementById('preview').classList.toggle('d-none');
document.querySelector('.navbar-close-icon').classList.toggle('d-none');
document.querySelector('.navbar-toggler-icon').classList.toggle('d-none');
}
}
}
}
}
</script>

View File

@ -6,12 +6,14 @@
<div class="text-center"
v-if="uid === 'items' && content instanceof Array">
<button @click="createItems"
class="btn btn-create-items btn-outline-info">{{ createText }}</button>
class="btn btn-create-items btn-outline-info">{{ createText }}
</button>
</div>
<button @click="copy"
v-if="!(uid === 'items' && content instanceof Array)"
class="btn-clipboard"
title="Copy to clipboard">{{ copyText }}</button>
title="Copy to clipboard">{{ copyText }}
</button>
<span class="pb-1" v-if="uid === 'habpanel'">
Paste the content in
@ -25,69 +27,69 @@
</template>
<style lang="scss">
.card-block {
padding: 0.5rem !important;
.card-block {
padding: 0.5rem !important;
pre {
margin-top: 0;
pre {
margin-top: 0;
}
}
}
</style>
<script>
import Vue from 'vue'
import Clipboard from 'v-clipboard'
import Vue from 'vue'
import Clipboard from 'v-clipboard'
Vue.use(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(/&lt;/g, '<')
.replace(/&gt;/g, '>');
}
this.$clipboard(content);
this.copyText = 'Copied!';
setTimeout(() => {
this.copyText = 'Copy';
}, 500);
export default {
props: ['uid', 'heading', 'content'],
data: () => ({
copyText: 'Copy',
createText: 'Create Items'
}),
http: {
root: window.location.origin + '/rest/'
},
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!';
}
methods: {
copy() {
let content = this.content;
setTimeout(() => {
this.createText = 'Create Items';
}, 500);
});
if (typeof content === 'object') {
content = JSON.stringify(this.content, null, 2);
} else {
content = content
.replace(/&lt;/g, '<')
.replace(/&gt;/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>

View File

@ -2,89 +2,95 @@
* 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' }
{name: 'Czech', id: 'cs-CZ'},
{name: 'Dutch', id: 'nl-NL'},
{name: 'English', id: 'en-UK'},
{name: 'French', id: 'fr-FR'},
{name: 'German', id: 'de-DE'},
{name: 'Hungarian', id: 'hu-HU'},
{name: 'Italian', id: 'it-IT'},
{name: 'Korean', id: 'ko-KR'},
{name: 'Polish', id: 'pl-PL'},
{name: 'Portuguese (BR)', id: 'pt-BR'},
{name: 'Russian', id: 'ru-RU'},
{name: 'Spanish', id: 'es-ES'},
{name: 'Tagalog (Philippines)', id: 'tl-PH'},
{name: 'Turkish', id: 'tr-TR'}
];
/**
* 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' }
{abbr: 'C', value: 'Cellar', icon: 'cellar', tags: ['Basement']},
{abbr: 'OU', value: 'Outside', icon: 'garden', tags: ['Outdoor']},
{abbr: 'GF', value: 'GroundFloor', icon: 'groundfloor', tags: ['GroundFloor']},
{abbr: 'FF', value: 'FirstFloor', icon: 'firstfloor', tags: ['FirstFloor']},
{abbr: 'F2', value: 'SecondFloor', icon: 'attic', tags: ['Attic']},
{abbr: 'F3', value: 'ThirdFloor', icon: 'attic', tags: ['Attic']},
{abbr: 'F4', value: 'FourthFloor', icon: 'attic', tags: ['Attic']},
{abbr: 'AT', value: 'Attic', icon: 'attic', tags: ['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' }
{value: 'Balcony', icon: '', tags: ['Outdoor']},
{value: 'Backyard', icon: 'lawnmower', tags: ['Garden']},
{value: 'Basement', icon: 'cellar', tags: ['Basement']},
{value: 'Bathroom', icon: 'bath', tags: ['Bathroom']},
{value: 'Bedroom', icon: 'bedroom', tags: ['Bedroom']},
{value: 'Boiler', icon: 'gas', tags: ['Room']},
{value: 'Wardrobe', icon: 'wardrobe', tags: []},
{value: 'Cellar', icon: 'cellar', tags: ['Cellar']},
{value: 'Corridor', icon: 'corridor', tags: ['Corridor']},
{value: 'Deck', icon: '', tags: ['Terrace']},
{value: 'Dining', icon: '', tags: ['Room']},
{value: 'Driveway', icon: '', tags: ['Outdoor']},
{value: 'Entryway', icon: 'frontdoor', tags: ['Room']},
{value: 'FamilyRoom', icon: 'parents_2_4', tags: ['Room']},
{value: 'FrontYard', icon: 'lawnmower', tags: ['Garden']},
{value: 'Garage', icon: 'garage', tags: ['Garage']},
{value: 'GuestHouse', icon: 'house', tags: ['House']},
{value: 'GuestRoom', icon: 'parents_4_3', tags: ['Room']},
{value: 'Hallway', icon: 'corridor', tags: ['Corridor']},
{value: 'HomeCinema', icon: 'screen', tags: ['Room']},
{value: 'KidsRoom', icon: 'girl_3', tags: ['Room']},
{value: 'Kitchen', icon: 'kitchen', tags: ['Kitchen']},
{value: 'LaundryRoom', icon: 'washingmachine', tags: ['Room']},
{value: 'Library', icon: 'office', tags: ['Room']},
{value: 'LivingRoom', icon: 'sofa', tags: ['LivingRoom']},
{value: 'LivingDining', icon: 'sofa', tags: ['LivingRoom']},
{value: 'Loft', icon: 'attic', tags: ['Room']},
{value: 'Lounge', icon: 'sofa', tags: ['Room']},
{value: 'MasterBedroom', icon: 'bedroom_red', tags: ['Bedroom']},
{value: 'NannyRoom', icon: 'woman_1', tags: ['Room']},
{value: 'Office', icon: 'office', tags: ['Room']},
{value: 'Outside', icon: 'garden', tags: ['Outside']},
{value: 'Patio', icon: 'terrace', tags: ['Outside']},
{value: 'Porch', icon: 'group', tags: ['Outside']},
{value: 'Stairwell', icon: 'qualityofservice', tags: []},
{value: 'StorageRoom', icon: 'suitcase', tags: ['Room']},
{value: 'Studio', icon: 'pantry', tags: ['Room']},
{value: 'Shed', icon: 'greenhouse', tags: ['Garage']},
{value: 'Toilet', icon: 'toilet', tags: ['Bathroom']},
{value: 'Terrace', icon: 'terrace', tags: ['Terrace']}
];
/**
* 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 %%]' },
{value: 'Light', icon: 'light', type: 'Switch:OR(ON, OFF)', unit: '[(%d)]', tags: ['Lighting', 'Switchable']},
{value: 'Window', icon: 'window', type: 'Contact:OR(OPEN, CLOSED)', unit: '[MAP(en.map):%s]', tags: ['Window']},
{value: 'Door', icon: 'door', type: 'Contact:OR(OPEN, CLOSED)', unit: '[MAP(en.map):%s]', tags: ['Door']},
{value: 'Motion', icon: 'motion', type: 'Switch:OR(ON, OFF)', unit: '[(%d)]', tags: ['MotionDetector', 'Switchable']},
{value: 'Power', icon: 'poweroutlet', type: 'Switch:OR(ON, OFF)', unit: '[(%d)]', tags: ['Switch', 'Switchable']},
{value: 'Shutter', icon: 'rollershutter', type: 'Rollershutter:OR(UP, DOWN)', unit: '[(%d)]', tags: ['Rollershutter']},
{value: 'Blind', icon: 'blinds', type: 'Dimmer', unit: '[%d %%]', tags: ['Blinds', 'Switchable']},
{value: 'Fan', icon: 'fan_ceiling', type: 'Switch:OR(ON, OFF)', unit: '[(%d)]', tags: ['Switchable']},
{value: 'AirCon', icon: 'snow', type: 'Switch:OR(ON, OFF)', unit: '[(%d)]', tags: ['HVAC', 'Switchable']},
{value: 'Heating', icon: 'heating', type: 'Number:AVG', unit: '[%.1f °C]', tags: ['HVAC']},
{value: 'Temperature', icon: 'temperature', type: 'Number:AVG', unit: '[%.1f °C]', tags: ['Temperature']},
{value: 'Humidity', icon: 'humidity', type: 'Number:AVG', unit: '[%d %%]', tags: ['Humidity']}
];
export const OBJECTS_SUFFIX = '_objects';

View File

@ -1,25 +1,25 @@
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'
import {validators} from 'vue-form-generator'
import {floors, languages, objects, OBJECTS_SUFFIX, rooms} 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);
this.$parent.$parent.$parent.fetchTranslations(newVal);
}
/**
* Creates a custom room entry
*
* @param {string} newTag
* @param {string} id
* @param {Object} options
* @param {string} value
*
* @param {string} newTag
* @param {string} id
* @param {Object} options
* @param {string} value
*/
function newRoomTag(newTag, id, options, value) {
const tag = {
@ -32,15 +32,17 @@ function newRoomTag(newTag, id, options, value) {
.cleanDiacritics()
.classify()
.value()
}
};
rooms.push(tag);
value.push(tag);
};
if (value) {
value.push(tag);
}
}
/**
* Returns a group function for a given
* Item's type
* @param {*} type
* @param {*} type
*/
function getGroupFunc(type) {
let func = '';
@ -67,11 +69,11 @@ function getGroupFunc(type) {
/**
* Creates a custom object entry
*
* @param {string} newTag
* @param {string} id
* @param {Object} options
* @param {string} value
*
* @param {string} newTag
* @param {string} id
* @param {Object} options
* @param {string} value
*/
function newObjectTag(newTag, id, options, value) {
let split = newTag.split(':');
@ -91,65 +93,71 @@ function newObjectTag(newTag, id, options, value) {
.cleanDiacritics()
.classify()
.value()
}
};
objects.push(tag);
value.push(tag);
};
if (value) {
value.push(tag);
}
}
/**
* Is being executed when
* collection of rooms in floor multiselect field
* Creates a custom floor entry
*
* @param {string} newTag
* @param {string} id
* @param {Object} options
* @param {string} value
*/
function newFloorTag(newTag, id, options, value) {
const tag = {
abbr: s(newTag)
.trim()
.toUpperCase()
.cleanDiacritics()
.substr(0, 1)
.value(),
name: newTag,
icon: 'none',
custom: true,
value: s(newTag)
.trim()
.toLowerCase()
.cleanDiacritics()
.classify()
.value()
};
floors.push(tag);
if (value) {
value.push(tag);
}
}
/**
* Is being executed when
* collection of rooms in floor vueMultiSelect 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 {Object} model
* @param {Array} newVal
* @param {Array} oldVal
* @param {Object} field
* @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
});
}
let floor = field.model;
if (lastRemoved) {
roomName = floor + '_' + lastRemoved + OBJECTS_SUFFIX;
_.remove(objectsFields, { model: roomName });
let roomName = floor + '_' + lastRemoved + OBJECTS_SUFFIX;
delete model[roomName];
}
}
@ -158,11 +166,12 @@ function roomsChanged(model, newVal, oldVal, field) {
* Schema describing the basic form
* generated by vue-form-generator
*/
export var basicFields = [{
export var basicFields = [
{
type: 'select',
model: 'language',
label: 'Please select your language',
values: function() {
values: function () {
return languages;
},
selectOptions: {
@ -170,7 +179,6 @@ export var basicFields = [{
},
onChanged: languageChanged
},
{
type: 'input',
inputType: 'text',
@ -184,36 +192,42 @@ export var basicFields = [{
];
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);
type: 'multiselect',
label: 'Floors',
styleClasses: 'rooms-list',
model: 'floors',
values: floors,
placeholder: 'Type to search or add floor',
selectOptions: {
multiple: true,
hideSelected: true,
closeOnSelect: false,
selectLabel: '',
trackBy: 'value',
label: 'name',
searchable: true,
taggable: true,
tagPlaceholder: 'Add this as a new floor',
onNewTag: newFloorTag
},
onChanged: function (model, newVal, oldVal, field) {
let oldList = oldVal ? _.map(oldVal, 'value') : [];
let newList = _.map(newVal, 'value');
let lastRemoved = _.first(_.difference(oldList, newList));
if (!fieldExists) {
roomsFields.push(roomsSelect(floorName, floor.name || floor.value));
if (lastRemoved) {
delete model[lastRemoved];
for (let property in model) {
if (model.hasOwnProperty(property) && property.startsWith(lastRemoved + '_')) {
delete model[property];
}
}
}
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 = [{
export var settingsFields = [
{
type: 'checklist',
model: 'filesGenerated',
label: 'What would you like to generate?',
@ -221,9 +235,9 @@ export var settingsFields = [{
listBox: true,
multiSelect: true,
values: [
{ name: 'Items', value: 'items' },
{ name: 'Sitemap', value: 'sitemap' },
{ name: 'Dashboard', value: 'habpanel' }
{name: 'Items', value: 'items'},
{name: 'Sitemap', value: 'sitemap'},
{name: 'Dashboard', value: 'habpanel'}
]
},
@ -235,8 +249,8 @@ export var settingsFields = [{
return model && model.filesGenerated.includes('items');
},
values: [
{ name: 'Textual Configuration Files', value: 'text' },
{ name: 'Internal Database', value: 'rest' }
{name: 'Textual Configuration Files', value: 'text'},
{name: 'Internal Database', value: 'rest'}
]
},
@ -275,7 +289,7 @@ export var settingsFields = [{
type: 'switch',
label: 'Include tags',
model: 'itemsTags',
default: false,
default: true,
textOn: 'Yes',
textOff: 'No',
valueOn: true,
@ -287,13 +301,12 @@ export var settingsFields = [{
];
/**
* Generates a multiselect input with
* rooms for specific floor (model)
*
* @param {string} model
* @param {string} label
* Generates a vueMultiSelect input with rooms for specific floor (model)
*
* @param {string} model
* @param {string} label
*/
function roomsSelect(model, label) {
export function roomsSelect(model, label) {
return {
type: 'multiselect',
label: label,
@ -318,13 +331,30 @@ function roomsSelect(model, label) {
}
/**
* Field group schema for the rooms
* Generates a vueMultiSelect input with objects for specific rooms (model)
*
* @param {string} model
* @param {string} label
*/
export var roomsFields = [
roomsSelect('GroundFloor', 'Ground Floor')
];
/**
* Field group schema for the objects
*/
export var objectsFields = [];
export function objectSelect(model, label) {
return {
type: 'multiselect',
label: label,
styleClasses: 'rooms-list',
model: model,
values: objects,
placeholder: 'Type to search or add object',
selectOptions: {
multiple: true,
hideSelected: true,
closeOnSelect: false,
selectLabel: '',
trackBy: 'value',
label: 'name',
searchable: true,
taggable: true,
tagPlaceholder: 'Add this as a new object',
onNewTag: newObjectTag
}
};
}

View File

@ -62,10 +62,8 @@ function getWidgetType(type) {
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.floors.forEach((floor) => {
if (!_.isUndefined(model[floor.value])) {
model[floor.value].forEach(function(room) {
let roomObjects = floor.value + '_' + room.value + OBJECTS_SUFFIX;
let objectCollection = model[roomObjects] || [];
@ -73,7 +71,7 @@ function makeWidgets(object, model) {
if (obj) {
widgets.push(makeWidget({
item: (model.floorsCount > 1 ? floor.abbr + '_' : '') + room.value + '_' + obj.value,
item: (model.floors.length > 1 ? floor.abbr + '_' : '') + room.value + '_' + obj.value,
name: room.name,
type: getWidgetType(_.first(object.type.split(':'))),
row: _.chunk(widgets, 6).length - 1,
@ -83,10 +81,11 @@ function makeWidgets(object, model) {
}
});
}
}
});
return widgets;
};
}
/**
* Generates a full HABPanel dashboard set

View File

@ -29,4 +29,3 @@ const vm = new Vue({
el: '#app',
render: h => h(App)
});

View File

@ -1,22 +1,18 @@
import * as _ from 'lodash'
import { floors, objects, OBJECTS_SUFFIX } from './definitions'
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;
return [{
type: 'Group',
name: floor.abbr,
label: floor.name || floor.value,
category: model.itemsIcons ? floor.icon : '',
groupNames: ['Home'],
entryType: 'floor',
tags: getTags(model, floor)
}];
}
export function addRooms(floor, model) {
@ -25,7 +21,7 @@ export function addRooms(floor, model) {
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 + '_' : '';
let floorPrefix = model.floors.length > 1 ? floor.abbr + '_' : '';
items.push({
type: 'Group',
@ -34,9 +30,10 @@ export function addRooms(floor, model) {
category: model.itemsIcons ? room.icon : '',
groupNames: _.compact([
'Home',
model.floorsCount > 1 ? floor.abbr : ''
model.floors.length > 1 ? floor.abbr : ''
]),
entryType: 'room'
entryType: 'room',
tags: getTags(model, room)
});
items = [
@ -65,23 +62,23 @@ export function addObjects(room, model, floorPrefix, roomObjects) {
floorPrefix + room.value,
GROUP_PREFIX + object.value
],
tags: addTags(object, model),
tags: getTags(model, object),
entryType: 'object'
}));
}
/**
* Generates a list of object groups
*
* @param {Object} model
*
* @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 });
chosenObjects.forEach(function (dev) {
let object = _.find(objects, {value: dev});
if (object) {
let type = object.type.split(':');
@ -98,7 +95,8 @@ export function addObjectGroups(model) {
category: model.itemsIcons ? object.icon : '',
groupNames: ['Home'],
groupType: groupType,
entryType: 'objectGroup'
entryType: 'objectGroup',
tags: getTags(model, object)
};
if (groupFuncName) {
@ -123,8 +121,8 @@ export function addObjectGroups(model) {
/**
* Gets list of objects chosen
* from collection
*
* @param {*} model
*
* @param {*} model
* @return {Array}
*/
export function getChosenObjects(model) {
@ -136,41 +134,8 @@ export function getChosenObjects(model) {
.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 : [];
function getTags(model, entry) {
return model.itemsTags ? entry.tags || [] : [];
}
export function getItems(model) {
@ -179,23 +144,23 @@ export function getItems(model) {
name: 'Home',
label: model.homeName,
category: model.itemsIcons ? 'house' : '',
entryType: 'home'
entryType: 'home',
tags: model.itemsTags ? ['Building'] : []
}];
for (var i = 0; i < model.floorsCount; i++) {
var floor = floors[i];
model.floors.forEach((floor) => {
items = [
...items,
...addFloors(floor, model),
...addRooms(floor, model)
];
}
});
items = [
...items,
...addObjectGroups(model)
]
];
return items;
}
@ -204,8 +169,8 @@ export function getItems(model) {
* Returns array of Items
* without `entryType` which is not a valid parameter
* for the request
*
* @param {*} model
*
* @param {*} model
*/
export function generateItemsJson(model) {
let items = getItems(model);

View File

@ -8,7 +8,7 @@ export let sitemapName = '';
function getFloorItem(floor, model) {
let floorFrame = 'Frame ';
if (model.floorsCount > 1) {
if (model.floors.length > 1) {
let icon = model.itemsIcons && floor.icon ? '" icon="' + floor.icon : '';
floorFrame += 'label="' + (floor.name || floor.value) + icon + '" ';
}
@ -29,11 +29,10 @@ function getRoomGroups(floor, model, floorPrefix) {
function addFloorFrames(model) {
let lines = [];
for (let i = 0; i < model.floorsCount; i++) {
let floor = floors[i];
model.floors.forEach((floor) => {
let floorPrefix = '';
if (model.floorsCount > 1) {
if (model.floors.length > 1) {
floorPrefix = floor.abbr + '_';
}
@ -44,10 +43,8 @@ function addFloorFrames(model) {
s.pad(' ', 4) + '}'
];
if (i < model.floorsCount) {
lines.push('');
}
}
lines.push('');
});
return lines;
}

View File

@ -1,14 +1,14 @@
import * as _ from 'lodash'
import * as AsciiTable from 'ascii-table'
import { getItems } from './restItems'
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
*
* @param {Object} item
* @param {Object} model
* @return {string}
*/
function generateType(item) {
@ -27,7 +27,7 @@ function generateType(item) {
/**
* Generates a label for the Item
* @param {Object} item
* @param {Object} item
* @return {string}
*/
function generateLabel(item) {
@ -36,9 +36,9 @@ function generateLabel(item) {
/**
* Generates an icon if there's any.
*
* @param {Object} item
* @param {Object} model
*
* @param {Object} item
* @param {Object} model
* @return {string}
*/
function generateIcon(item, model) {
@ -80,8 +80,8 @@ function generateChannel(item, model) {
/**
* Generates an array or items
* to be later processed by AsciiTable
*
* @param {*} items
*
* @param {*} items
*/
function generateTextualItems(items, model) {
let result = items.map(item => {
@ -105,17 +105,17 @@ function generateTextualItems(items, model) {
}
/**
* Generates an array or items
* Generates an array or items
* for a given type
*
* @param {string} entryType
* @param {Object} model
*
* @param {string} entryType
* @param {Object} model
* @return {Array}
*/
function getItemsOfType(entryType, model) {
let allItems = getItems(model);
let items = _(allItems)
.filter({ entryType: entryType })
.filter({entryType: entryType})
.uniq()
.value() || [];
@ -125,7 +125,7 @@ function getItemsOfType(entryType, model) {
/**
* Transforms array of lines
* into column-aligned table
* @param {Array} lines
* @param {Array} lines
*/
function toTable(lines) {
let table = new AsciiTable();
@ -149,7 +149,7 @@ function toTable(lines) {
* Generates a textual Items file based on user input
* Needs several datapoints from the model such as
* floorsCount, rooms collection, objects etc.
* @param {*} model
* @param {*} model
* @return {string}
*/
export function generateItems(model) {

View File

@ -1,5 +1,5 @@
var path = require('path')
var webpack = require('webpack')
var path = require('path');
var webpack = require('webpack');
module.exports = {
entry: './src/main.js',
@ -59,10 +59,10 @@ module.exports = {
hints: false
},
devtool: '#eval-source-map'
}
};
if (process.env.NODE_ENV === 'production') {
module.exports.devtool = '#source-map'
module.exports.devtool = '#source-map';
// http://vue-loader.vuejs.org/en/workflow/production.html
module.exports.plugins = (module.exports.plugins || []).concat([
new webpack.DefinePlugin({