Skip to content

Commit c771e61

Browse files
Merge pull request #6 from tensorflow/inputselector
[debugger] Add input selector
2 parents f7f1bea + c319db3 commit c771e61

15 files changed

+448
-9
lines changed

toolings/tfjs-debugger/src/app/common/types.ts

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
export enum UrlParamKey {
2020
SELECTED_MODEL_TYPE_ID = 'mid',
2121
TFJS_MODEL_URL = 'tfjsmu',
22+
SELECTED_INPUT_TYPE_ID = 'iid',
2223
}
2324

2425
/** Valid config index. */

toolings/tfjs-debugger/src/app/components/config_section/config_section.component.html

+4
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,8 @@
2020
<div class="title">Model</div>
2121
<model-selector [configIndex]="configIndex"></model-selector>
2222
</div>
23+
<div class="section">
24+
<div class="title">Input</div>
25+
<input-selector [configIndex]="configIndex"></input-selector>
26+
</div>
2327
</div>

toolings/tfjs-debugger/src/app/components/config_section/config_section.component.scss

+2
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
padding: var.$spacing-3x var.$spacing-3x;
2424

2525
.section {
26+
margin-bottom: var.$spacing-5x;
27+
2628
.title {
2729
font-weight: 500;
2830
}

toolings/tfjs-debugger/src/app/components/config_section/config_section.module.ts

+2
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import {CommonModule} from '@angular/common';
1919
import {NgModule} from '@angular/core';
2020

21+
import {InputSelectorModule} from '../input_selector/input_selector.module';
2122
import {ModelSelectorModule} from '../model_selector/model_selector.module';
2223

2324
import {ConfigSection} from './config_section.component';
@@ -29,6 +30,7 @@ import {ConfigSection} from './config_section.component';
2930
imports: [
3031
CommonModule,
3132
ModelSelectorModule,
33+
InputSelectorModule,
3234
],
3335
exports: [
3436
ConfigSection,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<!-- Copyright 2021 Google LLC. All Rights Reserved.
2+
3+
Licensed under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License.
5+
You may obtain a copy of the License at
6+
7+
http://www.apache.org/licenses/LICENSE-2.0
8+
9+
Unless required by applicable law or agreed to in writing, software
10+
distributed under the License is distributed on an "AS IS" BASIS,
11+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
See the License for the specific language governing permissions and
13+
limitations under the License.
14+
==============================================================================
15+
-->
16+
17+
<div class="container">
18+
<!-- Drop down select -->
19+
<mat-form-field appearance="outline" floatLabel="never">
20+
<mat-select
21+
[(value)]="selectedInputTypeId"
22+
[class.same-as-config1-selected]="isSameAsConfig1Selected"
23+
(selectionChange)="handleSelectionChange($event)">
24+
<mat-option *ngFor="let option of inputTypes"
25+
[value]="option.id"
26+
[disabled]="option.disabled">
27+
{{option.label}}
28+
</mat-option>
29+
</mat-select>
30+
</mat-form-field>
31+
</div>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/**
2+
* @license
3+
* Copyright 2021 Google LLC. All Rights Reserved.
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
* =============================================================================
16+
*/
17+
18+
@use '../../../variables' as var;
19+
20+
.container {
21+
mat-form-field {
22+
width: 100%;
23+
}
24+
25+
mat-select.same-as-config1-selected ::ng-deep .mat-select-value-text {
26+
font-style: italic;
27+
}
28+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
/**
2+
* @license
3+
* Copyright 2021 Google LLC. All Rights Reserved.
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
* =============================================================================
16+
*/
17+
18+
import {HarnessLoader} from '@angular/cdk/testing';
19+
import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed';
20+
import {ComponentFixture, fakeAsync, TestBed, tick} from '@angular/core/testing';
21+
import {MatSelectHarness} from '@angular/material/select/testing';
22+
import {By} from '@angular/platform-browser';
23+
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
24+
import {Router} from '@angular/router';
25+
import {RouterTestingModule} from '@angular/router/testing';
26+
import {routerReducer, RouterReducerState, StoreRouterConnectingModule} from '@ngrx/router-store';
27+
import {createFeatureSelector, Store, StoreModule} from '@ngrx/store';
28+
import {UrlParamKey} from 'src/app/common/types';
29+
import {appendConfigIndexToKey} from 'src/app/common/utils';
30+
import {InputTypeId} from 'src/app/data_model/input_type';
31+
import {AppState} from 'src/app/store/state';
32+
33+
import {InputSelector} from './input_selector.component';
34+
import {InputSelectorModule} from './input_selector.module';
35+
36+
describe('InputSelector', () => {
37+
let store: Store<AppState>;
38+
let router: Router;
39+
let fixture: ComponentFixture<InputSelector>;
40+
let loader: HarnessLoader;
41+
const selectRouter = createFeatureSelector<RouterReducerState>('router');
42+
43+
beforeEach(() => {
44+
TestBed.configureTestingModule({
45+
declarations: [
46+
InputSelector,
47+
],
48+
imports: [
49+
InputSelectorModule,
50+
BrowserAnimationsModule,
51+
RouterTestingModule,
52+
StoreModule.forRoot({
53+
'router': routerReducer,
54+
}),
55+
StoreRouterConnectingModule.forRoot(),
56+
],
57+
});
58+
59+
router = TestBed.inject(Router);
60+
store = TestBed.inject(Store);
61+
fixture = TestBed.createComponent(InputSelector);
62+
loader = TestbedHarnessEnvironment.loader(fixture);
63+
});
64+
65+
it('should select the correct input type encoded in URL', fakeAsync(() => {
66+
// Verify that the default selection is the first one in the inputTypes
67+
// array.
68+
fixture.componentInstance.configIndex = 1;
69+
// Need 2 rounds of change detections (one for initial data binding and
70+
// one for navigation) to properly update the UI
71+
detectChanges(2);
72+
const selectEle: HTMLElement =
73+
fixture.debugElement.query(By.css('mat-select')).nativeElement;
74+
expect(selectEle.textContent)
75+
.toBe(fixture.componentInstance.inputTypes[0].label);
76+
77+
// After updating the url with inputTypeId = CUSTOM_VALUE, verify that
78+
// the input type selector has the correct one selected.
79+
router.navigate([], {
80+
queryParams: {
81+
[appendConfigIndexToKey(
82+
UrlParamKey.SELECTED_INPUT_TYPE_ID,
83+
fixture.componentInstance.configIndex)]:
84+
`${InputTypeId.CUSTOM_VALUE}`,
85+
}
86+
});
87+
detectChanges();
88+
89+
const inputType = fixture.componentInstance.inputTypes.find(
90+
inputType => inputType.id === InputTypeId.CUSTOM_VALUE)!;
91+
expect(selectEle.textContent).toBe(inputType.label);
92+
}));
93+
94+
it('should correctly encode the selected input type in URL', fakeAsync(() => {
95+
fixture.componentInstance.configIndex = 1;
96+
detectChanges(2);
97+
98+
// Click the "Random" in the input type selector.
99+
const selectInputType = async () => {
100+
const selector = await loader.getHarness(MatSelectHarness);
101+
await selector.open();
102+
const options = await selector.getOptions({text: 'Random'});
103+
await options[0].click();
104+
};
105+
106+
selectInputType();
107+
tick();
108+
109+
// Verify that the router store has the correct query param for the
110+
// selected input type id.
111+
let curRouterState!: RouterReducerState;
112+
store.select(selectRouter).subscribe(routerState => {
113+
curRouterState = routerState;
114+
});
115+
tick();
116+
117+
expect(curRouterState.state.root.queryParams[appendConfigIndexToKey(
118+
UrlParamKey.SELECTED_INPUT_TYPE_ID,
119+
fixture.componentInstance.configIndex)])
120+
.toBe(`${InputTypeId.RANDOM}`);
121+
}));
122+
123+
const detectChanges = (count = 1) => {
124+
for (let i = 0; i < count; i++) {
125+
tick();
126+
fixture.detectChanges();
127+
}
128+
};
129+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
/**
2+
* @license
3+
* Copyright 2021 Google LLC. All Rights Reserved.
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
* =============================================================================
16+
*/
17+
18+
import {ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnDestroy, OnInit} from '@angular/core';
19+
import {MatSelectChange} from '@angular/material/select';
20+
import {Store} from '@ngrx/store';
21+
import {takeWhile} from 'rxjs';
22+
import {ConfigIndex, UrlParamKey} from 'src/app/common/types';
23+
import {appendConfigIndexToKey} from 'src/app/common/utils';
24+
import {InputTypeId} from 'src/app/data_model/input_type';
25+
import {UrlService} from 'src/app/services/url_service';
26+
import {selectConfigValueFromUrl} from 'src/app/store/selectors';
27+
import {AppState} from 'src/app/store/state';
28+
29+
import {InputTypeOption} from './types';
30+
31+
/**
32+
* A selector for users to select the type of input (e.g. random, const, image,
33+
* etc) to the model.
34+
*/
35+
@Component({
36+
selector: 'input-selector',
37+
templateUrl: './input_selector.component.html',
38+
styleUrls: ['./input_selector.component.scss'],
39+
changeDetection: ChangeDetectionStrategy.OnPush,
40+
})
41+
export class InputSelector implements OnInit, OnDestroy {
42+
@Input() configIndex: ConfigIndex = 0;
43+
44+
/** All supported input types. */
45+
readonly inputTypes: InputTypeOption[] = [
46+
{
47+
id: InputTypeId.RANDOM,
48+
label: 'Random',
49+
},
50+
// TODO: the following types are disabled for now. Will enable them as they
51+
// are implemented.
52+
{
53+
id: InputTypeId.CUSTOM_VALUE,
54+
label: 'Custom value',
55+
disabled: true,
56+
},
57+
{
58+
id: InputTypeId.IMAGE,
59+
label: 'Image',
60+
disabled: true,
61+
},
62+
];
63+
64+
/** Stores the currently selected input type. */
65+
selectedInputTypeId!: InputTypeId;
66+
67+
private active = true;
68+
69+
constructor(
70+
private readonly changeDetectorRef: ChangeDetectorRef,
71+
private readonly store: Store<AppState>,
72+
private readonly urlService: UrlService,
73+
) {}
74+
75+
ngOnInit() {
76+
// Add "Same as configuration 1" option when the input selector is used in
77+
// the configuration 2.
78+
if (this.configIndex === 1) {
79+
this.inputTypes.unshift({
80+
id: InputTypeId.SAME_AS_CONFIG1,
81+
label: 'Same as configuration 1',
82+
});
83+
}
84+
85+
// Update currently selected input type from URL.
86+
this.store
87+
.select(selectConfigValueFromUrl(
88+
this.configIndex, UrlParamKey.SELECTED_INPUT_TYPE_ID))
89+
.pipe(takeWhile(() => this.active))
90+
.subscribe((strId) => {
91+
// The first one is the default.
92+
let inputTypeId = this.inputTypes[0].id;
93+
if (strId != null) {
94+
inputTypeId = Number(strId);
95+
}
96+
this.selectedInputTypeId = inputTypeId;
97+
this.changeDetectorRef.markForCheck();
98+
99+
// TODO: update configs in store.
100+
});
101+
}
102+
103+
ngOnDestroy() {
104+
this.active = false;
105+
}
106+
107+
handleSelectionChange(event: MatSelectChange) {
108+
const inputTypeId = event.value as InputTypeId;
109+
110+
// TODO: add other logic as needed to handle selection change (e.g. show
111+
// certain UI elements when an item is selected).
112+
113+
// Update url with selected input type id.
114+
this.urlService.updateUrlParameters({
115+
[appendConfigIndexToKey(
116+
UrlParamKey.SELECTED_INPUT_TYPE_ID, this.configIndex)]:
117+
`${inputTypeId}`
118+
});
119+
}
120+
121+
get isSameAsConfig1Selected(): boolean {
122+
return this.selectedInputTypeId === InputTypeId.SAME_AS_CONFIG1;
123+
}
124+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/**
2+
* @license
3+
* Copyright 2021 Google LLC. All Rights Reserved.
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
* =============================================================================
16+
*/
17+
18+
import {CommonModule} from '@angular/common';
19+
import {NgModule} from '@angular/core';
20+
import {FormsModule} from '@angular/forms';
21+
import {MatFormFieldModule} from '@angular/material/form-field';
22+
import {MatSelectModule} from '@angular/material/select';
23+
24+
import {InputSelector} from './input_selector.component';
25+
26+
@NgModule({
27+
imports: [
28+
CommonModule,
29+
FormsModule,
30+
MatFormFieldModule,
31+
MatSelectModule,
32+
],
33+
declarations: [
34+
InputSelector,
35+
],
36+
exports: [
37+
InputSelector,
38+
],
39+
})
40+
export class InputSelectorModule {
41+
}

0 commit comments

Comments
 (0)