mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-13 03:28:33 -05:00
added radio button model view component (#1439)
* added radio button model view component
This commit is contained in:
83
src/sql/base/browser/ui/radioButton/radioButton.ts
Normal file
83
src/sql/base/browser/ui/radioButton/radioButton.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
import { InputBox } from 'vs/base/browser/ui/inputbox/inputBox';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { Widget } from 'vs/base/browser/ui/widget';
|
||||
|
||||
export interface IRadioButtonOptions {
|
||||
label: string;
|
||||
enabled?: boolean;
|
||||
checked?: boolean;
|
||||
}
|
||||
|
||||
export class RadioButton extends Widget {
|
||||
|
||||
private inputElement: HTMLInputElement;
|
||||
private _onClicked = new Emitter<void>();
|
||||
public readonly onClicked: Event<void> = this._onClicked.event;
|
||||
private _label: HTMLSpanElement;
|
||||
|
||||
constructor(container: HTMLElement, opts: IRadioButtonOptions) {
|
||||
super();
|
||||
this.inputElement = document.createElement('input');
|
||||
this.inputElement.type = 'radio';
|
||||
|
||||
this._label = document.createElement('span');
|
||||
|
||||
this.label = opts.label;
|
||||
this.enabled = opts.enabled || true;
|
||||
this.checked = opts.checked || false;
|
||||
this.onclick(this.inputElement, () => this._onClicked.fire());
|
||||
|
||||
container.appendChild(this.inputElement);
|
||||
container.appendChild(this._label);
|
||||
}
|
||||
|
||||
public set name(value: string) {
|
||||
this.inputElement.setAttribute('name', value);
|
||||
}
|
||||
|
||||
public get name(): string {
|
||||
return this.inputElement.getAttribute('name');
|
||||
}
|
||||
|
||||
public set value(value: string) {
|
||||
this.inputElement.setAttribute('value', value);
|
||||
}
|
||||
|
||||
public get value(): string {
|
||||
return this.inputElement.getAttribute('value');
|
||||
}
|
||||
|
||||
public set checked(val: boolean) {
|
||||
this.inputElement.checked = val;
|
||||
}
|
||||
|
||||
public get checked(): boolean {
|
||||
return this.inputElement.checked;
|
||||
}
|
||||
|
||||
public set enabled(val: boolean) {
|
||||
this.inputElement.disabled = !val;
|
||||
}
|
||||
|
||||
public get enabled(): boolean {
|
||||
return !this.inputElement.disabled;
|
||||
}
|
||||
|
||||
public isEnabled(): boolean {
|
||||
return !this.inputElement.hasAttribute('disabled');
|
||||
}
|
||||
|
||||
public set label(val: string) {
|
||||
this._label.innerText = val;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -78,6 +78,11 @@ export default class CheckBoxComponent extends ComponentBase implements ICompone
|
||||
super.setProperties(properties);
|
||||
this._input.checked = this.checked;
|
||||
this._input.label = this.label;
|
||||
if (this.enabled) {
|
||||
this._input.enable();
|
||||
} else {
|
||||
this._input.disable();
|
||||
}
|
||||
}
|
||||
|
||||
// CSS-bound properties
|
||||
@@ -87,11 +92,7 @@ export default class CheckBoxComponent extends ComponentBase implements ICompone
|
||||
}
|
||||
|
||||
public set value(newValue: boolean) {
|
||||
this.setPropertyFromUI<sqlops.CheckBoxProperties, boolean>(this.setInputBoxProperties, newValue);
|
||||
}
|
||||
|
||||
private setInputBoxProperties(properties: sqlops.CheckBoxProperties, value: boolean): void {
|
||||
properties.checked = value;
|
||||
this.setPropertyFromUI<sqlops.CheckBoxProperties, boolean>((properties, value) => { properties.checked = value; }, newValue);
|
||||
}
|
||||
|
||||
private get label(): string {
|
||||
@@ -99,10 +100,6 @@ export default class CheckBoxComponent extends ComponentBase implements ICompone
|
||||
}
|
||||
|
||||
private set label(newValue: string) {
|
||||
this.setPropertyFromUI<sqlops.CheckBoxProperties, string>(this.setValueProperties, newValue);
|
||||
}
|
||||
|
||||
private setValueProperties(properties: sqlops.CheckBoxProperties, label: string): void {
|
||||
properties.label = label;
|
||||
this.setPropertyFromUI<sqlops.CheckBoxProperties, string>((properties, label) => { properties.label = label; }, newValue);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,7 +94,15 @@ export abstract class ComponentBase extends Disposable implements IComponent, On
|
||||
public get enabled(): boolean {
|
||||
let properties = this.getProperties();
|
||||
let enabled = properties['enabled'];
|
||||
return enabled !== undefined ? <boolean>enabled : true;
|
||||
if (enabled === undefined) {
|
||||
enabled = true;
|
||||
properties['enabled'] = enabled;
|
||||
this.fireEvent({
|
||||
eventType: ComponentEventType.PropertiesChanged,
|
||||
args: this.getProperties()
|
||||
});
|
||||
}
|
||||
return <boolean>enabled;
|
||||
}
|
||||
|
||||
public get valid(): boolean {
|
||||
|
||||
@@ -10,6 +10,7 @@ import InputBoxComponent from './inputbox.component';
|
||||
import DropDownComponent from './dropdown.component';
|
||||
import ButtonComponent from './button.component';
|
||||
import CheckBoxComponent from './checkbox.component';
|
||||
import RadioButtonComponent from './radioButton.component';
|
||||
import { registerComponentType } from 'sql/platform/dashboard/common/modelComponentRegistry';
|
||||
import { ModelComponentTypes } from 'sql/workbench/api/common/sqlExtHostTypes';
|
||||
|
||||
@@ -34,3 +35,6 @@ registerComponentType(BUTTON_COMPONENT, ModelComponentTypes.Button, ButtonCompon
|
||||
|
||||
export const CHECKBOX_COMPONENT = 'checkbox-component';
|
||||
registerComponentType(CHECKBOX_COMPONENT, ModelComponentTypes.CheckBox, CheckBoxComponent);
|
||||
|
||||
export const RADIOBUTTON_COMPONENT = 'radiobutton-component';
|
||||
registerComponentType(RADIOBUTTON_COMPONENT, ModelComponentTypes.RadioButton, RadioButtonComponent);
|
||||
|
||||
@@ -87,6 +87,7 @@ export default class DropDownComponent extends ComponentBase implements ICompone
|
||||
if (this.value) {
|
||||
this._dropdown.value = this.value;
|
||||
}
|
||||
this._dropdown.enabled = this.enabled;
|
||||
}
|
||||
|
||||
// CSS-bound properties
|
||||
|
||||
@@ -22,7 +22,7 @@ class FlexItem {
|
||||
@Component({
|
||||
template: `
|
||||
<div *ngIf="items" class="flexContainer" [style.flexFlow]="flexFlow" [style.justifyContent]="justifyContent"
|
||||
[style.alignItems]="alignItems" [style.alignContent]="alignContent">
|
||||
[style.alignItems]="alignItems" [style.alignContent]="alignContent" [style.height]="height">
|
||||
<div *ngFor="let item of items" [style.flex]="getItemFlex(item)" [style.order]="getItemOrder(item)" >
|
||||
<model-component-wrapper [descriptor]="item.descriptor" [modelStore]="modelStore">
|
||||
</model-component-wrapper>
|
||||
@@ -37,6 +37,7 @@ export default class FlexContainer extends ContainerBase<FlexItemLayout> impleme
|
||||
private _justifyContent: string;
|
||||
private _alignItems: string;
|
||||
private _alignContent: string;
|
||||
private _height: string;
|
||||
|
||||
@ViewChildren(ModelComponentWrapper) private _componentWrappers: QueryList<ModelComponentWrapper>;
|
||||
|
||||
@@ -70,6 +71,7 @@ export default class FlexContainer extends ContainerBase<FlexItemLayout> impleme
|
||||
this._justifyContent= layout.justifyContent ? layout.justifyContent : '';
|
||||
this._alignItems= layout.alignItems ? layout.alignItems : '';
|
||||
this._alignContent= layout.alignContent ? layout.alignContent : '';
|
||||
this._height= layout.height ? layout.height + 'px' : '';
|
||||
this.layout();
|
||||
}
|
||||
|
||||
@@ -86,6 +88,10 @@ export default class FlexContainer extends ContainerBase<FlexItemLayout> impleme
|
||||
return this._alignItems;
|
||||
}
|
||||
|
||||
public get height(): string {
|
||||
return this._height;
|
||||
}
|
||||
|
||||
public get alignContent(): string {
|
||||
return this._alignContent;
|
||||
}
|
||||
|
||||
119
src/sql/parts/modelComponents/radioButton.component.ts
Normal file
119
src/sql/parts/modelComponents/radioButton.component.ts
Normal file
@@ -0,0 +1,119 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./radioButton';
|
||||
import {
|
||||
Component, Input, Inject, ChangeDetectorRef, forwardRef, ComponentFactoryResolver,
|
||||
ViewChild, ViewChildren, ElementRef, Injector, OnDestroy, QueryList, AfterViewInit
|
||||
} from '@angular/core';
|
||||
|
||||
import * as sqlops from 'sqlops';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
|
||||
import { ComponentBase } from 'sql/parts/modelComponents/componentBase';
|
||||
import { IComponent, IComponentDescriptor, IModelStore, ComponentEventType } from 'sql/parts/modelComponents/interfaces';
|
||||
import { RadioButton } from 'sql/base/browser/ui/radioButton/radioButton';
|
||||
import { CommonServiceInterface } from 'sql/services/common/commonServiceInterface.service';
|
||||
|
||||
@Component({
|
||||
selector: 'radioButton',
|
||||
template: `
|
||||
<div #input class="modelview-radiobutton-container">
|
||||
|
||||
</div>
|
||||
`
|
||||
})
|
||||
export default class RadioButtonComponent extends ComponentBase implements IComponent, OnDestroy, AfterViewInit {
|
||||
@Input() descriptor: IComponentDescriptor;
|
||||
@Input() modelStore: IModelStore;
|
||||
private _input: RadioButton;
|
||||
|
||||
@ViewChild('input', { read: ElementRef }) private _inputContainer: ElementRef;
|
||||
constructor(
|
||||
@Inject(forwardRef(() => CommonServiceInterface)) private _commonService: CommonServiceInterface,
|
||||
@Inject(forwardRef(() => ChangeDetectorRef)) changeRef: ChangeDetectorRef) {
|
||||
super(changeRef);
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.baseInit();
|
||||
|
||||
}
|
||||
|
||||
ngAfterViewInit(): void {
|
||||
if (this._inputContainer) {
|
||||
this._input = new RadioButton(this._inputContainer.nativeElement, {
|
||||
label: this.label
|
||||
});
|
||||
|
||||
this._register(this._input);
|
||||
this._register(this._input.onClicked(e => {
|
||||
this._onEventEmitter.fire({
|
||||
eventType: ComponentEventType.onDidClick,
|
||||
args: e
|
||||
});
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.baseDestroy();
|
||||
}
|
||||
|
||||
/// IComponent implementation
|
||||
|
||||
public layout(): void {
|
||||
this._changeRef.detectChanges();
|
||||
}
|
||||
|
||||
public setLayout(layout: any): void {
|
||||
// TODO allow configuring the look and feel
|
||||
this.layout();
|
||||
}
|
||||
|
||||
public setProperties(properties: { [key: string]: any; }): void {
|
||||
super.setProperties(properties);
|
||||
this._input.name = this.name;
|
||||
this._input.value = this.value;
|
||||
this._input.label = this.label;
|
||||
this._input.enabled = this.enabled;
|
||||
|
||||
this._input.checked = this.checked;
|
||||
}
|
||||
|
||||
// CSS-bound properties
|
||||
|
||||
public get checked(): boolean {
|
||||
return this.getPropertyOrDefault<sqlops.RadioButtonProperties, boolean>((props) => props.checked, false);
|
||||
}
|
||||
|
||||
public set value(newValue: string) {
|
||||
this.setPropertyFromUI<sqlops.RadioButtonProperties, string>((properties, value) => { properties.checked = value; }, newValue);
|
||||
}
|
||||
|
||||
public get value(): string {
|
||||
return this.getPropertyOrDefault<sqlops.RadioButtonProperties, string>((props) => props.value, '');
|
||||
}
|
||||
|
||||
public getLabel(): string {
|
||||
return this.label;
|
||||
}
|
||||
|
||||
public get label(): string {
|
||||
return this.getPropertyOrDefault<sqlops.RadioButtonProperties, string>((props) => props.label, '');
|
||||
}
|
||||
|
||||
public set label(newValue: string) {
|
||||
this.setPropertyFromUI<sqlops.RadioButtonProperties, string>((properties, label) => { properties.label = label; }, newValue);
|
||||
}
|
||||
|
||||
public get name(): string {
|
||||
return this.getPropertyOrDefault<sqlops.RadioButtonProperties, string>((props) => props.name, '');
|
||||
}
|
||||
|
||||
public set name(newValue: string) {
|
||||
this.setPropertyFromUI<sqlops.RadioButtonProperties, string>((properties, label) => { properties.name = label; }, newValue);
|
||||
}
|
||||
}
|
||||
11
src/sql/parts/modelComponents/radioButton.css
Normal file
11
src/sql/parts/modelComponents/radioButton.css
Normal file
@@ -0,0 +1,11 @@
|
||||
.modelview-radiobutton-container {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.modelview-radiobutton-item {
|
||||
align-self: flex-start ;
|
||||
}
|
||||
|
||||
.modelview-radiobutton-title {
|
||||
|
||||
}
|
||||
30
src/sql/sqlops.proposed.d.ts
vendored
30
src/sql/sqlops.proposed.d.ts
vendored
@@ -21,6 +21,7 @@ declare module 'sqlops' {
|
||||
card(): ComponentBuilder<CardComponent>;
|
||||
inputBox(): ComponentBuilder<InputBoxComponent>;
|
||||
checkBox(): ComponentBuilder<CheckBoxComponent>;
|
||||
radioButton(): ComponentBuilder<RadioButtonComponent>;
|
||||
button(): ComponentBuilder<ButtonComponent>;
|
||||
dropDown(): ComponentBuilder<DropDownComponent>;
|
||||
dashboardWidget(widgetId: string): ComponentBuilder<WidgetComponent>;
|
||||
@@ -44,6 +45,22 @@ declare module 'sqlops' {
|
||||
|
||||
export interface FormBuilder extends ContainerBuilder<FormContainer, FormLayout, FormItemLayout> {
|
||||
withFormItems(components: FormComponent[], itemLayout?: FormItemLayout): ContainerBuilder<FormContainer, FormLayout, FormItemLayout>;
|
||||
|
||||
/**
|
||||
* Creates a collection of child components and adds them all to this container
|
||||
*
|
||||
* @param formComponents the definitions
|
||||
* @param {*} [itemLayout] Optional layout for the child items
|
||||
*/
|
||||
addFormItems(formComponents: Array<FormComponent>, itemLayout?: FormItemLayout): void;
|
||||
|
||||
/**
|
||||
* Creates a child component and adds it to this container.
|
||||
*
|
||||
* @param formComponent the component to be added
|
||||
* @param {*} [itemLayout] Optional layout for this child item
|
||||
*/
|
||||
addFormItem(formComponent: FormComponent, itemLayout?: FormItemLayout): void;
|
||||
}
|
||||
|
||||
export interface Component {
|
||||
@@ -147,6 +164,8 @@ declare module 'sqlops' {
|
||||
* Matches the align-content CSS property.
|
||||
*/
|
||||
alignContent?: string;
|
||||
|
||||
height? : number;
|
||||
}
|
||||
|
||||
export interface FlexItemLayout {
|
||||
@@ -236,6 +255,13 @@ declare module 'sqlops' {
|
||||
label?: string;
|
||||
}
|
||||
|
||||
export interface RadioButtonProperties {
|
||||
name?: string;
|
||||
label?: string;
|
||||
value?: string;
|
||||
checked?: boolean;
|
||||
}
|
||||
|
||||
export interface DropDownProperties {
|
||||
value?: string;
|
||||
values?: string[];
|
||||
@@ -256,6 +282,10 @@ declare module 'sqlops' {
|
||||
onTextChanged: vscode.Event<any>;
|
||||
}
|
||||
|
||||
export interface RadioButtonComponent extends Component, RadioButtonProperties {
|
||||
onDidClick: vscode.Event<any>;
|
||||
}
|
||||
|
||||
export interface CheckBoxComponent extends Component {
|
||||
checked: boolean;
|
||||
label: string;
|
||||
|
||||
@@ -71,6 +71,7 @@ export enum ModelComponentTypes {
|
||||
DropDown,
|
||||
Button,
|
||||
CheckBox,
|
||||
RadioButton,
|
||||
DashboardWidget,
|
||||
DashboardWebview,
|
||||
Form
|
||||
|
||||
@@ -59,6 +59,13 @@ class ModelBuilderImpl implements sqlops.ModelBuilder {
|
||||
return builder;
|
||||
}
|
||||
|
||||
radioButton(): sqlops.ComponentBuilder<sqlops.RadioButtonComponent> {
|
||||
let id = this.getNextComponentId();
|
||||
let builder: ComponentBuilderImpl<sqlops.RadioButtonComponent> = this.getComponentBuilder(new RadioButtonWrapper(this._proxy, this._handle, id), id);
|
||||
this._componentBuilders.set(id, builder);
|
||||
return builder;
|
||||
}
|
||||
|
||||
checkBox(): sqlops.ComponentBuilder<sqlops.CheckBoxComponent> {
|
||||
let id = this.getNextComponentId();
|
||||
let builder: ComponentBuilderImpl<sqlops.CheckBoxComponent> = this.getComponentBuilder(new CheckBoxWrapper(this._proxy, this._handle, id), id);
|
||||
@@ -177,36 +184,56 @@ class ContainerBuilderImpl<T extends sqlops.Component, TLayout, TItemLayout> ext
|
||||
}
|
||||
}
|
||||
|
||||
class FormContainerBuilder extends ContainerBuilderImpl<sqlops.FormContainer, sqlops.FormLayout, sqlops.FormItemLayout> {
|
||||
class FormContainerBuilder extends ContainerBuilderImpl<sqlops.FormContainer, sqlops.FormLayout, sqlops.FormItemLayout> implements sqlops.FormBuilder {
|
||||
|
||||
withFormItems(components: sqlops.FormComponent[], itemLayout?: sqlops.FormItemLayout): sqlops.ContainerBuilder<sqlops.FormContainer, sqlops.FormLayout, sqlops.FormItemLayout> {
|
||||
|
||||
this._component.itemConfigs = components.map(item => {
|
||||
let componentWrapper = item.component as ComponentWrapper;
|
||||
let actions: string[] = undefined;
|
||||
if (item.actions) {
|
||||
actions = item.actions.map(action => {
|
||||
let actionComponentWrapper = action as ComponentWrapper;
|
||||
return actionComponentWrapper.id;
|
||||
});
|
||||
}
|
||||
return new InternalItemConfig(componentWrapper, Object.assign({}, itemLayout, {
|
||||
title: item.title,
|
||||
actions: actions,
|
||||
isFormComponent: true
|
||||
}));
|
||||
return this.convertToItemConfig(item, itemLayout);
|
||||
});
|
||||
|
||||
components.forEach(formItem => {
|
||||
if (formItem.actions) {
|
||||
formItem.actions.forEach(component => {
|
||||
let componentWrapper = component as ComponentWrapper;
|
||||
this._component.itemConfigs.push(new InternalItemConfig(componentWrapper, itemLayout));
|
||||
});
|
||||
}
|
||||
this.addComponentActions(formItem, itemLayout);
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
private convertToItemConfig(formComponent: sqlops.FormComponent, itemLayout?: sqlops.FormItemLayout): InternalItemConfig {
|
||||
let componentWrapper = formComponent.component as ComponentWrapper;
|
||||
let actions: string[] = undefined;
|
||||
if (formComponent.actions) {
|
||||
actions = formComponent.actions.map(action => {
|
||||
let actionComponentWrapper = action as ComponentWrapper;
|
||||
return actionComponentWrapper.id;
|
||||
});
|
||||
}
|
||||
|
||||
return new InternalItemConfig(componentWrapper, Object.assign({}, itemLayout, {
|
||||
title: formComponent.title,
|
||||
actions: actions,
|
||||
isFormComponent: true
|
||||
}));
|
||||
}
|
||||
|
||||
private addComponentActions(formComponent: sqlops.FormComponent, itemLayout?: sqlops.FormItemLayout): void {
|
||||
if (formComponent.actions) {
|
||||
formComponent.actions.forEach(component => {
|
||||
let componentWrapper = component as ComponentWrapper;
|
||||
this._component.addItem(componentWrapper, itemLayout);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
addFormItems(formComponents: Array<sqlops.FormComponent>, itemLayout?: sqlops.FormItemLayout): void {
|
||||
formComponents.forEach(formComponent => {
|
||||
this.addFormItem(formComponent, itemLayout);
|
||||
});
|
||||
}
|
||||
|
||||
addFormItem(formComponent: sqlops.FormComponent, itemLayout?: sqlops.FormItemLayout): void {
|
||||
let itemImpl = this.convertToItemConfig(formComponent, itemLayout);
|
||||
this._component.addItem(formComponent.component as ComponentWrapper, itemImpl.config);
|
||||
this.addComponentActions(formComponent, itemLayout);
|
||||
}
|
||||
}
|
||||
|
||||
class InternalItemConfig {
|
||||
@@ -489,6 +516,47 @@ class CheckBoxWrapper extends ComponentWrapper implements sqlops.CheckBoxCompone
|
||||
}
|
||||
}
|
||||
|
||||
class RadioButtonWrapper extends ComponentWrapper implements sqlops.RadioButtonComponent {
|
||||
|
||||
constructor(proxy: MainThreadModelViewShape, handle: number, id: string) {
|
||||
super(proxy, handle, ModelComponentTypes.RadioButton, id);
|
||||
this.properties = {};
|
||||
this._emitterMap.set(ComponentEventType.onDidClick, new Emitter<any>());
|
||||
}
|
||||
|
||||
public get name(): string {
|
||||
return this.properties['name'];
|
||||
}
|
||||
public set name(v: string) {
|
||||
this.setProperty('name', v);
|
||||
}
|
||||
|
||||
public get label(): string {
|
||||
return this.properties['label'];
|
||||
}
|
||||
public set label(v: string) {
|
||||
this.setProperty('label', v);
|
||||
}
|
||||
|
||||
public get value(): string {
|
||||
return this.properties['value'];
|
||||
}
|
||||
public set value(v: string) {
|
||||
this.setProperty('value', v);
|
||||
}
|
||||
public get checked(): boolean {
|
||||
return this.properties['checked'];
|
||||
}
|
||||
public set checked(v: boolean) {
|
||||
this.setProperty('checked', v);
|
||||
}
|
||||
|
||||
public get onDidClick(): vscode.Event<any> {
|
||||
let emitter = this._emitterMap.get(ComponentEventType.onDidClick);
|
||||
return emitter && emitter.event;
|
||||
}
|
||||
}
|
||||
|
||||
class DropDownWrapper extends ComponentWrapper implements sqlops.DropDownComponent {
|
||||
|
||||
constructor(proxy: MainThreadModelViewShape, handle: number, id: string) {
|
||||
|
||||
Reference in New Issue
Block a user