Add keymap page (#80)

This commit is contained in:
Nejc Zdovc
2016-08-29 21:51:30 +02:00
committed by József Farkas
parent bb3a2d77b6
commit dee9c1077b
20 changed files with 1258 additions and 34 deletions

View File

@@ -10,11 +10,15 @@ import { KeymapComponent } from './components/keymap';
import { MacroComponent } from './components/macro'; import { MacroComponent } from './components/macro';
import { LegacyLoaderComponent } from './components/legacy-loader'; import { LegacyLoaderComponent } from './components/legacy-loader';
import { NotificationComponent } from './components/notification'; import { NotificationComponent } from './components/notification';
import { SvgKeystrokeKeyComponent, SvgOneLineTextKeyComponent, SvgTwoLineTextKeyComponent } from './components/svg/keys'; import {
import {SvgKeyboardWrapComponent} from './components/svg/wrap/svg-keyboard-wrap.component'; SvgKeystrokeKeyComponent, SvgOneLineTextKeyComponent, SvgTwoLineTextKeyComponent
import {LayersComponent} from './components/layers/layers.component'; } from './components/svg/keys';
import {SvgKeyboardComponent} from './components/svg/keyboard/svg-keyboard.component'; import { SvgKeyboardWrapComponent } from './components/svg/wrap';
import {PopoverComponent} from './components/popover/popover.component'; import { LayersComponent } from './components/layers';
import { SvgKeyboardComponent } from './components/svg/keyboard';
import { PopoverComponent } from './components/popover';
import { KeymapAddComponent } from './components/keymap';
import {UhkConfigurationService} from './services/uhk-configuration.service';
@NgModule({ @NgModule({
declarations: [ declarations: [
@@ -29,11 +33,13 @@ import {PopoverComponent} from './components/popover/popover.component';
SvgKeyboardWrapComponent, SvgKeyboardWrapComponent,
LayersComponent, LayersComponent,
PopoverComponent, PopoverComponent,
SvgKeyboardComponent SvgKeyboardComponent,
KeymapAddComponent
], ],
imports: [BrowserModule], imports: [BrowserModule],
providers: [ providers: [
DataProviderService, DataProviderService,
UhkConfigurationService,
MapperService, MapperService,
APP_ROUTER_PROVIDERS, APP_ROUTER_PROVIDERS,
{ provide: LocationStrategy, useClass: HashLocationStrategy } { provide: LocationStrategy, useClass: HashLocationStrategy }

View File

@@ -0,0 +1,32 @@
<h1>
<i class="fa fa-keyboard-o"></i>
<span>Add new keymap</span>
</h1>
<div class="keymap__search clearfix">
<div class="input-group">
<span class="input-group-addon" id="sizing-addon1">
<i class="fa fa-search"></i>
</span>
<input type="text" class="form-control" placeholder="Search ..." (input)="filterKeyboards($event.target.value)">
</div>
<div class="keymap__search_amount">
{{ keymaps.length }} / {{ keymapsAll.length }} keymaps shown
</div>
</div>
<div class="keymap__list">
<div #keyboard class="keymap__list_item" *ngFor="let keymap of keymaps">
<h2>{{ keymap.name }}</h2>
<p class="keymap__description">
{{ keymap.description }}
</p>
<svg-keyboard-wrap
[layers]="keymap.layers.elements"
[popoverEnabled]="false"
[tooltipEnabled]="true"
>
</svg-keyboard-wrap>
<div class="btn-group btn-group-lg">
<button class="btn btn-default">Add keymap</button>
</div>
</div>
</div>

View File

@@ -0,0 +1,61 @@
:host {
overflow-y: auto;
display: block;
height: 100%;
}
.uhk__layer-switcher--wrapper {
position: relative;
&:before {
content: attr(data-title);
display: inline-block;
position: absolute;
bottom: -0.3em;
right: 100%;
font-size: 2.4rem;
padding-right: 0.25em;
margin: 0;
}
}
.keymap {
&__search {
margin-top: 10px;
.input-group {
width: 100%;
max-width: 350px;
float: left;
}
&_amount {
float: left;
margin: 7px 0 0 20px;
}
}
&__description {
margin-bottom: 20px;
}
&__list {
margin-top: 40px;
&_item {
margin-bottom: 50px;
}
.btn-group-lg {
margin: 30px 0 0;
width: 100%;
text-align: center;
.btn {
float: none;
padding-left: 50px;
padding-right: 50px;
}
}
}
}

View File

@@ -0,0 +1,27 @@
import { Component } from '@angular/core';
import { Keymap } from '../../../config-serializer/config-items/Keymap';
import { Keymaps } from '../../../config-serializer/config-items/Keymaps';
import {DataProviderService} from '../../../services/data-provider.service';
@Component({
selector: 'keymap-add',
template: require('./keymap-add.component.html'),
styles: [require('./keymap-add.component.scss')]
})
export class KeymapAddComponent {
private keymaps: Keymap[];
private keymapsAll: Keymap[];
private currentKeyboards: number;
constructor(data: DataProviderService) {
let json: any = data.getDefaultKeymaps();
let all: Keymaps = new Keymaps().fromJsObject(json.keymaps);
this.keymaps = all.elements;
this.keymapsAll = this.keymaps.slice(0);
this.currentKeyboards = this.keymaps.length;
}
filterKeyboards(value: string) {
this.keymaps = this.keymapsAll.filter((item: Keymap) => item.name.toLocaleLowerCase().indexOf(value) !== -1);
}
}

View File

@@ -1,2 +1,3 @@
export * from './keymap.component'; export * from './keymap.component';
export * from './keymap.routes'; export * from './keymap.routes';
export * from './add/keymap-add.component';

View File

@@ -9,8 +9,7 @@ import { Subscription } from 'rxjs/Subscription';
@Component({ @Component({
selector: 'keymap', selector: 'keymap',
template: require('./keymap.component.html'), template: require('./keymap.component.html'),
styles: [require('./keymap.component.scss')], styles: [require('./keymap.component.scss')]
providers: [UhkConfigurationService]
}) })
export class KeymapComponent implements OnInit { export class KeymapComponent implements OnInit {
private keymapId: number = 0; private keymapId: number = 0;

View File

@@ -1,5 +1,6 @@
import { RouterConfig } from '@angular/router'; import { RouterConfig } from '@angular/router';
import { KeymapComponent } from './keymap.component'; import { KeymapComponent } from './keymap.component';
import { KeymapAddComponent } from './add/keymap-add.component';
export const keymapRoutes: RouterConfig = [ export const keymapRoutes: RouterConfig = [
{ {
@@ -11,6 +12,10 @@ export const keymapRoutes: RouterConfig = [
path: 'keymap', path: 'keymap',
component: KeymapComponent component: KeymapComponent
}, },
{
path: 'keymap/add',
component: KeymapAddComponent
},
{ {
path: 'keymap/:id', path: 'keymap/:id',
component: KeymapComponent component: KeymapComponent

View File

@@ -2,7 +2,7 @@
<li class="sidebar__level-1--item"> <li class="sidebar__level-1--item">
<div class="sidebar__level-1"> <div class="sidebar__level-1">
<i class="fa fa-keyboard-o"></i> Keymaps <i class="fa fa-keyboard-o"></i> Keymaps
<a href="#" class="btn btn-default pull-right btn-sm"> <a [routerLink]="['/keymap/add']" class="btn btn-default pull-right btn-sm">
<i class="fa fa-plus"></i> <i class="fa fa-plus"></i>
</a> </a>
<i class="fa fa-chevron-up pull-right" (click)="toggleHide($event, keymapElement)"></i> <i class="fa fa-chevron-up pull-right" (click)="toggleHide($event, keymapElement)"></i>

View File

@@ -9,7 +9,6 @@ import { ROUTER_DIRECTIVES } from '@angular/router';
selector: 'side-menu', selector: 'side-menu',
template: require('./side-menu.component.html'), template: require('./side-menu.component.html'),
styles: [require('./side-menu.component.scss')], styles: [require('./side-menu.component.scss')],
providers: [UhkConfigurationService],
directives: [ROUTER_DIRECTIVES] directives: [ROUTER_DIRECTIVES]
}) })
export class SideMenuComponent implements OnInit { export class SideMenuComponent implements OnInit {

View File

@@ -5,7 +5,8 @@
[keyboardKeys]="module.keyboardKeys" [keyboardKeys]="module.keyboardKeys"
[attr.transform]="module.attributes.transform" [attr.transform]="module.attributes.transform"
[keyActions]="moduleConfig[i].keyActions.elements" [keyActions]="moduleConfig[i].keyActions.elements"
(editKeyActionRequest)="onEditKeyActionRequest(i, $event)" (keyClick)="onKeyClick(i, $event)"
(keyHover)="onKeyHover($event.index, $event.event, $event.over, i)"
/> />
</svg:g> </svg:g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 602 B

After

Width:  |  Height:  |  Size: 662 B

View File

@@ -13,6 +13,7 @@ import {DataProviderService} from '../../../services/data-provider.service';
export class SvgKeyboardComponent implements OnInit { export class SvgKeyboardComponent implements OnInit {
@Input() moduleConfig: Module[]; @Input() moduleConfig: Module[];
@Output() keyClick = new EventEmitter(); @Output() keyClick = new EventEmitter();
@Output() keyHover = new EventEmitter();
private modules: SvgModule[]; private modules: SvgModule[];
private svgAttributes: { viewBox: string, transform: string, fill: string }; private svgAttributes: { viewBox: string, transform: string, fill: string };
@@ -26,11 +27,20 @@ export class SvgKeyboardComponent implements OnInit {
this.modules = this.dps.getSvgModules(); this.modules = this.dps.getSvgModules();
} }
onEditKeyActionRequest(moduleId: number, keyId: number): void { onKeyClick(moduleId: number, keyId: number): void {
this.keyClick.emit({ this.keyClick.emit({
moduleId, moduleId,
keyId keyId
}); });
} }
onKeyHover(keyId: number, event: MouseEvent, over: boolean, moduleId: number): void {
this.keyHover.emit({
moduleId,
event,
over,
keyId
});
}
} }

View File

@@ -6,5 +6,6 @@
[attr.transform]="'translate(' + key.x + ' ' + key.y + ')'" [attr.transform]="'translate(' + key.x + ' ' + key.y + ')'"
[keyAction]="keyActions[i]" [keyAction]="keyActions[i]"
(click)="onKeyClick(i)" (click)="onKeyClick(i)"
/> (mouseover)="onKeyHover(i, $event, true)"
<popover *ngIf="popOverEnabled"></popover> (mouseleave)="onKeyHover(i, $event, false)"
/>

Before

Width:  |  Height:  |  Size: 424 B

After

Width:  |  Height:  |  Size: 483 B

View File

@@ -1,4 +1,4 @@
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; import { Component, Input, Output, EventEmitter } from '@angular/core';
import { SvgKeyboardKey, SvgKeyboardKeyComponent } from '../keys'; import { SvgKeyboardKey, SvgKeyboardKeyComponent } from '../keys';
import {KeyAction} from '../../../config-serializer/config-items/KeyAction'; import {KeyAction} from '../../../config-serializer/config-items/KeyAction';
@@ -9,20 +9,27 @@ import {KeyAction} from '../../../config-serializer/config-items/KeyAction';
styles: [require('./svg-module.component.scss')], styles: [require('./svg-module.component.scss')],
directives: [SvgKeyboardKeyComponent] directives: [SvgKeyboardKeyComponent]
}) })
export class SvgModuleComponent implements OnInit { export class SvgModuleComponent {
@Input() coverages: any[]; @Input() coverages: any[];
@Input() keyboardKeys: SvgKeyboardKey[]; @Input() keyboardKeys: SvgKeyboardKey[];
@Input() keyActions: KeyAction[]; @Input() keyActions: KeyAction[];
@Output() editKeyActionRequest = new EventEmitter<number>(); @Output() keyClick = new EventEmitter<number>();
@Output() keyHover = new EventEmitter();
constructor() { constructor() {
this.keyboardKeys = []; this.keyboardKeys = [];
} }
ngOnInit() { }
onKeyClick(index: number): void { onKeyClick(index: number): void {
this.editKeyActionRequest.emit(index); this.keyClick.emit(index);
}
onKeyHover(index: number, event: MouseEvent, over: boolean): void {
this.keyHover.emit({
index,
event,
over
});
} }
} }

View File

@@ -1,12 +1,25 @@
<template ngIf="layers"> <template ngIf="layers">
<layers (select)="selectLayer($event.oldIndex, $event.index)" [current]="currentLayer"></layers> <layers (select)="selectLayer($event.oldIndex, $event.index)" [current]="currentLayer"></layers>
<div class="keyboard-slider" > <div class="keyboard-slider" (mouseout)="hideTooltip($event)">
<svg-keyboard *ngFor="let layer of layers" <svg-keyboard *ngFor="let layer of layers"
[@layerState]="layer.animation" [@layerState]="layer.animation"
[moduleConfig]="layer.modules.elements" [moduleConfig]="layer.modules.elements"
(keyClick)="onKeyClick($event.moduleId, $event.keyId)" (keyClick)="onKeyClick($event.moduleId, $event.keyId)"
(keyHover)="onKeyHover($event.moduleId, $event.event, $event.over, $event.keyId)"
> >
</svg-keyboard> </svg-keyboard>
</div> </div>
<popover *ngIf="popoverShown && popoverEnabled" [defaultKeyAction]="popoverInitKeyAction" (cancel)="hidePopover()" (remap)="onRemap($event)"></popover> <popover *ngIf="popoverShown" [defaultKeyAction]="popoverInitKeyAction" (cancel)="hidePopover()" (remap)="onRemap($event)"></popover>
<div class="tooltip top fade" role="tooltip"
[class.in]="tooltipData.shown"
[style.top.px]="tooltipData.posTop"
[style.left.px]="tooltipData.posLeft"
>
<div class="tooltip-arrow"></div>
<div class="tooltip-inner">
<p *ngFor="let item of tooltipData.content">
{{ item.name }}: {{ item.value }}
</p>
</div>
</div>
</template> </template>

View File

@@ -10,12 +10,42 @@ svg-keyboard {
max-width: 1400px; max-width: 1400px;
position: absolute; position: absolute;
left: 0; left: 0;
transform: translateX(-100%); transform: translateX(-101%);
} }
.keyboard-slider { .keyboard-slider {
height: calc(100% - 145px);
position: relative; position: relative;
overflow: hidden; overflow: hidden;
width: 100%; /* TODO create dynamic */
height: 500px;
margin-top: 30px;
}
.tooltip {
position: fixed;
transform: translate(-50%, -85%);
&-inner {
background: #fff;
color: #000;
box-shadow: 0 1px 5px #000;
text-align: left;
p {
margin-bottom: 2px;
&:last-of-type {
margin-bottom: 0;
}
}
}
&.top {
.tooltip-arrow {
border-top-color: #fff;
}
}
&.in {
opacity: 1;
}
} }

View File

@@ -1,15 +1,17 @@
import { import {
Component, Input, OnInit, style, Component, Input, OnInit, style,
state, animate, transition, trigger state, animate, transition, trigger, OnChanges
} from '@angular/core'; } from '@angular/core';
import { KeyAction } from '../../../config-serializer/config-items/KeyAction'; import { KeyAction } from '../../../config-serializer/config-items/KeyAction';
import { Layer } from '../../../config-serializer/config-items/Layer'; import { Layer } from '../../../config-serializer/config-items/Layer';
import { NoneAction } from '../../../config-serializer/config-items/NoneAction';
@Component({ @Component({
selector: 'svg-keyboard-wrap', selector: 'svg-keyboard-wrap',
template: require('./svg-keyboard-wrap.component.html'), template: require('./svg-keyboard-wrap.component.html'),
styles: [require('./svg-keyboard-wrap.component.scss')], styles: [require('./svg-keyboard-wrap.component.scss')],
// We use 101%, because there was still a trace of the keyboard in the screen when animation was done
animations: [ animations: [
trigger('layerState', [ trigger('layerState', [
/* Right -> Left animation*/ /* Right -> Left animation*/
@@ -18,7 +20,7 @@ import { Layer } from '../../../config-serializer/config-items/Layer';
left: '50%' left: '50%'
})), })),
state('leftOut', style({ state('leftOut', style({
transform: 'translateX(-100%)', transform: 'translateX(-101%)',
left: '0' left: '0'
})), })),
/* Right -> Left animation */ /* Right -> Left animation */
@@ -28,14 +30,14 @@ import { Layer } from '../../../config-serializer/config-items/Layer';
})), })),
state('rightOut', style({ state('rightOut', style({
transform: 'translateX(0%)', transform: 'translateX(0%)',
left: '100%' left: '101%'
})), })),
/* Transitions */ /* Transitions */
transition('none => leftIn, leftOut => leftIn', [ transition('none => leftIn, leftOut => leftIn', [
style({ style({
opacity: 0, opacity: 0,
transform: 'translateX(0%)', transform: 'translateX(0%)',
left: '100%' left: '101%'
}), }),
style({ style({
opacity: 1 opacity: 1
@@ -45,7 +47,7 @@ import { Layer } from '../../../config-serializer/config-items/Layer';
transition('* => none', [ transition('* => none', [
style({ style({
opacity: 0, opacity: 0,
transform: 'translateX(-100%)', transform: 'translateX(-101%)',
left: '0' left: '0'
}), }),
style({ style({
@@ -55,7 +57,7 @@ import { Layer } from '../../../config-serializer/config-items/Layer';
transition('none => rightIn, rightOut => rightIn', [ transition('none => rightIn, rightOut => rightIn', [
style({ style({
opacity: 0, opacity: 0,
transform: 'translateX(-100%)', transform: 'translateX(-101%)',
left: '0' left: '0'
}), }),
style({ style({
@@ -73,21 +75,29 @@ import { Layer } from '../../../config-serializer/config-items/Layer';
]) ])
] ]
}) })
export class SvgKeyboardWrapComponent implements OnInit { export class SvgKeyboardWrapComponent implements OnInit, OnChanges {
@Input() layers: Layer[]; @Input() layers: Layer[];
@Input() popoverEnabled: boolean = true; @Input() popoverEnabled: boolean = true;
@Input() animationEnabled: boolean = true; @Input() tooltipEnabled: boolean = false;
private popoverShown: boolean; private popoverShown: boolean;
private keyEditConfig: { moduleId: number, keyId: number }; private keyEditConfig: { moduleId: number, keyId: number };
private popoverInitKeyAction: KeyAction; private popoverInitKeyAction: KeyAction;
private currentLayer: number = 0; private currentLayer: number = 0;
private tooltipData: { posTop: number, posLeft: number, content: {name: string, value: string}[], shown: boolean };
constructor() { constructor() {
this.keyEditConfig = { this.keyEditConfig = {
moduleId: undefined, moduleId: undefined,
keyId: undefined keyId: undefined
}; };
this.tooltipData = {
posTop: 0,
posLeft: 0,
content: [],
shown: false
};
} }
ngOnInit() { ngOnInit() {
@@ -107,7 +117,7 @@ export class SvgKeyboardWrapComponent implements OnInit {
} }
onKeyClick(moduleId: number, keyId: number): void { onKeyClick(moduleId: number, keyId: number): void {
if (!this.popoverShown) { if (!this.popoverShown && this.popoverEnabled) {
this.keyEditConfig = { this.keyEditConfig = {
moduleId, moduleId,
keyId keyId
@@ -118,6 +128,18 @@ export class SvgKeyboardWrapComponent implements OnInit {
} }
} }
onKeyHover(moduleId: number, event: MouseEvent, over: boolean, keyId: number): void {
let keyActionToEdit: KeyAction = this.layers[this.currentLayer].modules.elements[moduleId].keyActions.elements[keyId];
if (this.tooltipEnabled) {
if (over) {
this.showTooltip(keyActionToEdit, event);
} else {
this.hideTooltip(event);
}
}
}
onRemap(keyAction: KeyAction): void { onRemap(keyAction: KeyAction): void {
this.changeKeyAction(keyAction); this.changeKeyAction(keyAction);
this.hidePopover(); this.hidePopover();
@@ -128,6 +150,56 @@ export class SvgKeyboardWrapComponent implements OnInit {
this.popoverShown = true; this.popoverShown = true;
} }
showTooltip(keyAction: KeyAction, event: MouseEvent): void {
if (keyAction instanceof NoneAction || keyAction === undefined) {
return;
}
let el: Element = event.target as Element || event.srcElement;
let position: ClientRect = el.getBoundingClientRect();
let posLeft: number = this.tooltipData.posLeft;
let posTop: number = this.tooltipData.posTop;
if (el.tagName === 'rect') {
posLeft = position.left + (position.width / 2);
posTop = position.top;
}
// TODO connect with real data
let dummyData = [
{
name: 'Key action',
value: 'o'
},
{
name: 'Scancode',
value: '55'
}
];
this.tooltipData = {
posLeft: posLeft,
posTop: posTop,
content: dummyData,
shown: true
};
}
hideTooltip(event: MouseEvent) {
let target: HTMLElement = event.relatedTarget as HTMLElement;
if (!target) {
this.tooltipData.shown = false;
return;
}
// Check if we are hovering tooltip
let list: DOMTokenList = target.classList;
if (!list.contains('tooltip') && !list.contains('tooltip-inner')) {
this.tooltipData.shown = false;
}
}
hidePopover(): void { hidePopover(): void {
this.popoverShown = false; this.popoverShown = false;
this.popoverInitKeyAction = undefined; this.popoverInitKeyAction = undefined;

View File

@@ -10,6 +10,8 @@ export class Keymap extends Serializable<Keymap> {
name: string; name: string;
description: string;
abbreviation: string; abbreviation: string;
isDefault: boolean; isDefault: boolean;
@@ -21,6 +23,7 @@ export class Keymap extends Serializable<Keymap> {
this.isDefault = jsObject.isDefault; this.isDefault = jsObject.isDefault;
this.abbreviation = jsObject.abbreviation; this.abbreviation = jsObject.abbreviation;
this.name = jsObject.name; this.name = jsObject.name;
this.description = jsObject.description;
this.layers = new Layers().fromJsObject(jsObject.layers); this.layers = new Layers().fromJsObject(jsObject.layers);
return this; return this;
} }
@@ -30,6 +33,7 @@ export class Keymap extends Serializable<Keymap> {
this.isDefault = buffer.readBoolean(); this.isDefault = buffer.readBoolean();
this.abbreviation = buffer.readString(); this.abbreviation = buffer.readString();
this.name = buffer.readString(); this.name = buffer.readString();
this.description = buffer.readString();
this.layers = new Layers().fromBinary(buffer); this.layers = new Layers().fromBinary(buffer);
return this; return this;
} }
@@ -40,6 +44,7 @@ export class Keymap extends Serializable<Keymap> {
isDefault: this.isDefault, isDefault: this.isDefault,
abbreviation: this.abbreviation, abbreviation: this.abbreviation,
name: this.name, name: this.name,
description: this.description,
layers: this.layers.toJsObject() layers: this.layers.toJsObject()
}; };
} }
@@ -49,6 +54,7 @@ export class Keymap extends Serializable<Keymap> {
buffer.writeBoolean(this.isDefault); buffer.writeBoolean(this.isDefault);
buffer.writeString(this.abbreviation); buffer.writeString(this.abbreviation);
buffer.writeString(this.name); buffer.writeString(this.name);
buffer.writeString(this.description);
this.layers.toBinary(buffer); this.layers.toBinary(buffer);
} }

File diff suppressed because it is too large Load Diff

View File

@@ -18,6 +18,7 @@
"isDefault": true, "isDefault": true,
"abbreviation": "QTY", "abbreviation": "QTY",
"name": "QWERTY", "name": "QWERTY",
"description": "",
"layers": [ "layers": [
{ {
"modules": [ "modules": [
@@ -812,6 +813,7 @@
"isDefault": false, "isDefault": false,
"abbreviation": "VIM", "abbreviation": "VIM",
"name": "VIM", "name": "VIM",
"description": "",
"layers": [ "layers": [
{ {
"modules": [ "modules": [
@@ -914,6 +916,7 @@
"isDefault": false, "isDefault": false,
"abbreviation": "DVR", "abbreviation": "DVR",
"name": "DVR", "name": "DVR",
"description": "",
"layers": [ "layers": [
{ {
"modules": [ "modules": [

View File

@@ -11,6 +11,10 @@ export class DataProviderService {
return require('json!../config-serializer/uhk-config.json'); return require('json!../config-serializer/uhk-config.json');
} }
getDefaultKeymaps(): any {
return require('json!../config-serializer/default-keymaps.json');
}
getKeyboardSvgAttributes(): { viewBox: string, transform: string, fill: string } { getKeyboardSvgAttributes(): { viewBox: string, transform: string, fill: string } {
let svg: any = this.getBaseLayer(); let svg: any = this.getBaseLayer();
return { return {