Merge remote-tracking branch 'origin/master' into develop/2.5
This commit is contained in:
commit
7c24b850fb
@ -330,13 +330,13 @@
|
||||
"name": "Web Camera Input",
|
||||
"descriptor": {
|
||||
"type": "latest",
|
||||
"sizeX": 9.5,
|
||||
"sizeY": 6.5,
|
||||
"sizeX": 7.5,
|
||||
"sizeY": 3,
|
||||
"resources": [],
|
||||
"templateHtml": "<tb-web-camera-widget ctx=\"ctx\">\n</tb-web-camera-widget>",
|
||||
"templateCss": "#container {\n overflow: auto;\n}\n\n.tbDatasource-container {\n margin: 5px;\n padding: 8px;\n}\n\n.tbDatasource-title {\n font-size: 1.200rem;\n font-weight: 500;\n padding-bottom: 10px;\n}\n\n.tbDatasource-table {\n width: 100%;\n box-shadow: 0 0 10px #ccc;\n border-collapse: collapse;\n white-space: nowrap;\n font-size: 1.000rem;\n color: #757575;\n}\n\n.tbDatasource-table td {\n position: relative;\n border-top: 1px solid rgba(0, 0, 0, 0.12);\n border-bottom: 1px solid rgba(0, 0, 0, 0.12);\n padding: 0px 18px;\n box-sizing: border-box;\n}",
|
||||
"templateCss": "",
|
||||
"controllerScript": "self.onInit = function() {\n var scope = self.ctx.$scope;\n scope.ctx = self.ctx;\n}\n\nself.onDataUpdated = function() {\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1\n }\n}\n\nself.onDestroy = function() {\n}\n",
|
||||
"settingsSchema": "{}",
|
||||
"settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Web Camera\",\n \"properties\": {\n \"widgetTitle\": {\n \"title\": \"Widget title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"imageFormat\": {\n \"title\": \"Image Format\",\n \"type\": \"string\",\n \"default\": \"image/png\"\n },\n \"imageQuality\":{\n \"title\":\"Image quality that use lossy compression such as jpeg and webp\",\n \"type\":\"number\",\n \"default\": 0.92,\n \"min\": 0,\n \"max\": 1\n },\n \"maxWidth\": {\n \"title\": \"The maximal image width\",\n \"type\": \"number\",\n \"default\": 640\n }, \n \"maxHeight\": {\n \"title\": \"The maximal image heigth\",\n \"type\": \"number\",\n \"default\": 480\n }\n },\n \"required\": []\n },\n \"form\": [\n \"widgetTitle\",\n {\n \"key\": \"imageFormat\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"image/jpeg\",\n \"label\": \"JPEG\"\n },\n {\n \"value\": \"image/png\",\n \"label\": \"PNG\"\n },\n {\n \"value\": \"image/webp\",\n \"label\": \"WEBP\"\n }\n ]\n },\n \"imageQuality\",\n \"maxWidth\",\n \"maxHeight\"\n ]\n}",
|
||||
"dataKeySettingsSchema": "{}\n",
|
||||
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Web Camera Input\",\"showTitleIcon\":false,\"titleIcon\":\"more_horiz\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"displayTimewindow\":true,\"showLegend\":false,\"actions\":{}}"
|
||||
}
|
||||
|
||||
@ -201,6 +201,7 @@ public class AuthController extends BaseController {
|
||||
@ResponseBody
|
||||
public JsonNode activateUser(
|
||||
@RequestBody JsonNode activateRequest,
|
||||
@RequestParam(required = false, defaultValue = "true") boolean sendActivationMail,
|
||||
HttpServletRequest request) throws ThingsboardException {
|
||||
try {
|
||||
String activateToken = activateRequest.get("activateToken").asText();
|
||||
@ -215,10 +216,12 @@ public class AuthController extends BaseController {
|
||||
String loginUrl = String.format("%s/login", baseUrl);
|
||||
String email = user.getEmail();
|
||||
|
||||
try {
|
||||
mailService.sendAccountActivatedEmail(loginUrl, email);
|
||||
} catch (Exception e) {
|
||||
log.info("Unable to send account activation email [{}]", e.getMessage());
|
||||
if (sendActivationMail) {
|
||||
try {
|
||||
mailService.sendAccountActivatedEmail(loginUrl, email);
|
||||
} catch (Exception e) {
|
||||
log.info("Unable to send account activation email [{}]", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
JwtToken accessToken = tokenFactory.createAccessJwtToken(securityUser);
|
||||
|
||||
@ -277,6 +277,7 @@ public class DefaultMailService implements MailService {
|
||||
} else {
|
||||
message = exception.getMessage();
|
||||
}
|
||||
log.warn("Unable to send mail: {}", message);
|
||||
return new ThingsboardException(String.format("Unable to send mail: %s", message),
|
||||
ThingsboardErrorCode.GENERAL);
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -435,6 +435,10 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
|
||||
if (user.authority === 'CUSTOMER_USER') {
|
||||
entityId.id = user.customerId;
|
||||
}
|
||||
} else if (entityType === types.aliasEntityType.current_tenant){
|
||||
let user = userService.getCurrentUser();
|
||||
entityId.entityType = types.entityType.tenant;
|
||||
entityId.id = user.tenantId;
|
||||
}
|
||||
return entityId;
|
||||
}
|
||||
@ -806,6 +810,7 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
|
||||
entityTypes.dashboard = types.entityType.dashboard;
|
||||
if (useAliasEntityTypes) {
|
||||
entityTypes.current_customer = types.aliasEntityType.current_customer;
|
||||
entityTypes.current_tenant = types.aliasEntityType.current_tenant;
|
||||
}
|
||||
break;
|
||||
case 'CUSTOMER_USER':
|
||||
|
||||
@ -85,9 +85,12 @@ function LoginService($http, $q) {
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function activate(activateToken, password) {
|
||||
function activate(activateToken, password, sendActivationMail) {
|
||||
var deferred = $q.defer();
|
||||
var url = '/api/noauth/activate';
|
||||
if(sendActivationMail === true || sendActivationMail === false) {
|
||||
url += '?sendActivationMail=' + sendActivationMail;
|
||||
}
|
||||
$http.post(url, {activateToken: activateToken, password: password}).then(function success(response) {
|
||||
deferred.resolve(response);
|
||||
}, function fail() {
|
||||
|
||||
@ -408,7 +408,8 @@ export default angular.module('thingsboard.types', [])
|
||||
}
|
||||
},
|
||||
aliasEntityType: {
|
||||
current_customer: "CURRENT_CUSTOMER"
|
||||
current_customer: "CURRENT_CUSTOMER",
|
||||
current_tenant: "CURRENT_TENANT"
|
||||
},
|
||||
entityTypeTranslations: {
|
||||
"DEVICE": {
|
||||
@ -474,6 +475,10 @@ export default angular.module('thingsboard.types', [])
|
||||
"CURRENT_CUSTOMER": {
|
||||
type: 'entity.type-current-customer',
|
||||
list: 'entity.type-current-customer'
|
||||
},
|
||||
"CURRENT_TENANT": {
|
||||
type: 'entity.type-current-tenant',
|
||||
list: 'entity.type-current-tenant'
|
||||
}
|
||||
},
|
||||
entityField: {
|
||||
|
||||
@ -83,9 +83,9 @@ class ThingsboardAceEditor extends React.Component {
|
||||
fixAceEditor(editor);
|
||||
}
|
||||
|
||||
onToggleFull(groupId) {
|
||||
onToggleFull() {
|
||||
this.setState({ isFull: !this.state.isFull });
|
||||
this.props.onToggleFullscreen(groupId);
|
||||
this.props.onToggleFullscreen();
|
||||
this.updateAceEditorSize = true;
|
||||
}
|
||||
|
||||
@ -140,7 +140,7 @@ class ThingsboardAceEditor extends React.Component {
|
||||
<div className="title-panel">
|
||||
<label>{this.props.mode}</label>
|
||||
<FlatButton style={ styles.tidyButtonStyle } className="tidy-button" label={'Tidy'} onTouchTap={this.onTidy}/>
|
||||
<FlatButton style={ styles.tidyButtonStyle } className="tidy-button" label={this.state.isFull ? 'Exit fullscreen' : 'Fullscreen'} onTouchTap={() => this.onToggleFull(this.props.groupId)}/>
|
||||
<FlatButton style={ styles.tidyButtonStyle } className="tidy-button" label={this.state.isFull ? 'Exit fullscreen' : 'Fullscreen'} onTouchTap={this.onToggleFull}/>
|
||||
</div>
|
||||
<AceEditor mode={this.props.mode}
|
||||
height={this.state.isFull ? "100%" : "150px"}
|
||||
|
||||
@ -131,7 +131,7 @@ class ThingsboardArray extends React.Component {
|
||||
}
|
||||
let forms = this.props.form.items.map(function(form, index){
|
||||
var copy = this.copyWithIndex(form, i);
|
||||
return this.props.builder(copy, this.props.groupId, this.props.model, index, this.props.onChange, this.props.onColorClick, this.props.onIconClick, this.props.onToggleFullscreen, this.props.mapper, this.props.builder);
|
||||
return this.props.builder(copy, this.props.model, index, this.props.onChange, this.props.onColorClick, this.props.onIconClick, this.props.onToggleFullscreen, this.props.mapper, this.props.builder);
|
||||
}.bind(this));
|
||||
arrays.push(
|
||||
<li key={keys[i]} className="list-group-item">
|
||||
|
||||
@ -19,7 +19,7 @@ class ThingsboardFieldSet extends React.Component {
|
||||
|
||||
render() {
|
||||
let forms = this.props.form.items.map(function(form, index){
|
||||
return this.props.builder(form, this.props.groupId, this.props.model, index, this.props.onChange, this.props.onColorClick, this.props.onIconClick, this.props.onToggleFullscreen, this.props.mapper, this.props.builder);
|
||||
return this.props.builder(form, this.props.model, index, this.props.onChange, this.props.onColorClick, this.props.onIconClick, this.props.onToggleFullscreen, this.props.mapper, this.props.builder);
|
||||
}.bind(this));
|
||||
|
||||
return (
|
||||
|
||||
@ -40,9 +40,6 @@ class ThingsboardSchemaForm extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
groupId: null,
|
||||
};
|
||||
|
||||
this.mapper = {
|
||||
'number': ThingsboardNumber,
|
||||
@ -88,15 +85,12 @@ class ThingsboardSchemaForm extends React.Component {
|
||||
this.props.onIconClick(event);
|
||||
}
|
||||
|
||||
onToggleFullscreen(groupId) {
|
||||
this.setState({
|
||||
groupId: groupId
|
||||
});
|
||||
onToggleFullscreen() {
|
||||
this.props.onToggleFullscreen();
|
||||
}
|
||||
|
||||
|
||||
builder(form, groupId, model, index, onChange, onColorClick, onIconClick, onToggleFullscreen, mapper) {
|
||||
builder(form, model, index, onChange, onColorClick, onIconClick, onToggleFullscreen, mapper) {
|
||||
var type = form.type;
|
||||
let Field = this.mapper[type];
|
||||
if(!Field) {
|
||||
@ -109,21 +103,21 @@ class ThingsboardSchemaForm extends React.Component {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return <Field model={model} groupId={groupId} form={form} key={index} onChange={onChange} onColorClick={onColorClick} onIconClick={onIconClick} onToggleFullscreen={onToggleFullscreen} mapper={mapper} builder={this.builder}/>
|
||||
return <Field model={model} form={form} key={index} onChange={onChange} onColorClick={onColorClick} onIconClick={onIconClick} onToggleFullscreen={onToggleFullscreen} mapper={mapper} builder={this.builder}/>
|
||||
}
|
||||
|
||||
createSchema(theForm, groupId) {
|
||||
createSchema(theForm) {
|
||||
let merged = utils.merge(this.props.schema, theForm, this.props.ignore, this.props.option);
|
||||
let mapper = this.mapper;
|
||||
if(this.props.mapper) {
|
||||
mapper = _.merge(this.mapper, this.props.mapper);
|
||||
}
|
||||
let forms = merged.map(function(form, index) {
|
||||
return this.builder(form, groupId, this.props.model, index, this.onChange, this.onColorClick, this.onIconClick, this.onToggleFullscreen, mapper);
|
||||
return this.builder(form, this.props.model, index, this.onChange, this.onColorClick, this.onIconClick, this.onToggleFullscreen, mapper);
|
||||
}.bind(this));
|
||||
|
||||
let formClass = 'SchemaForm';
|
||||
if (this.props.isFullscreen && groupId === this.state.groupId) {
|
||||
if (this.props.isFullscreen) {
|
||||
formClass += ' SchemaFormFullscreen';
|
||||
}
|
||||
|
||||
@ -136,7 +130,7 @@ class ThingsboardSchemaForm extends React.Component {
|
||||
if(this.props.groupInfoes&&this.props.groupInfoes.length>0){
|
||||
let content=[];
|
||||
for(let info of this.props.groupInfoes){
|
||||
let forms = this.createSchema(this.props.form[info.formIndex], info.formIndex);
|
||||
let forms = this.createSchema(this.props.form[info.formIndex]);
|
||||
let item = <ThingsboardSchemaGroup key={content.length} forms={forms} info={info}></ThingsboardSchemaGroup>;
|
||||
content.push(item);
|
||||
}
|
||||
@ -166,8 +160,8 @@ class ThingsboardSchemaGroup extends React.Component{
|
||||
render() {
|
||||
let theCla = "pull-right fa fa-chevron-down md-toggle-icon"+(this.state.showGroup?"":" tb-toggled")
|
||||
return (<section className="md-whiteframe-z1" style={{marginTop: '10px'}}>
|
||||
<div className='SchemaGroupname md-button-toggle' onClick={this.toogleGroup.bind(this)}>{this.props.info.GroupTitle}<span className={theCla}></span></div>
|
||||
<div style={{padding: '20px'}} className={this.state.showGroup?"":"invisible"}>{this.props.forms}</div>
|
||||
</section>);
|
||||
<div className='SchemaGroupname md-button-toggle' onClick={this.toogleGroup.bind(this)}>{this.props.info.GroupTitle}<span className={theCla}></span></div>
|
||||
<div style={{padding: '20px'}} className={this.state.showGroup?"":"invisible"}>{this.props.forms}</div>
|
||||
</section>);
|
||||
}
|
||||
}
|
||||
|
||||
@ -24,15 +24,12 @@ $input-label-float-scale: .75 !default;
|
||||
.tb-fullscreen {
|
||||
[name="ReactSchemaForm"] {
|
||||
.SchemaForm {
|
||||
display: none;
|
||||
|
||||
&.SchemaFormFullscreen {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
display: block;
|
||||
|
||||
> div:not(.fullscreen-form-field) {
|
||||
display: none !important;
|
||||
|
||||
@ -152,7 +152,7 @@ export default function WidgetController($scope, $state, $timeout, $window, $ocL
|
||||
var entityInfo = getActiveEntityInfo();
|
||||
var entityId = entityInfo ? entityInfo.entityId : null;
|
||||
var entityName = entityInfo ? entityInfo.entityName : null;
|
||||
var entityLabel = entityInfo && entityInfo.label ? entityInfo.label : null;
|
||||
var entityLabel = entityInfo && entityInfo.entityLabel ? entityInfo.entityLabel : null;
|
||||
handleWidgetAction($event, this.descriptor, entityId, entityName, null, entityLabel);
|
||||
}
|
||||
widgetContext.customHeaderActions.push(headerAction);
|
||||
|
||||
@ -15,7 +15,7 @@
|
||||
limitations under the License.
|
||||
|
||||
-->
|
||||
<md-dialog aria-label="{{ 'dashboard.manage-states' | translate }}" style="width: 620px;">
|
||||
<md-dialog aria-label="{{ 'dashboard.manage-states' | translate }}" style="min-width: 600px;">
|
||||
<form name="theForm" ng-submit="vm.save()">
|
||||
<md-toolbar>
|
||||
<div class="md-toolbar-tools">
|
||||
@ -72,7 +72,7 @@
|
||||
</md-toolbar>
|
||||
<md-table-container>
|
||||
<table md-table>
|
||||
<thead fix-head md-head md-order="vm.query.order" md-on-reorder="vm.onReorder">
|
||||
<thead md-head md-order="vm.query.order" md-on-reorder="vm.onReorder">
|
||||
<tr md-row>
|
||||
<th md-column md-order-by="name"><span translate>dashboard.state-name</span></th>
|
||||
<th md-column md-order-by="id"><span translate>dashboard.state-id</span></th>
|
||||
|
||||
@ -22,13 +22,14 @@ import entitySelectTemplate from './entity-select.tpl.html';
|
||||
/* eslint-enable import/no-unresolved, import/default */
|
||||
|
||||
/*@ngInject*/
|
||||
export default function EntitySelect($compile, $templateCache, entityService) {
|
||||
export default function EntitySelect($compile, $templateCache, entityService, types) {
|
||||
|
||||
var linker = function (scope, element, attrs, ngModelCtrl) {
|
||||
var template = $templateCache.get(entitySelectTemplate);
|
||||
element.html(template);
|
||||
|
||||
scope.tbRequired = angular.isDefined(scope.tbRequired) ? scope.tbRequired : false;
|
||||
scope.entityTypeCurrentTenant = types.aliasEntityType.current_tenant;
|
||||
|
||||
var entityTypes = entityService.prepareAllowedEntityTypesList(scope.allowedEntityTypes, scope.useAliasEntityTypes);
|
||||
|
||||
@ -48,7 +49,8 @@ export default function EntitySelect($compile, $templateCache, entityService) {
|
||||
scope.updateView = function () {
|
||||
if (!scope.disabled) {
|
||||
var value = ngModelCtrl.$viewValue;
|
||||
if (scope.model && scope.model.entityType && scope.model.entityId) {
|
||||
if (scope.model && scope.model.entityType &&
|
||||
(scope.model.entityId || scope.model.entityType === scope.entityTypeCurrentTenant)) {
|
||||
if (!value) {
|
||||
value = {};
|
||||
}
|
||||
|
||||
@ -25,11 +25,11 @@
|
||||
allowed-entity-types="allowedEntityTypes"
|
||||
ng-model="model.entityType">
|
||||
</tb-entity-type-select>
|
||||
<tb-entity-autocomplete flex ng-if="model.entityType"
|
||||
<tb-entity-autocomplete flex ng-if="model.entityType && model.entityType !== entityTypeCurrentTenant"
|
||||
the-form="theForm"
|
||||
ng-disabled="disabled"
|
||||
tb-required="tbRequired"
|
||||
entity-type="model.entityType"
|
||||
ng-model="model.entityId">
|
||||
</tb-entity-autocomplete>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -770,6 +770,7 @@
|
||||
"list-of-rulenodes": "{ count, plural, 1 {Jeden uzel pravidla} other {Seznam # uzlů pravidel} }",
|
||||
"rulenode-name-starts-with": "Uzly pravidel, jejichž název začíná '{{prefix}}'",
|
||||
"type-current-customer": "Stávající zákazník",
|
||||
"type-current-tenant": "Stávající tenant",
|
||||
"search": "Vyhledat entity",
|
||||
"selected-entities": "{ count, plural, 1 {1 entita} other {# entit} } zvoleno",
|
||||
"entity-name": "Název entity",
|
||||
|
||||
@ -811,6 +811,7 @@
|
||||
"list-of-rulenodes": "{ count, plural, 1 {One rule node} other {List of # rule nodes} }",
|
||||
"rulenode-name-starts-with": "Rule nodes whose names start with '{{prefix}}'",
|
||||
"type-current-customer": "Current Customer",
|
||||
"type-current-tenant": "Current Tenant",
|
||||
"search": "Search entities",
|
||||
"selected-entities": "{ count, plural, 1 {1 entity} other {# entities} } selected",
|
||||
"entity-name": "Entity name",
|
||||
|
||||
@ -809,6 +809,7 @@
|
||||
"list-of-rulenodes": "{ count, plural, 1 {Одно правило} other {Список из # правил} }",
|
||||
"rulenode-name-starts-with": "Правила, чьи названия начинаются с '{{prefix}}'",
|
||||
"type-current-customer": "Текущий клиент",
|
||||
"type-current-tenant": "Текущий владелец",
|
||||
"search": "Поиск объектов",
|
||||
"selected-entities": "Выбран(ы) { count, plural, 1 {1 объект} few {# объекта} other {# объектов} }",
|
||||
"entity-name": "Название объекта",
|
||||
|
||||
@ -942,6 +942,7 @@
|
||||
"list-of-rulenodes": "{ count, plural, 1 {Одне правило} other {Список # правил} }",
|
||||
"rulenode-name-starts-with": "Список правил, імена яких починаються '{{prefix}}'",
|
||||
"type-current-customer": "Поточний клієнт",
|
||||
"type-current-tenant": "Поточний власник",
|
||||
"search": "Пошук сутностей",
|
||||
"selected-entities": "{ count, plural, 1 {1 сутність} other {# сутності} } вибрано",
|
||||
"entity-name": "Ім'я сутності",
|
||||
|
||||
@ -20,7 +20,7 @@ import logoSvg from '../../svg/logo_title_white.svg';
|
||||
/* eslint-enable import/no-unresolved, import/default */
|
||||
|
||||
/*@ngInject*/
|
||||
export default function LoginController(toast, loginService, userService, types, $state/*, $rootScope, $log, $translate*/) {
|
||||
export default function LoginController(toast, loginService, userService, types, $state, $stateParams/*, $rootScope, $log, $translate*/) {
|
||||
var vm = this;
|
||||
|
||||
vm.logoSvg = logoSvg;
|
||||
@ -32,6 +32,12 @@ export default function LoginController(toast, loginService, userService, types,
|
||||
|
||||
vm.login = login;
|
||||
|
||||
if ($stateParams.username && $stateParams.password) {
|
||||
vm.user.name = $stateParams.username;
|
||||
vm.user.password = $stateParams.password;
|
||||
doLogin();
|
||||
}
|
||||
|
||||
function doLogin() {
|
||||
loginService.login(vm.user).then(function success(response) {
|
||||
var token = response.data.token;
|
||||
|
||||
@ -25,7 +25,7 @@ import createPasswordTemplate from './create-password.tpl.html';
|
||||
/*@ngInject*/
|
||||
export default function LoginRoutes($stateProvider) {
|
||||
$stateProvider.state('login', {
|
||||
url: '/login',
|
||||
url: '/login?username&password',
|
||||
module: 'public',
|
||||
views: {
|
||||
"@": {
|
||||
|
||||
@ -52,6 +52,11 @@ function WebCameraWidgetController($element, $scope, $window, types, utils, attr
|
||||
let canvas = null;
|
||||
let photoCamera = null;
|
||||
let dataKeyType = "";
|
||||
let width = 640;
|
||||
let height = 480;
|
||||
|
||||
const DEFAULT_IMAGE_TYPE = 'image/jpeg';
|
||||
const DEFAULT_IMAGE_QUALITY = 0.92;
|
||||
|
||||
vm.getStream = getStream;
|
||||
vm.createPhoto = createPhoto;
|
||||
@ -79,6 +84,8 @@ function WebCameraWidgetController($element, $scope, $window, types, utils, attr
|
||||
vm.isEntityDetected = true;
|
||||
}
|
||||
}
|
||||
width = vm.ctx.settings.maxWidth ? vm.ctx.settings.maxWidth : 640;
|
||||
height = vm.ctx.settings.maxHeight ? vm.ctx.settings.maxWidth : 480;
|
||||
if (datasource.dataKeys.length) {
|
||||
$scope.currentKey = datasource.dataKeys[0].name;
|
||||
dataKeyType = datasource.dataKeys[0].type;
|
||||
@ -93,6 +100,24 @@ function WebCameraWidgetController($element, $scope, $window, types, utils, attr
|
||||
}
|
||||
});
|
||||
|
||||
function getVideoAspectRatio() {
|
||||
if (videoElement.videoWidth && videoElement.videoWidth > 0 &&
|
||||
videoElement.videoHeight && videoElement.videoHeight > 0) {
|
||||
return videoElement.videoWidth / videoElement.videoHeight;
|
||||
}
|
||||
return width / height;
|
||||
}
|
||||
|
||||
vm.videoWidth = function() {
|
||||
const videoRatio = getVideoAspectRatio();
|
||||
return Math.min(width, height * videoRatio);
|
||||
}
|
||||
|
||||
vm.videoHeight = function() {
|
||||
const videoRatio = getVideoAspectRatio();
|
||||
return Math.min(height, width / videoRatio);
|
||||
}
|
||||
|
||||
function hasGetUserMedia() {
|
||||
return !!($window.navigator.mediaDevices && $window.navigator.mediaDevices.getUserMedia);
|
||||
}
|
||||
@ -157,10 +182,12 @@ function WebCameraWidgetController($element, $scope, $window, types, utils, attr
|
||||
}
|
||||
|
||||
function createPhoto() {
|
||||
canvas.width = videoElement.videoWidth;
|
||||
canvas.height = videoElement.videoHeight;
|
||||
canvas.getContext('2d').drawImage(videoElement, 0, 0);
|
||||
vm.previewPhoto = canvas.toDataURL('image/png');
|
||||
canvas.width = vm.videoWidth();
|
||||
canvas.height = vm.videoHeight();
|
||||
canvas.getContext('2d').drawImage(videoElement, 0, 0, vm.videoWidth(), vm.videoHeight());
|
||||
const mimeType = vm.ctx.settings.imageFormat ? vm.ctx.settings.imageFormat : DEFAULT_IMAGE_TYPE;
|
||||
const quality = vm.ctx.settings.imageQuality ? vm.ctx.settings.imageQuality : DEFAULT_IMAGE_QUALITY;
|
||||
vm.previewPhoto = canvas.toDataURL(mimeType, quality);
|
||||
vm.isPreviewPhoto = true;
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user