Skip to content
This repository has been archived by the owner on May 7, 2020. It is now read-only.

Commit

Permalink
Paper UI: modularize things view with smaller components (#6340)
Browse files Browse the repository at this point in the history
* Introduce Magic online/offline toggle thing
* Paper UI: listen for thing status changes, too
* Paper UI: refactor things view to component style
* Split things view into smaller components, add thing status changed listener

Signed-off-by: Henning Treu <henning.treu@telekom.de>
  • Loading branch information
htreu authored and kaikreuzer committed Oct 12, 2018
1 parent 558a8dc commit 493a0a1
Show file tree
Hide file tree
Showing 21 changed files with 366 additions and 155 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -180,4 +180,15 @@
</parameter>
</config-description>
</thing-type>

<thing-type id="online-offline">
<label>Magic Online Offline</label>
<description>An online/offline toggle thing</description>
<config-description>
<parameter name="toggleTime" type="integer" required="true">
<label>Toggle Time</label>
<description>The time in seconds to toggle between ONLINE and OFFLINE status.</description>
</parameter>
</config-description>
</thing-type>
</thing:thing-descriptions>
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ public class MagicBindingConstants {
public static final ThingTypeUID THING_TYPE_ROLLERSHUTTER = new ThingTypeUID(BINDING_ID, "rollershutter");
public static final ThingTypeUID THING_TYPE_PLAYER = new ThingTypeUID(BINDING_ID, "player");
public static final ThingTypeUID THING_TYPE_IMAGE = new ThingTypeUID(BINDING_ID, "image");
public static final ThingTypeUID THING_TYPE_ONLINE_OFFLINE = new ThingTypeUID(BINDING_ID, "online-offline");

// bridged things
public static final ThingTypeUID THING_TYPE_BRIDGE_1 = new ThingTypeUID(BINDING_ID, "magic-bridge1");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/**
* Copyright (c) 2014,2018 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.smarthome.magic.binding.handler;

import java.math.BigDecimal;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

import org.eclipse.smarthome.core.thing.ChannelUID;
import org.eclipse.smarthome.core.thing.Thing;
import org.eclipse.smarthome.core.thing.ThingStatus;
import org.eclipse.smarthome.core.thing.binding.BaseThingHandler;
import org.eclipse.smarthome.core.types.Command;

/**
* The {@link MagicOnlineOfflineHandler} is responsible for handling commands, which are
* sent to one of the channels.
*
* @author Henning Treu - Initial contribution
*/
public class MagicOnlineOfflineHandler extends BaseThingHandler {

private static final String TOGGLE_TIME = "toggleTime";

private ScheduledFuture<?> toggleJob;

public MagicOnlineOfflineHandler(Thing thing) {
super(thing);
}

@Override
public void handleCommand(ChannelUID channelUID, Command command) {
}

@Override
public void initialize() {
int toggleTime = ((BigDecimal) getConfig().get(TOGGLE_TIME)).intValue();

toggleJob = scheduler.scheduleWithFixedDelay(() -> {
if (getThing().getStatus() == ThingStatus.ONLINE) {
updateStatus(ThingStatus.OFFLINE);
} else if (getThing().getStatus() != ThingStatus.ONLINE) {
updateStatus(ThingStatus.ONLINE);
}
}, 0, toggleTime, TimeUnit.SECONDS);
}

@Override
public void dispose() {
if (toggleJob != null) {
toggleJob.cancel(true);
toggleJob = null;
}
super.dispose();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import org.eclipse.smarthome.magic.binding.handler.MagicImageHandler;
import org.eclipse.smarthome.magic.binding.handler.MagicLocationThingHandler;
import org.eclipse.smarthome.magic.binding.handler.MagicOnOffLightHandler;
import org.eclipse.smarthome.magic.binding.handler.MagicOnlineOfflineHandler;
import org.eclipse.smarthome.magic.binding.handler.MagicPlayerHandler;
import org.eclipse.smarthome.magic.binding.handler.MagicRolllershutterHandler;
import org.eclipse.smarthome.magic.binding.handler.MagicThermostatThingHandler;
Expand All @@ -55,7 +56,8 @@ public class MagicHandlerFactory extends BaseThingHandlerFactory {
THING_TYPE_ON_OFF_LIGHT, THING_TYPE_DIMMABLE_LIGHT, THING_TYPE_COLOR_LIGHT, THING_TYPE_CONTACT_SENSOR,
THING_TYPE_CONFIG_THING, THING_TYPE_DELAYED_THING, THING_TYPE_LOCATION, THING_TYPE_THERMOSTAT,
THING_TYPE_FIRMWARE_UPDATE, THING_TYPE_BRIDGE_1, THING_TYPE_BRIDGE_2, THING_TYPE_BRIDGED_THING,
THING_TYPE_CHATTY_THING, THING_TYPE_ROLLERSHUTTER, THING_TYPE_PLAYER, THING_TYPE_IMAGE);
THING_TYPE_CHATTY_THING, THING_TYPE_ROLLERSHUTTER, THING_TYPE_PLAYER, THING_TYPE_IMAGE,
THING_TYPE_ONLINE_OFFLINE);

@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
Expand Down Expand Up @@ -111,6 +113,9 @@ protected ThingHandler createHandler(Thing thing) {
if (thingTypeUID.equals(THING_TYPE_IMAGE)) {
return new MagicImageHandler(thing);
}
if (thingTypeUID.equals(THING_TYPE_ONLINE_OFFLINE)) {
return new MagicOnlineOfflineHandler(thing);
}

if (thingTypeUID.equals(THING_TYPE_BRIDGE_1) || thingTypeUID.equals(THING_TYPE_BRIDGE_2)) {
return new MagicBridgeHandler((Bridge) thing);
Expand Down
4 changes: 4 additions & 0 deletions extensions/ui/org.eclipse.smarthome.ui.paper/gulpfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ var paths = {
'./web-src/js/items/controller.metadata-parameter.dialog.js',
'./web-src/js/system/controller*.js',
'./web-src/js/things/things-module.js',
'./web-src/js/things/route-config.js',
'./web-src/js/things/component*.js',
'./web-src/js/things/controller*.js',
'./web-src/js/extensions/controller*.js',
'./web-src/js/rules/controller*.js',
Expand Down Expand Up @@ -264,6 +266,8 @@ gulp.task('inject', ['build'], function () {
'./web-src/js/items/controller.metadata-parameter.dialog.js',
'./web-src/js/system/controller*.js',
'./web-src/js/things/things-module.js',
'./web-src/js/things/route-config.js',
'./web-src/js/things/component*.js',
'./web-src/js/things/controller*.js',
'./web-src/js/extensions/controller*.js',
'./web-src/js/rules/controller*.js',
Expand Down
12 changes: 0 additions & 12 deletions extensions/ui/org.eclipse.smarthome.ui.paper/web-src/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,18 +52,6 @@ angular.module('PaperUI', [//
templateUrl : 'partials/services/configuration.multiService.html',
controller : 'MultiServicesController',
title : 'Configuration'
}).when('/configuration/things', {
templateUrl : 'partials/things/configuration.things.html',
controller : 'ThingController',
title : 'Configuration'
}).when('/configuration/things/view/:thingUID', {
templateUrl : 'partials/things/configuration.things.html',
controller : 'ThingController',
title : 'Configuration'
}).when('/configuration/things/edit/:thingUID', {
templateUrl : 'partials/things/configuration.things.html',
controller : 'ThingController',
title : 'Configuration'
}).when('/configuration/system', {
templateUrl : 'partials/system/system.configuration.html',
controller : 'SystemController',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ angular.module('PaperUI.services.repositories')//
});
if ((existing && mustExist) || (!existing && !mustExist)) {
$rootScope.$apply(function(scope) {
action(existing)
action(existing);
});
}
}
Expand All @@ -54,6 +54,7 @@ angular.module('PaperUI.services.repositories')//
existingThing.statusInfo = statusInfo;
});
});

eventService.onEvent('smarthome/things/*/added', function(topic, thing) {
updateInRepository(topic.split('/')[2], false, function(existingThing) {
repository.add(thing);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ angular.module('PaperUI.services', [ 'PaperUI.services.repositories', 'PaperUI.c
});
}).factory('eventService', function($resource, $log, restConfig) {

var callbacks = [];
var listeners = [];
var eventSrc;

var initializeEventService = function() {
Expand All @@ -39,10 +39,10 @@ angular.module('PaperUI.services', [ 'PaperUI.services.repositories', 'PaperUI.c
eventSrc.addEventListener('message', function(event) {
var data = JSON.parse(event.data);
$log.debug('Event received: ' + data.topic + ' - ' + data.payload);
angular.forEach(callbacks, function(element) {
var match = data.topic.match(element.topic);
angular.forEach(listeners, function(listener) {
var match = data.topic.match(listener.topic);
if (match != null && match == data.topic) {
element.callback(data.topic, JSON.parse(data.payload));
listener.callback(data.topic, JSON.parse(data.payload));
}
});
});
Expand All @@ -54,11 +54,19 @@ angular.module('PaperUI.services', [ 'PaperUI.services.repositories', 'PaperUI.c
return new function() {
this.onEvent = function(topic, callback) {
var topicRegex = topic.replace('/', '\/').replace('*', '.*');
callbacks.push({
listeners.push({
topic : topicRegex,
callback : callback
});
}

this.removeListener = function(topic) {
for ( var i in listeners) {
if (listeners[i]['topic'] === topic) {
listeners.splice(i, 1);
}
}
}
};
}).factory('toastService', function($mdToast, $rootScope) {
return new function() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<small class="badge" ng-class="$ctrl.statusInfo.status.toLowerCase()">{{$ctrl.statusInfo.status}}</small>
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
;
(function() {
'use strict';

angular.module('PaperUI.things').component('thingStatus', {
bindings : {
statusInfo : '<'
},
templateUrl : 'partials/things/component.thing.statusInfo.html'
});
})()
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<div class="thing fab-item text-left" data-thing-uid="{{$ctrl.thing.UID}}" ng-click="$ctrl.navigateTo('view/' + $ctrl.thing.UID)">
<div class="circle">{{$ctrl.thing.thingTypeUID?$ctrl.thing.thingTypeUID.split(':')[1].substring(0,1).toUpperCase():''}}</div>
<div class="cbody item-content">
<div class="description">
<h3>
{{$ctrl.thing.label}}
<thing-status status-info="$ctrl.thing.statusInfo"></thing-status>
</h3>
<p>{{$ctrl.getThingTypeLabel()}}</p>
<p>{{$ctrl.thing.UID}}</p>
</div>
<div class="actions">
<i class="material-icons" ng-click="$ctrl.navigateTo('edit/' + $ctrl.thing.UID)" aria-label="Edit">edit</i>
<i class="material-icons" ng-click="$ctrl.remove($ctrl.thing, $event)" aria-label="Delete">delete</i>
</div>
</div>
<hr class="border-line" ng-show="!$last" />
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
;
(function() {
'use strict';

angular.module('PaperUI.things').component('thingEntry', {
bindings : {
thing : '<'
},
templateUrl : 'partials/things/component.things-list-entry.html',
controller : ThingEntryController

});

ThingEntryController.$inject = [ '$location', '$timeout', '$mdDialog', 'thingTypeRepository', 'eventService' ];

function ThingEntryController($location, $timeout, $mdDialog, thingTypeRepository, eventService) {
var ctrl = this;
this.thingTypes = [];

this.getThingTypeLabel = getThingTypeLabel;
this.navigateTo = navigateTo;
this.remove = remove;

this.$onInit = activate;
this.$onDestroy = dispose;

function activate() {
eventService.onEvent('smarthome/things/' + ctrl.thing.UID + '/statuschanged', function(topic, statusInfo) {
$timeout(function() {
ctrl.thing.statusInfo = statusInfo[0];
}, 0);
});

return refreshThingTypes();
}

function dispose() {
eventService.removeListener('smarthome/things/' + ctrl.thing.UID + '/statuschanged');
}

function navigateTo(path) {
if (path.startsWith("/")) {
$location.path(path);
} else {
$location.path('configuration/things/' + path);
}
}

function getThingTypeLabel() {
var thingType = ctrl.thingTypes[ctrl.thing.thingTypeUID]
return thingType ? thingType.label : '';
}

function refreshThingTypes() {
return thingTypeRepository.getAll(function(thingTypes) {
angular.forEach(thingTypes, function(thingType) {
ctrl.thingTypes[thingType.UID] = thingType;
});
});
}

function remove(thing, event) {
event.stopImmediatePropagation();
$mdDialog.show({
controller : 'RemoveThingDialogController',
templateUrl : 'partials/things/dialog.removething.html',
targetEvent : event,
hasBackdrop : true,
locals : {
thing : thing
}
});
}
}

})()
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<section class="fill-height configMain">
<div class="things white-bg">
<div class="header-toolbar">
<md-button ng-click="$ctrl.refresh()" aria-label="Refresh"> <i class="material-icons">refresh</i></md-button>
</div>
<div class="section-header">
<div class="container">
<div class="toolbar">
<md-button class="md-fab" ng-click="$ctrl.navigateTo('/inbox/setup')" aria-label="Add"> <i class="material-icons">add</i></md-button>
</div>
</div>
</div>
<div class="search itemSearch" layout="row" layout-align="center center">
<div layout="row" flex="85" class="searchControl" layout-align="start center">
<i ng-style="{'font-size': '24px'}" class="material-icons">search</i>
<md-input-container flex="90" md-no-float class="md-block searchBox"> <input ng-model="searchText" type="text" placeholder="Search"> </md-input-container>
<i ng-click="searchText = undefined" ng-class="{'vhidden': !searchText}" ng-style="{'font-size': '24px'}" class="material-icons clickable">close</i>
</div>
<div layout="row" flex="15" class="controls" layout-align="start center" ng-init="showMore=false">
<i class="material-icons clickable" ng-click="showMore=!showMore">{{showMore?'unfold_less':'unfold_more'}}</i>
<button class="md-button clickable" ng-click="$ctrl.clearAll()">clear</button>
</div>
</div>
<div class="row" ng-show="showMore" class="searchBox" search-filters>
<md-autocomplete md-no-cache config='{"index":0,"targetField":"bindingType","sourceField":"id"}' class="col-xs-12 md-filter" md-min-length="0" md-selected-item="selectedOptions[0].value" md-item-text="binding.name" md-search-text="searchType" md-items="binding in searchInOptions($ctrl.bindings,['name'],searchType) | orderBy:'name'" placeholder="Filter by binding"> <md-item-template> <span md-highlight-text="searchType" md-highlight-flags="^i">{{binding.name}}</span> </md-item-template> <md-not-found> No matches found. </md-not-found> </md-autocomplete>
</div>
<div class="container thingContainer">
<p class="text-center" ng-show="$ctrl.things.length == 0">
No things configured yet.
<button class="md-button" ng-click="$ctrl.navigateTo('/inbox/setup')">Add Things</button>
</p>
<div class="things">
<div class="clickable" ng-class="{'new':newThingUID==thing.UID,'lastThing':$last}" ng-repeat="thing in $ctrl.things | filter:filterItems(['label','UID'])| orderBy:'label'">
<thing-entry thing="thing"></thing-entry>
</div>
</div>
</div>
</div>
</section>
Loading

0 comments on commit 493a0a1

Please sign in to comment.